Merge branch 'release/1.8.6' into production
This commit is contained in:
commit
093f3ed412
17
docs.json
17
docs.json
|
@ -3,18 +3,21 @@
|
|||
"content": [
|
||||
"lib/application.js",
|
||||
"lib/loopback.js",
|
||||
"lib/middleware/token.js",
|
||||
{ "title": "Base model", "depth": 2 },
|
||||
"lib/models/model.js",
|
||||
"lib/models/data-model.js",
|
||||
{ "title": "Middleware", "depth": 2 },
|
||||
"lib/middleware/rest.js",
|
||||
"lib/middleware/status.js",
|
||||
"lib/middleware/token.js",
|
||||
"lib/middleware/urlNotFound.js",
|
||||
{ "title": "Built-in models", "depth": 2 },
|
||||
"lib/models/access-token.js",
|
||||
"lib/models/access-context.js",
|
||||
"lib/models/acl.js",
|
||||
"lib/models/application.js",
|
||||
"lib/models/email.js",
|
||||
"lib/models/model.js",
|
||||
"lib/models/data-model.js",
|
||||
"lib/models/role.js",
|
||||
"lib/models/user.js",
|
||||
"docs/api-model.md",
|
||||
"docs/api-model-remote.md"
|
||||
"lib/models/user.js"
|
||||
],
|
||||
"assets": "/docs/assets"
|
||||
}
|
||||
|
|
|
@ -1,212 +0,0 @@
|
|||
## Data Source object
|
||||
|
||||
LoopBack models can manipulate data via the DataSource object. Attaching a `DataSource` to a `Model` adds instance methods and static methods to the `Model`; some of the added methods may be remote methods.
|
||||
|
||||
Define a data source for persisting models.
|
||||
|
||||
```js
|
||||
var oracle = loopback.createDataSource({
|
||||
connector: 'oracle',
|
||||
host: '111.22.333.44',
|
||||
database: 'MYDB',
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
});
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
#### dataSource.createModel(name, properties, options)
|
||||
|
||||
Define a model and attach it to a `DataSource`.
|
||||
|
||||
```js
|
||||
var Color = oracle.createModel('color', {name: String});
|
||||
```
|
||||
|
||||
You can define an ACL when you create a new data source with the `DataSource.create()` method. For example:
|
||||
|
||||
```js
|
||||
var Customer = ds.createModel('Customer', {
|
||||
name: {
|
||||
type: String,
|
||||
acls: [
|
||||
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
|
||||
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
acls: [
|
||||
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
#### dataSource.discoverModelDefinitions([username], fn)
|
||||
|
||||
Discover a set of model definitions (table or collection names) based on tables or collections in a data source.
|
||||
|
||||
```js
|
||||
oracle.discoverModelDefinitions(function (err, models) {
|
||||
models.forEach(function (def) {
|
||||
// def.name ~ the model name
|
||||
oracle.discoverSchema(null, def.name, function (err, schema) {
|
||||
console.log(schema);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### dataSource.discoverSchema([owner], name, fn)
|
||||
|
||||
Discover the schema of a specific table or collection.
|
||||
|
||||
**Example schema from oracle connector:**
|
||||
|
||||
```js
|
||||
{
|
||||
"name": "Product",
|
||||
"options": {
|
||||
"idInjection": false,
|
||||
"oracle": {
|
||||
"schema": "BLACKPOOL",
|
||||
"table": "PRODUCT"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "String",
|
||||
"required": true,
|
||||
"length": 20,
|
||||
"id": 1,
|
||||
"oracle": {
|
||||
"columnName": "ID",
|
||||
"dataType": "VARCHAR2",
|
||||
"dataLength": 20,
|
||||
"nullable": "N"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "String",
|
||||
"required": false,
|
||||
"length": 64,
|
||||
"oracle": {
|
||||
"columnName": "NAME",
|
||||
"dataType": "VARCHAR2",
|
||||
"dataLength": 64,
|
||||
"nullable": "Y"
|
||||
}
|
||||
},
|
||||
"audibleRange": {
|
||||
"type": "Number",
|
||||
"required": false,
|
||||
"length": 22,
|
||||
"oracle": {
|
||||
"columnName": "AUDIBLE_RANGE",
|
||||
"dataType": "NUMBER",
|
||||
"dataLength": 22,
|
||||
"nullable": "Y"
|
||||
}
|
||||
},
|
||||
"effectiveRange": {
|
||||
"type": "Number",
|
||||
"required": false,
|
||||
"length": 22,
|
||||
"oracle": {
|
||||
"columnName": "EFFECTIVE_RANGE",
|
||||
"dataType": "NUMBER",
|
||||
"dataLength": 22,
|
||||
"nullable": "Y"
|
||||
}
|
||||
},
|
||||
"rounds": {
|
||||
"type": "Number",
|
||||
"required": false,
|
||||
"length": 22,
|
||||
"oracle": {
|
||||
"columnName": "ROUNDS",
|
||||
"dataType": "NUMBER",
|
||||
"dataLength": 22,
|
||||
"nullable": "Y"
|
||||
}
|
||||
},
|
||||
"extras": {
|
||||
"type": "String",
|
||||
"required": false,
|
||||
"length": 64,
|
||||
"oracle": {
|
||||
"columnName": "EXTRAS",
|
||||
"dataType": "VARCHAR2",
|
||||
"dataLength": 64,
|
||||
"nullable": "Y"
|
||||
}
|
||||
},
|
||||
"fireModes": {
|
||||
"type": "String",
|
||||
"required": false,
|
||||
"length": 64,
|
||||
"oracle": {
|
||||
"columnName": "FIRE_MODES",
|
||||
"dataType": "VARCHAR2",
|
||||
"dataLength": 64,
|
||||
"nullable": "Y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### dataSource.enableRemote(operation)
|
||||
|
||||
Enable remote access to a data source operation. Each [connector](#connector) has its own set of set remotely enabled and disabled operations. You can always list these by calling `dataSource.operations()`.
|
||||
|
||||
|
||||
#### dataSource.disableRemote(operation)
|
||||
|
||||
Disable remote access to a data source operation. Each [connector](#connector) has its own set of set enabled and disabled operations. You can always list these by calling `dataSource.operations()`.
|
||||
|
||||
```js
|
||||
// all rest data source operations are
|
||||
// disabled by default
|
||||
var oracle = loopback.createDataSource({
|
||||
connector: require('loopback-connector-oracle'),
|
||||
host: '...',
|
||||
...
|
||||
});
|
||||
|
||||
// or only disable it as a remote method
|
||||
oracle.disableRemote('destroyAll');
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Disabled operations will not be added to attached models.
|
||||
- Disabling the remoting for a method only affects client access (it will still be available from server models).
|
||||
- Data sources must enable / disable operations before attaching or creating models.
|
||||
|
||||
#### dataSource.operations()
|
||||
|
||||
List the enabled and disabled operations.
|
||||
|
||||
console.log(oracle.operations());
|
||||
|
||||
Output:
|
||||
|
||||
```js
|
||||
{
|
||||
find: {
|
||||
remoteEnabled: true,
|
||||
accepts: [...],
|
||||
returns: [...]
|
||||
enabled: true
|
||||
},
|
||||
save: {
|
||||
remoteEnabled: true,
|
||||
prototype: true,
|
||||
accepts: [...],
|
||||
returns: [...],
|
||||
enabled: true
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
|
@ -1,69 +0,0 @@
|
|||
## GeoPoint object
|
||||
|
||||
The GeoPoint object represents a physical location.
|
||||
|
||||
Use the `GeoPoint` class.
|
||||
|
||||
```js
|
||||
var GeoPoint = require('loopback').GeoPoint;
|
||||
```
|
||||
|
||||
Embed a latitude / longitude point in a [Model](#model).
|
||||
|
||||
```js
|
||||
var CoffeeShop = loopback.createModel('coffee-shop', {
|
||||
location: 'GeoPoint'
|
||||
});
|
||||
```
|
||||
|
||||
You can query LoopBack models with a GeoPoint property and an attached data source using geo-spatial filters and sorting. For example, the following code finds the three nearest coffee shops.
|
||||
|
||||
```js
|
||||
CoffeeShop.attachTo(oracle);
|
||||
var here = new GeoPoint({lat: 10.32424, lng: 5.84978});
|
||||
CoffeeShop.find({where: {location: {near: here}}, limit:3}, function(err, nearbyShops) {
|
||||
console.info(nearbyShops); // [CoffeeShop, ...]
|
||||
});
|
||||
```
|
||||
|
||||
### Distance Types
|
||||
|
||||
**Note:** all distance methods use `miles` by default.
|
||||
|
||||
- `miles`
|
||||
- `radians`
|
||||
- `kilometers`
|
||||
- `meters`
|
||||
- `miles`
|
||||
- `feet`
|
||||
- `degrees`
|
||||
|
||||
### Methods
|
||||
|
||||
#### geoPoint.distanceTo(geoPoint, options)
|
||||
|
||||
Get the distance to another `GeoPoint`; for example:
|
||||
|
||||
```js
|
||||
var here = new GeoPoint({lat: 10, lng: 10});
|
||||
var there = new GeoPoint({lat: 5, lng: 5});
|
||||
console.log(here.distanceTo(there, {type: 'miles'})); // 438
|
||||
```
|
||||
|
||||
#### GeoPoint.distanceBetween(a, b, options)
|
||||
|
||||
Get the distance between two points; for example:
|
||||
|
||||
```js
|
||||
GeoPoint.distanceBetween(here, there, {type: 'miles'}) // 438
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
#### geoPoint.lat
|
||||
|
||||
The latitude point in degrees. Range: -90 to 90.
|
||||
|
||||
#### geoPoint.lng
|
||||
|
||||
The longitude point in degrees. Range: -180 to 180.
|
|
@ -1,448 +0,0 @@
|
|||
## Model object
|
||||
|
||||
A Loopback `Model` is a vanilla JavaScript class constructor with an attached set of properties and options. A `Model` instance is created by passing a data object containing properties to the `Model` constructor. A `Model` constructor will clean the object passed to it and only set the values matching the properties you define.
|
||||
|
||||
```js
|
||||
// valid color
|
||||
var Color = loopback.createModel('color', {name: String});
|
||||
var red = new Color({name: 'red'});
|
||||
console.log(red.name); // red
|
||||
|
||||
// invalid color
|
||||
var foo = new Color({bar: 'bat baz'});
|
||||
console.log(foo.bar); // undefined
|
||||
```
|
||||
|
||||
**Properties**
|
||||
|
||||
A model defines a list of property names, types and other validation metadata. A [DataSource](#data-source) uses this definition to validate a `Model` during operations such as `save()`.
|
||||
|
||||
**Options**
|
||||
|
||||
Some [DataSources](#data-source) may support additional `Model` options.
|
||||
|
||||
Define A Loopbackmodel.
|
||||
|
||||
```js
|
||||
var User = loopback.createModel('user', {
|
||||
first: String,
|
||||
last: String,
|
||||
age: Number
|
||||
});
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
#### Model.attachTo(dataSource)
|
||||
|
||||
Attach a model to a [DataSource](#data-source). Attaching a [DataSource](#data-source) updates the model with additional methods and behaviors.
|
||||
|
||||
```js
|
||||
var oracle = loopback.createDataSource({
|
||||
connector: require('loopback-connector-oracle'),
|
||||
host: '111.22.333.44',
|
||||
database: 'MYDB',
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
});
|
||||
|
||||
User.attachTo(oracle);
|
||||
```
|
||||
|
||||
NOTE: until a model is attached to a data source it will not have any attached methods.
|
||||
|
||||
### Properties
|
||||
|
||||
#### Model.properties
|
||||
|
||||
An object containing a normalized set of properties supplied to `loopback.createModel(name, properties)`.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
var props = {
|
||||
a: String,
|
||||
b: {type: 'Number'},
|
||||
c: {type: 'String', min: 10, max: 100},
|
||||
d: Date,
|
||||
e: loopback.GeoPoint
|
||||
};
|
||||
|
||||
var MyModel = loopback.createModel('foo', props);
|
||||
|
||||
console.log(MyModel.properties);
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
```js
|
||||
{
|
||||
"a": {type: String},
|
||||
"b": {type: Number},
|
||||
"c": {
|
||||
"type": String,
|
||||
"min": 10,
|
||||
"max": 100
|
||||
},
|
||||
"d": {type: Date},
|
||||
"e": {type: GeoPoint},
|
||||
"id": {
|
||||
"id": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CRUD and Query Mixins
|
||||
|
||||
Mixins are added by attaching a vanilla model to a [data source](#data-source) with a [connector](#connectors). Each [connector](#connectors) enables its own set of operations that are mixed into a `Model` as methods. To see available methods for a data source call `dataSource.operations()`.
|
||||
|
||||
Log the available methods for a memory data source.
|
||||
|
||||
```js
|
||||
var ops = loopback
|
||||
.createDataSource({connector: loopback.Memory})
|
||||
.operations();
|
||||
|
||||
console.log(Object.keys(ops));
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
```js
|
||||
[ 'create',
|
||||
'updateOrCreate',
|
||||
'upsert',
|
||||
'findOrCreate',
|
||||
'exists',
|
||||
'findById',
|
||||
'find',
|
||||
'all',
|
||||
'findOne',
|
||||
'destroyAll',
|
||||
'deleteAll',
|
||||
'count',
|
||||
'include',
|
||||
'relationNameFor',
|
||||
'hasMany',
|
||||
'belongsTo',
|
||||
'hasAndBelongsToMany',
|
||||
'save',
|
||||
'isNewRecord',
|
||||
'destroy',
|
||||
'delete',
|
||||
'updateAttribute',
|
||||
'updateAttributes',
|
||||
'reload' ]
|
||||
```
|
||||
|
||||
Here is the definition of the `count()` operation.
|
||||
|
||||
```js
|
||||
{
|
||||
accepts: [ { arg: 'where', type: 'object' } ],
|
||||
http: { verb: 'get', path: '/count' },
|
||||
remoteEnabled: true,
|
||||
name: 'count'
|
||||
}
|
||||
```
|
||||
|
||||
### Static Methods
|
||||
|
||||
**Note:** These are the default mixin methods for a `Model` attached to a data source. See the specific connector for additional API documentation.
|
||||
|
||||
#### Model.create(data, [callback])
|
||||
|
||||
Create an instance of Model with given data and save to the attached data source. Callback is optional.
|
||||
|
||||
```js
|
||||
User.create({first: 'Joe', last: 'Bob'}, function(err, user) {
|
||||
console.log(user instanceof User); // true
|
||||
});
|
||||
```
|
||||
|
||||
**Note:** You must include a callback and use the created model provided in the callback if your code depends on your model being saved or having an `id`.
|
||||
|
||||
#### Model.count([query], callback)
|
||||
|
||||
Query count of Model instances in data source. Optional query param allows to count filtered set of Model instances.
|
||||
|
||||
```js
|
||||
User.count({approved: true}, function(err, count) {
|
||||
console.log(count); // 2081
|
||||
});
|
||||
```
|
||||
|
||||
#### Model.find(filter, callback)
|
||||
|
||||
Find all instances of Model, matched by query. Fields used for filter and sort should be declared with `{index: true}` in model definition.
|
||||
|
||||
**filter**
|
||||
|
||||
- **where** `Object` { key: val, key2: {gt: 'val2'}} The search criteria
|
||||
- Format: {key: val} or {key: {op: val}}
|
||||
- Operations:
|
||||
- gt: >
|
||||
- gte: >=
|
||||
- lt: <
|
||||
- lte: <=
|
||||
- between
|
||||
- inq: IN
|
||||
- nin: NOT IN
|
||||
- neq: !=
|
||||
- like: LIKE
|
||||
- nlike: NOT LIKE
|
||||
|
||||
- **include** `String`, `Object` or `Array` Allows you to load relations of several objects and optimize numbers of requests.
|
||||
- Format:
|
||||
- 'posts': Load posts
|
||||
- ['posts', 'passports']: Load posts and passports
|
||||
- {'owner': 'posts'}: Load owner and owner's posts
|
||||
- {'owner': ['posts', 'passports']}: Load owner, owner's posts, and owner's passports
|
||||
- {'owner': [{posts: 'images'}, 'passports']}: Load owner, owner's posts, owner's posts' images, and owner's passports
|
||||
|
||||
- **order** `String` The sorting order
|
||||
- Format: 'key1 ASC, key2 DESC'
|
||||
|
||||
- **limit** `Number` The maximum number of instances to be returned
|
||||
- **skip** `Number` Skip the number of instances
|
||||
- **offset** `Number` Alias for skip
|
||||
|
||||
- **fields** `Object|Array|String` The included/excluded fields
|
||||
- `['foo']` or `'foo'` - include only the foo property
|
||||
- `['foo', 'bar']` - include the foo and bar properties
|
||||
- `{foo: true}` - include only foo
|
||||
- `{bat: false}` - include all properties, exclude bat
|
||||
|
||||
Find the second page of 10 users over age 21 in descending order exluding the password property.
|
||||
|
||||
```js
|
||||
User.find({
|
||||
where: {
|
||||
age: {gt: 21}},
|
||||
order: 'age DESC',
|
||||
limit: 10,
|
||||
skip: 10,
|
||||
fields: {password: false}
|
||||
},
|
||||
console.log
|
||||
);
|
||||
```
|
||||
|
||||
**Note:** See the specific connector's [docs](#connectors) for more info.
|
||||
|
||||
#### Model.destroyAll([where], callback)
|
||||
|
||||
Delete all Model instances from data source. **Note:** destroyAll method does not perform destroy hooks.
|
||||
|
||||
```js
|
||||
Product.destroyAll({price: {gt: 99}}, function(err) {
|
||||
// removed matching products
|
||||
});
|
||||
```
|
||||
|
||||
> **NOTE:* `where` is optional and a where object... do NOT pass a filter object
|
||||
|
||||
#### Model.findById(id, callback)
|
||||
|
||||
Find instance by id.
|
||||
|
||||
```js
|
||||
User.findById(23, function(err, user) {
|
||||
console.info(user.id); // 23
|
||||
});
|
||||
```
|
||||
|
||||
#### Model.findOne(where, callback)
|
||||
|
||||
Find a single instance that matches the given where expression.
|
||||
|
||||
```js
|
||||
User.findOne({where: {id: 23}}, function(err, user) {
|
||||
console.info(user.id); // 23
|
||||
});
|
||||
```
|
||||
|
||||
#### Model.upsert(data, callback)
|
||||
|
||||
Update when record with id=data.id found, insert otherwise. **Note:** no setters, validations or hooks applied when using upsert.
|
||||
|
||||
#### Custom static methods
|
||||
|
||||
Define a static model method.
|
||||
|
||||
```js
|
||||
User.login = function (username, password, fn) {
|
||||
var passwordHash = hashPassword(password);
|
||||
this.findOne({username: username}, function (err, user) {
|
||||
var failErr = new Error('login failed');
|
||||
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else if(!user) {
|
||||
fn(failErr);
|
||||
} else if(user.password === passwordHash) {
|
||||
MyAccessTokenModel.create({userId: user.id}, function (err, accessToken) {
|
||||
fn(null, accessToken.id);
|
||||
});
|
||||
} else {
|
||||
fn(failErr);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Setup the static model method to be exposed to clients as a [remote method](#remote-method).
|
||||
|
||||
```js
|
||||
loopback.remoteMethod(
|
||||
User.login,
|
||||
{
|
||||
accepts: [
|
||||
{arg: 'username', type: 'string', required: true},
|
||||
{arg: 'password', type: 'string', required: true}
|
||||
],
|
||||
returns: {arg: 'sessionId', type: 'any'},
|
||||
http: {path: '/sign-in'}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Instance methods
|
||||
|
||||
**Note:** These are the default mixin methods for a `Model` attached to a data source. See the specific connector for additional API documentation.
|
||||
|
||||
#### model.save([options], [callback])
|
||||
|
||||
Save an instance of a Model to the attached data source.
|
||||
|
||||
```js
|
||||
var joe = new User({first: 'Joe', last: 'Bob'});
|
||||
joe.save(function(err, user) {
|
||||
if(user.errors) {
|
||||
console.log(user.errors);
|
||||
} else {
|
||||
console.log(user.id);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### model.updateAttributes(data, [callback])
|
||||
|
||||
Save specified attributes to the attached data source.
|
||||
|
||||
```js
|
||||
user.updateAttributes({
|
||||
first: 'updatedFirst',
|
||||
name: 'updatedLast'
|
||||
}, fn);
|
||||
```
|
||||
|
||||
#### model.destroy([callback])
|
||||
|
||||
Remove a model from the attached data source.
|
||||
|
||||
```js
|
||||
model.destroy(function(err) {
|
||||
// model instance destroyed
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom instance methods
|
||||
|
||||
Define an instance method.
|
||||
|
||||
```js
|
||||
User.prototype.logout = function (fn) {
|
||||
MySessionModel.destroyAll({userId: this.id}, fn);
|
||||
}
|
||||
```
|
||||
|
||||
Define a remote model instance method.
|
||||
|
||||
```js
|
||||
loopback.remoteMethod(User.prototype.logout)
|
||||
```
|
||||
|
||||
### Relationships
|
||||
|
||||
#### Model.hasMany(Model, options)
|
||||
|
||||
Define a "one to many" relationship.
|
||||
|
||||
```js
|
||||
// by referencing model
|
||||
Book.hasMany(Chapter);
|
||||
// specify the name
|
||||
Book.hasMany('chapters', {model: Chapter});
|
||||
```
|
||||
|
||||
Query and create the related models.
|
||||
|
||||
```js
|
||||
Book.create(function(err, book) {
|
||||
// create a chapter instance
|
||||
// ready to be saved in the data source
|
||||
var chapter = book.chapters.build({name: 'Chapter 1'});
|
||||
|
||||
// save the new chapter
|
||||
chapter.save();
|
||||
|
||||
// you can also call the Chapter.create method with
|
||||
// the `chapters` property which will build a chapter
|
||||
// instance and save the it in the data source
|
||||
book.chapters.create({name: 'Chapter 2'}, function(err, savedChapter) {
|
||||
// this callback is optional
|
||||
});
|
||||
|
||||
// query chapters for the book using the
|
||||
book.chapters(function(err, chapters) {
|
||||
// all chapters with bookId = book.id
|
||||
console.log(chapters);
|
||||
});
|
||||
|
||||
book.chapters({where: {name: 'test'}, function(err, chapters) {
|
||||
// all chapters with bookId = book.id and name = 'test'
|
||||
console.log(chapters);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### Model.belongsTo(Model, options)
|
||||
|
||||
A `belongsTo` relation sets up a one-to-one connection with another model, such
|
||||
that each instance of the declaring model "belongs to" one instance of the other
|
||||
model. For example, if your application includes users and posts, and each post
|
||||
can be written by exactly one user.
|
||||
|
||||
```js
|
||||
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
||||
```
|
||||
|
||||
The code above basically says Post has a reference called `author` to User using
|
||||
the `userId` property of Post as the foreign key. Now we can access the author
|
||||
in one of the following styles:
|
||||
|
||||
```js
|
||||
post.author(callback); // Get the User object for the post author asynchronously
|
||||
post.author(); // Get the User object for the post author synchronously
|
||||
post.author(user) // Set the author to be the given user
|
||||
```
|
||||
|
||||
#### Model.hasAndBelongsToMany(Model, options)
|
||||
|
||||
A `hasAndBelongsToMany` relation creates a direct many-to-many connection with
|
||||
another model, with no intervening model. For example, if your application
|
||||
includes users and groups, with each group having many users and each user
|
||||
appearing in many groups, you could declare the models this way,
|
||||
|
||||
```js
|
||||
User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'});
|
||||
user.groups(callback); // get groups of the user
|
||||
user.groups.create(data, callback); // create a new group and connect it with the user
|
||||
user.groups.add(group, callback); // connect an existing group with the user
|
||||
user.groups.remove(group, callback); // remove the user from the group
|
||||
```
|
||||
|
||||
### Shared methods
|
||||
|
||||
Any static or instance method can be decorated as `shared`. These methods are exposed over the provided transport (eg. [loopback.rest](#rest)).
|
|
@ -1,17 +1,24 @@
|
|||
/**
|
||||
/*!
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var loopback = require('../loopback');
|
||||
|
||||
/**
|
||||
/*!
|
||||
* Export the middleware.
|
||||
*/
|
||||
|
||||
module.exports = rest;
|
||||
|
||||
/**
|
||||
* Build a temp app for mounting resources.
|
||||
* Expose models over REST.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* app.use(loopback.rest());
|
||||
* ```
|
||||
* For more information, see [Exposing models over a REST API](http://docs.strongloop.com/display/DOC/Exposing+models+over+a+REST+API).
|
||||
* @header loopback.rest()
|
||||
*/
|
||||
|
||||
function rest() {
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
/**
|
||||
/*!
|
||||
* Export the middleware.
|
||||
*/
|
||||
|
||||
module.exports = status;
|
||||
|
||||
/**
|
||||
* Return [HTTP response](http://expressjs.com/4x/api.html#res.send) with basic application status information:
|
||||
* date the application was started and uptime, in JSON format.
|
||||
* For example:
|
||||
* ```js
|
||||
* {
|
||||
* "started": "2014-06-05T00:26:49.750Z",
|
||||
* "uptime": 9.394
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @header loopback.status()
|
||||
*/
|
||||
function status() {
|
||||
var started = new Date();
|
||||
|
||||
|
|
|
@ -12,14 +12,17 @@ var assert = require('assert');
|
|||
module.exports = token;
|
||||
|
||||
/**
|
||||
* **Options**
|
||||
* Check for an access token in cookies, headers, and query string parameters.
|
||||
* This function always checks for the following:
|
||||
*
|
||||
* - `cookies` - An `Array` of cookie names
|
||||
* - `headers` - An `Array` of header names
|
||||
* - `params` - An `Array` of param names
|
||||
* - `model` - Specify an AccessToken class to use
|
||||
* - `access_token`
|
||||
* - `X-Access-Token`
|
||||
* - `authorization`
|
||||
*
|
||||
* It checks for these values in cookies, headers, and query string parameters _in addition_ to the items
|
||||
* specified in the options parameter.
|
||||
*
|
||||
* Each array is used to add additional keys to find an `accessToken` for a `request`.
|
||||
* **NOTE:** This function only checks for [signed cookies](http://expressjs.com/api.html#req.signedCookies).
|
||||
*
|
||||
* The following example illustrates how to check for an `accessToken` in a custom cookie, query string parameter
|
||||
* and header called `foo-auth`.
|
||||
|
@ -28,23 +31,16 @@ module.exports = token;
|
|||
* app.use(loopback.token({
|
||||
* cookies: ['foo-auth'],
|
||||
* headers: ['foo-auth', 'X-Foo-Auth'],
|
||||
* cookies: ['foo-auth', 'foo_auth']
|
||||
* params: ['foo-auth', 'foo_auth']
|
||||
* }));
|
||||
* ```
|
||||
*
|
||||
* **Defaults**
|
||||
*
|
||||
* By default the following names will be checked. These names are appended to any optional names. They will always
|
||||
* be checked, but any names specified will be checked first.
|
||||
*
|
||||
* - **access_token**
|
||||
* - **X-Access-Token**
|
||||
* - **authorization**
|
||||
* - **access_token**
|
||||
*
|
||||
* **NOTE:** The `loopback.token()` middleware will only check for [signed cookies](http://expressjs.com/api.html#req.signedCookies).
|
||||
*
|
||||
* @header loopback.token(options)
|
||||
* @options {Object} [options] Each option array is used to add additional keys to find an `accessToken` for a `request`.
|
||||
* @property {Array} [cookies] Array of cookie names.
|
||||
* @property {Array} [headers] Array of header names.
|
||||
* @property {Array} [params] Array of param names.
|
||||
* @property {Array} [model] An AccessToken object to use.
|
||||
* @header loopback.token([options])
|
||||
*/
|
||||
|
||||
function token(options) {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
/**
|
||||
/*!
|
||||
* Export the middleware.
|
||||
* See discussion in Connect pull request #954 for more details
|
||||
* https://github.com/senchalabs/connect/pull/954.
|
||||
*/
|
||||
module.exports = urlNotFound;
|
||||
|
||||
/**
|
||||
* Convert any request not handled so far to a 404 error
|
||||
* to be handled by error-handling middleware.
|
||||
* See discussion in Connect pull request #954 for more details
|
||||
* https://github.com/senchalabs/connect/pull/954
|
||||
* @header loopback.urlNotFound()
|
||||
*/
|
||||
function urlNotFound() {
|
||||
return function raiseUrlNotFoundError(req, res, next) {
|
||||
|
|
|
@ -73,14 +73,12 @@ function convertNullToNotFoundError(ctx, cb) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create new instance of Model class, saved in database
|
||||
* Create new instance of Model class, saved in database.
|
||||
*
|
||||
* @param data [optional]
|
||||
* @param callback(err, obj)
|
||||
* callback called with arguments:
|
||||
*
|
||||
* - err (null or Error)
|
||||
* - instance (null or Model)
|
||||
* @param {Object} [data] Object containing model instance data.
|
||||
* @callback {Function} callback Callback function; see below.
|
||||
* @param {Error|null} err Error object
|
||||
* @param {Model|null} Model instance
|
||||
*/
|
||||
|
||||
DataModel.create = function (data, callback) {
|
||||
|
@ -100,10 +98,15 @@ setRemoting(DataModel.create, {
|
|||
* @param {Function} [callback] The callback function
|
||||
*/
|
||||
|
||||
DataModel.upsert = DataModel.updateOrCreate = function upsert(data, callback) {
|
||||
DataModel.upsert = function upsert(data, callback) {
|
||||
throwNotAttached(this.modelName, 'updateOrCreate');
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias for upsert function.
|
||||
*/
|
||||
DataModel.updateOrCreate = DataModel.upsert;
|
||||
|
||||
// upsert ~ remoting attributes
|
||||
setRemoting(DataModel.upsert, {
|
||||
description: 'Update an existing model instance or insert a new one into the data source',
|
||||
|
@ -113,8 +116,8 @@ setRemoting(DataModel.upsert, {
|
|||
});
|
||||
|
||||
/**
|
||||
* Find one record, same as `all`, limited by 1 and return object, not collection,
|
||||
* if not found, create using data provided as second argument
|
||||
* Find one record instance, same as `all`, limited by one and return object, not collection.
|
||||
* If not found, create the record using data provided as second argument.
|
||||
*
|
||||
* @param {Object} query - search conditions: {where: {test: 'me'}}.
|
||||
* @param {Object} data - object to create.
|
||||
|
@ -126,7 +129,7 @@ DataModel.findOrCreate = function findOrCreate(query, data, callback) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Check whether a model instance exists in database
|
||||
* Check whether a model instance exists in database.
|
||||
*
|
||||
* @param {id} id - identifier of object (primary key value)
|
||||
* @param {Function} cb - callbacl called with (err, exists: Bool)
|
||||
|
@ -263,10 +266,15 @@ setRemoting(DataModel.count, {
|
|||
});
|
||||
|
||||
/**
|
||||
* Save instance. When instance haven't id, create method called instead.
|
||||
* Triggers: validate, save, update | create
|
||||
* @param options {validate: true, throws: false} [optional]
|
||||
* @param callback(err, obj)
|
||||
* Save instance. When instance does not have an ID, create method instead.
|
||||
* Triggers: validate, save, update or create.
|
||||
*
|
||||
* @options [options] Options
|
||||
* @property {Boolean} validate Whether to validate.
|
||||
* @property {Boolean} throws
|
||||
* @callback {Function} callback Callback function.
|
||||
* @param {Error} err Error object
|
||||
* @param {Object}
|
||||
*/
|
||||
|
||||
DataModel.prototype.save = function (options, callback) {
|
||||
|
@ -290,7 +298,7 @@ DataModel.prototype.save = function (options, callback) {
|
|||
|
||||
/**
|
||||
* Determine if the data model is new.
|
||||
* @returns {Boolean}
|
||||
* @returns {Boolean} True if data model is new.
|
||||
*/
|
||||
|
||||
DataModel.prototype.isNewRecord = function () {
|
||||
|
@ -310,9 +318,8 @@ DataModel.prototype.destroy = function (cb) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Update single attribute
|
||||
*
|
||||
* equals to `updateAttributes({name: value}, cb)
|
||||
* Update single attribute.
|
||||
* Equivalent to `updateAttributes({name: value}, cb)`
|
||||
*
|
||||
* @param {String} name - name of property
|
||||
* @param {Mixed} value - value of property
|
||||
|
@ -326,7 +333,7 @@ DataModel.prototype.updateAttribute = function updateAttribute(name, value, call
|
|||
/**
|
||||
* Update set of attributes
|
||||
*
|
||||
* this method performs validation before updating
|
||||
* Performs validation before updating
|
||||
*
|
||||
* @trigger `validation`, `save` and `update` hooks
|
||||
* @param {Object} data - data to update
|
||||
|
@ -349,7 +356,9 @@ setRemoting(DataModel.prototype.updateAttributes, {
|
|||
* Reload object from persistence
|
||||
*
|
||||
* @requires `id` member of `object` to be able to call `find`
|
||||
* @param {Function} callback - called with (err, instance) arguments
|
||||
* @callback {Function} callback Callback function
|
||||
* @param {Error} err
|
||||
* @param {Object} instance
|
||||
*/
|
||||
|
||||
DataModel.prototype.reload = function reload(callback) {
|
||||
|
|
|
@ -2,14 +2,12 @@
|
|||
* Module Dependencies.
|
||||
*/
|
||||
|
||||
var Model = require('../loopback').Model
|
||||
, loopback = require('../loopback')
|
||||
var loopback = require('../loopback')
|
||||
, Model = loopback.Model
|
||||
, path = require('path')
|
||||
, SALT_WORK_FACTOR = 10
|
||||
, crypto = require('crypto')
|
||||
, bcrypt = require('bcryptjs')
|
||||
, passport = require('passport')
|
||||
, LocalStrategy = require('passport-local').Strategy
|
||||
, BaseAccessToken = require('./access-token')
|
||||
, DEFAULT_TTL = 1209600 // 2 weeks in seconds
|
||||
, DEFAULT_RESET_PW_TTL = 15 * 60 // 15 mins in seconds
|
||||
|
@ -128,6 +126,23 @@ var options = {
|
|||
|
||||
var User = module.exports = Model.extend('User', properties, options);
|
||||
|
||||
/**
|
||||
* Create access token for the logged in user. This method can be overridden to
|
||||
* customize how access tokens are generated
|
||||
*
|
||||
* @param [Number} ttl The requested ttl
|
||||
* @callack {Function} cb The callback function
|
||||
* @param {String|Error} err The error string or object
|
||||
* @param {AccessToken} token The generated access token object
|
||||
*/
|
||||
User.prototype.createAccessToken = function(ttl, cb) {
|
||||
var userModel = this.constructor;
|
||||
ttl = Math.min(ttl || userModel.settings.ttl, userModel.settings.maxTTL);
|
||||
this.accessTokens.create({
|
||||
ttl: ttl
|
||||
}, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Login a user by with the given `credentials`.
|
||||
*
|
||||
|
@ -144,6 +159,7 @@ var User = module.exports = Model.extend('User', properties, options);
|
|||
*/
|
||||
|
||||
User.login = function (credentials, include, fn) {
|
||||
var self = this;
|
||||
if (typeof include === 'function') {
|
||||
fn = include;
|
||||
include = undefined;
|
||||
|
@ -162,7 +178,7 @@ User.login = function (credentials, include, fn) {
|
|||
return fn(err);
|
||||
}
|
||||
|
||||
this.findOne({where: query}, function(err, user) {
|
||||
self.findOne({where: query}, function(err, user) {
|
||||
var defaultError = new Error('login failed');
|
||||
defaultError.statusCode = 401;
|
||||
|
||||
|
@ -175,9 +191,7 @@ User.login = function (credentials, include, fn) {
|
|||
debug('An error is reported from User.hasPassword: %j', err);
|
||||
fn(defaultError);
|
||||
} else if(isMatch) {
|
||||
user.accessTokens.create({
|
||||
ttl: Math.min(credentials.ttl || User.settings.ttl, User.settings.maxTTL)
|
||||
}, function(err, token) {
|
||||
user.createAccessToken(credentials.ttl, function(err, token) {
|
||||
if (err) return fn(err);
|
||||
if (include === 'user') {
|
||||
// NOTE(bajtos) We can't set token.user here:
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"mobile",
|
||||
"mBaaS"
|
||||
],
|
||||
"version": "1.8.6",
|
||||
"version": "1.8.7",
|
||||
"scripts": {
|
||||
"test": "mocha -R spec"
|
||||
},
|
||||
|
@ -35,8 +35,6 @@
|
|||
"express": "~3.5.0",
|
||||
"strong-remoting": "~1.5.0",
|
||||
"inflection": "~1.3.5",
|
||||
"passport": "~0.2.0",
|
||||
"passport-local": "~1.0.0",
|
||||
"nodemailer": "~0.6.5",
|
||||
"ejs": "~1.0.0",
|
||||
"bcryptjs": "~0.7.12",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
var User;
|
||||
var AccessToken = loopback.AccessToken;
|
||||
var passport = require('passport');
|
||||
var MailConnector = require('../lib/connectors/mail');
|
||||
|
||||
var userMemory = loopback.createDataSource({
|
||||
|
@ -9,6 +8,7 @@ var userMemory = loopback.createDataSource({
|
|||
|
||||
describe('User', function(){
|
||||
var validCredentials = {email: 'foo@bar.com', password: 'bar'};
|
||||
var validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600};
|
||||
var invalidCredentials = {email: 'foo1@bar.com', password: 'bar1'};
|
||||
var incompleteCredentials = {password: 'bar1'};
|
||||
|
||||
|
@ -117,6 +117,44 @@ describe('User', function(){
|
|||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Login a user by providing credentials with TTL', function(done) {
|
||||
User.login(validCredentialsWithTTL, function (err, accessToken) {
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.ttl, validCredentialsWithTTL.ttl);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Login a user using a custom createAccessToken', function(done) {
|
||||
var createToken = User.prototype.createAccessToken; // Save the original method
|
||||
// Override createAccessToken
|
||||
User.prototype.createAccessToken = function(ttl, cb) {
|
||||
// Reduce the ttl by half for testing purpose
|
||||
this.accessTokens.create({ttl: ttl /2 }, cb);
|
||||
};
|
||||
User.login(validCredentialsWithTTL, function (err, accessToken) {
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.ttl, 1800);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
|
||||
User.findById(accessToken.userId, function(err, user) {
|
||||
user.createAccessToken(120, function (err, accessToken) {
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.ttl, 60);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
// Restore create access token
|
||||
User.prototype.createAccessToken = createToken;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Login a user over REST by providing credentials', function(done) {
|
||||
request(app)
|
||||
|
|
Loading…
Reference in New Issue