diff --git a/README.md b/README.md
index ac9e7582..33ecd580 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ v0.9.0
- [DataSource](#data-source)
- [Connectors](#connectors)
- [Loopback Types](#loopback-types)
- - [GeoPoint](#geo-point)
+ - [GeoPoint](#geo-point)
- [REST Router](#rest-router)
- [Bundled Models](#bundled-models)
- [User](#user-model)
diff --git a/example/mobile-models/app.js b/example/mobile-models/app.js
new file mode 100644
index 00000000..f671f360
--- /dev/null
+++ b/example/mobile-models/app.js
@@ -0,0 +1,45 @@
+var models = require('../../lib/models');
+
+var loopback = require('../../');
+var app = loopback();
+
+app.use(loopback.rest());
+
+var dataSource = loopback.createDataSource('db', {connector: loopback.Memory});
+
+var Application = models.Application(dataSource);
+
+app.model(Application);
+
+
+var data = {pushSettings: [
+ { "platform": "apns",
+ "apns": {
+ "pushOptions": {
+ "gateway": "gateway.sandbox.push.apple.com",
+ "cert": "credentials/apns_cert_dev.pem",
+ "key": "credentials/apns_key_dev.pem"
+ },
+
+ "feedbackOptions": {
+ "gateway": "feedback.sandbox.push.apple.com",
+ "cert": "credentials/apns_cert_dev.pem",
+ "key": "credentials/apns_key_dev.pem",
+ "batchFeedback": true,
+ "interval": 300
+ }
+ }}
+]}
+
+Application.create(data, function(err, data) {
+ console.log('Created: ', data.toObject());
+});
+
+
+Application.register('MyApp', 'My first mobile application', 'rfeng', function (err, result) {
+ console.log(result.toObject());
+
+ result.resetKeys(function (err, result) {
+ console.log(result.toObject());
+ });
+});
diff --git a/lib/application.js b/lib/application.js
index 5e573ad2..4ed7b9f9 100644
--- a/lib/application.js
+++ b/lib/application.js
@@ -25,6 +25,21 @@ app.remotes = function () {
}
}
+/**
+ * Expose an object or Class remotely.
+ *
+ * @param {String} name The remote namespace (eg. url base)
+ * @param {Object|Function} obj The object to remote
+ */
+
+app.remote = function (name, obj) {
+ // add the object to the remote exports
+ this.remotes().exports[name] = obj;
+
+ // clear the handlers cache
+ this._handlers = {};
+}
+
/**
* Remove a route by reference.
*/
@@ -44,6 +59,7 @@ app.disuse = function (route) {
*/
app._models = [];
+app._services = [];
/**
* Expose a model.
@@ -52,6 +68,8 @@ app._models = [];
*/
app.model = function (Model) {
+ var remotes = this.remotes();
+
this._models.push(Model);
Model.shared = true;
Model.app = this;
@@ -67,22 +85,28 @@ app.models = function () {
}
/**
- * Get all remote objects.
+ * Expose a service.
+ *
+ * @param {String} name
+ * @param {Service} service
*/
-app.remoteObjects = function () {
- var result = {};
- var models = this.models();
+app.service = function (name, service) {
+ this._services.push(service);
+ service.shared = true;
- // add in models
- models.forEach(function (ModelCtor) {
- // only add shared models
- if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') {
- result[ModelCtor.pluralModelName] = ModelCtor;
- }
- });
-
- return result;
+ service.app = this;
+
+ // add to the remote exports
+ this.remote(name, service);
+}
+
+/**
+ * Get all exposed services.
+ */
+
+app.services = function () {
+ return this._services;
}
/**
@@ -91,4 +115,29 @@ app.remoteObjects = function () {
app.remotes = function () {
return this._remotes || (this._remotes = RemoteObjects.create());
-}
\ No newline at end of file
+}
+
+/**
+ * Get a remotes handler.
+ */
+
+app.handler = function (type) {
+ var handler = this._handlers[type];
+
+ if(!handler) {
+ // get the sl remoting object
+ var remotes = this.remotes();
+
+ // create and save the handler
+ handler = this._handlers[type] = remotes.handler(type);
+ }
+
+ return handler;
+}
+
+/*!
+ * Handlers
+ */
+
+app._handlers = {};
+
diff --git a/lib/middleware/rest.js b/lib/middleware/rest.js
index 468d0a44..5f1d9144 100644
--- a/lib/middleware/rest.js
+++ b/lib/middleware/rest.js
@@ -17,21 +17,12 @@ module.exports = rest;
function rest() {
return function (req, res, next) {
- var app = req.app;
- var remotes = app.remotes();
-
- // get all remote objects
- var objs = app.remoteObjects();
-
- // export remote objects
- remotes.exports = objs;
-
- var handler = remotes.handler('rest');
+ var handler = req.app.handler('rest');
if(req.url === '/routes') {
res.send(handler.adapter.allRoutes());
} else if(req.url === '/models') {
- return res.send(remotes.toJSON());
+ return res.send(req.app.remotes().toJSON());
} else {
handler(req, res, next);
}
diff --git a/lib/models/acl.js b/lib/models/acl.js
index 14a6cf8c..42c80a04 100644
--- a/lib/models/acl.js
+++ b/lib/models/acl.js
@@ -1,17 +1,25 @@
-// Schema ACL options
+/**
+Schema ACL options
+Object level permissions, for example, an album owned by a user
-// Object level permissions
+Factors to be authorized against:
-// open: no protection
-// none: always rejected
-// owner: only the owner
-// loggedIn: any logged in user
-// roles: logged in users with the roles
-// related: owner of the related objects
-
-// Class level permissions
+* model name: Album
+* model instance properties: userId of the album, friends, shared
+* methods
+* app and/or user ids/roles
+ ** loggedIn
+ ** roles
+ ** userId
+ ** appId
+ ** none
+ ** everyone
+ ** relations: owner/friend/granted
+Class level permissions, for example, Album
+ * model name: Album
+ * methods
// blog posts
allow: ['owner', 'admin'] to: '*' // allow owner's of posts and admins to do anything
allow: '*' to: ['find', 'read'] // allow everyone to read and find
@@ -27,5 +35,4 @@ allow: 'owner' to: ['*.destroy', '*.save']
// scopes
// URL level permissions
-
-
+*/
diff --git a/lib/models/application.js b/lib/models/application.js
index 173d2b97..e66e29da 100644
--- a/lib/models/application.js
+++ b/lib/models/application.js
@@ -1,17 +1,54 @@
-// Application model
+// Authentication schemes
+var AuthenticationSchemeSchema = {
+ scheme: String, // local, facebook, google, twitter, linkedin, github
+ credential: Object // Scheme-specific credentials
+}
+
+var APNSSettingSchema = {
+ pushOptions: {type: {
+ gateway: String,
+ cert: String,
+ key: String
+ }},
+
+ feedbackOptions: {type: {
+ gateway: String,
+ cert: String,
+ key: String,
+ batchFeedback: Boolean,
+ interval: Number
+ }}
+};
+
+// Push notification settings
+var PushNotificationSettingSchema = {
+ platform: {type: String, required: true}, // apns, gcm, mpns
+ // configuration: {type: Object} // platform-specific configurations
+ apns: APNSSettingSchema
+}
+
+/**
+ * Data model for Application
+ */
var ApplicationSchema = {
// Basic information
- id: {type: String, required: true},
- name: {type: String, required: true},
- description: String, // description
- icon: String, // The icon url
- public: Boolean,
- permissions: [String],
+ id: {type: String, required: true}, // The id
+ name: {type: String, required: true}, // The name
+ description: String, // The description
+ icon: String, // The icon image url
- userId: String,
+ owner: String, // The user id of the developer who registers the application
+ collaborators: [String], // A list of users ids who have permissions to work on this app
- status: String,
+ // EMail
+ email: String, // e-mail address
+ emailVerified: Boolean, // Is the e-mail verified
+
+ // oAuth 2.0 settings
+ url: String, // The application url
+ callbackUrls: [String], // oAuth 2.0 code/token callback url
+ permissions: [String], // A list of permissions required by the application
// Keys
clientKey: String,
@@ -21,21 +58,80 @@ var ApplicationSchema = {
masterKey: String,
// Push notification
- pushPlatforms: [String],
- pushCredentials: [],
+ pushSettings: [PushNotificationSettingSchema],
- // Authentication
- authenticationEnabled: Boolean,
- anonymousAllowed: Boolean,
- schemes: [String], // Basic, facebook, github, google
- attachedCredentials: [],
+ // User Authentication
+ authenticationEnabled: {type: Boolean, default: true},
+ anonymousAllowed: {type: Boolean, default: true},
+ authenticationSchemes: [AuthenticationSchemeSchema],
- // email
- email: String, // e-mail address
- emailVerified: Boolean, // Is the e-mail verified
+ status: {type: String, default: 'sandbox'}, // Status of the application, production/sandbox/disabled
- collaborators: [String], // A list of users ids who have permissions to work on this app
-
- created: Date,
- lastUpdated: Date
+ // Timestamps
+ created: {type: Date, default: Date},
+ modified: {type: Date, default: Date}
};
+
+
+/**
+ * Application management functions
+ */
+
+var crypto = require('crypto');
+
+function generateKey(hmacKey, algorithm) {
+ hmacKey = hmacKey || 'loopback';
+ algorithm = algorithm || 'sha256';
+ var hmac = crypto.createHmac(algorithm, hmacKey);
+ var buf = crypto.randomBytes(64);
+ hmac.update(buf);
+ return hmac.digest('base64');
+}
+
+module.exports = function (dataSource) {
+ dataSource = dataSource || new require('loopback-data').ModelBuilder();
+
+ // var AuthenticationScheme = dataSource.define('AuthenticationScheme', AuthenticationSchemeSchema);
+ // ApplicationSchema.authenticationSchemes = [AuthenticationScheme];
+
+ // var PushNotificationSetting = dataSource.define('PushNotificationSetting', PushNotificationSettingSchema);
+ // ApplicationSchema.pushSettings = [PushNotificationSetting];
+
+ var Application = dataSource.define('Application', ApplicationSchema);
+
+ // Application.hasMany(AuthenticationScheme, {as: 'authenticationSchemes', foreignKey: 'appId'});
+ // Application.hasMany(PushNotificationSetting, {as: 'pushNotificationSettings', foreignKey: 'appId'});
+
+ Application.afterInitialize = function () {
+ var app = this;
+ // use data argument to update object
+ app.created = app.modified = new Date();
+ app.id = generateKey('id', 'sha1');
+ app.clientKey = generateKey('client');
+ app.javaScriptKey = generateKey('javaScript');
+ app.restApiKey = generateKey('restApi');
+ app.windowsKey = generateKey('windows');
+ app.masterKey = generateKey('master');
+ };
+
+ // Register a new application
+ Application.register = function (name, description, owner, cb) {
+ Application.create({name: name, description: description, owner: owner}, cb);
+ }
+
+ Application.prototype.resetKeys = function(cb) {
+ this.clientKey = generateKey('client');
+ this.javaScriptKey = generateKey('javaScript');
+ this.restApiKey = generateKey('restApi');
+ this.windowsKey = generateKey('windows');
+ this.masterKey = generateKey('master');
+ this.save(cb);
+ }
+
+ return Application;
+}
+
+
+
+
+
diff --git a/lib/models/index.js b/lib/models/index.js
new file mode 100644
index 00000000..35f6a412
--- /dev/null
+++ b/lib/models/index.js
@@ -0,0 +1,6 @@
+exports.Application = require('./application');
+exports.ACL = require('./acl');
+exports.Role = require('./role');
+exports.Installation = require('./installation');
+
+
diff --git a/lib/models/installation.js b/lib/models/installation.js
index 37e8bd0d..d54b3246 100644
--- a/lib/models/installation.js
+++ b/lib/models/installation.js
@@ -1,4 +1,26 @@
-// Device registration
+// See Device registration
var InstallationSchema = {
+ id: {
+ type: String,
+ required: true,
+ id: 1
+ },
+ appId: String, // Application id
+ appVersion: String, // Application version
+ userId: String, // User id
+ deviceToken: String,
+ deviceType: String,
+ subscriptions: [String],
+ status: {type: String, default: 'active'}, // Status of the application, production/sandbox/disabled
+
+ // Timestamps
+ created: {type: Date, default: Date},
+ modified: {type: Date, default: Date}
};
+
+module.exports = function(dataSource) {
+ dataSource = dataSource || new require('loopback-data').ModelBuilder();
+ var Installation = dataSource.define('Installation', InstallationSchema);
+ return Installation;
+}
diff --git a/lib/models/role.js b/lib/models/role.js
index a9546777..810897e2 100644
--- a/lib/models/role.js
+++ b/lib/models/role.js
@@ -1,11 +1,18 @@
// Role model
var RoleSchema = {
- id: {type: String, required: true},
- name: {type: String, required: true},
- roles: [String],
- users: [String],
- acl: [],
+ id: {type: String, required: true}, // Id
+ name: {type: String, required: true}, // The name of a role
+ description: String, // Description
+ roles: [String], // A role can be an aggregate of other roles
+ users: [String], // A role contains a list of user ids
- created: Date,
- lastUpdated: Date
+ // Timestamps
+ created: {type: Date, default: Date},
+ modified: {type: Date, default: Date}
+}
+
+module.exports = function(dataSource) {
+ dataSource = dataSource || new require('loopback-data').ModelBuilder();
+ var Role = dataSource.define('Role', RoleSchema);
+ return Role;
}
\ No newline at end of file
diff --git a/lib/models/user.js b/lib/models/user.js
index 2865668d..93fdb07b 100644
--- a/lib/models/user.js
+++ b/lib/models/user.js
@@ -22,6 +22,7 @@ var properties = {
email: String,
emailVerified: Boolean,
verificationToken: String,
+
credentials: [
'UserCredential' // User credentials, private or public, such as private/public keys, Kerberos tickets, oAuth tokens, facebook, google, github ids
],
@@ -328,4 +329,4 @@ User.prototype.logout = function (fn) {
* Setup the base user.
*/
-User.setup();
\ No newline at end of file
+User.setup();
diff --git a/test/README.md b/test/README.md
index 85df4d75..15ca0530 100644
--- a/test/README.md
+++ b/test/README.md
@@ -2,6 +2,12 @@
- [app](#app)
- [app.model(Model)](#app-appmodelmodel)
- [app.models()](#app-appmodels)
+<<<<<<< HEAD
+=======
+ - [loopback](#loopback)
+ - [loopback.createDataSource(options)](#loopback-loopbackcreatedatasourceoptions)
+ - [loopback.remoteMethod(Model, fn, [options]);](#loopback-loopbackremotemethodmodel-fn-options)
+>>>>>>> master
- [DataSource](#datasource)
- [dataSource.createModel(name, properties, settings)](#datasource-datasourcecreatemodelname-properties-settings)
- [dataSource.operations()](#datasource-datasourceoperations)
@@ -76,6 +82,48 @@ assert.equal(models.length, 1);
assert.equal(models[0].modelName, 'color');
```
+<<<<<<< HEAD
+=======
+
+# loopback
+
+## loopback.createDataSource(options)
+Create a data source with a connector..
+
+```js
+var dataSource = loopback.createDataSource({
+ connector: loopback.Memory
+});
+assert(dataSource.connector());
+```
+
+
+## loopback.remoteMethod(Model, fn, [options]);
+Setup a remote method..
+
+```js
+var Product = loopback.createModel('product', {price: Number});
+
+Product.stats = function(fn) {
+ // ...
+}
+
+loopback.remoteMethod(
+ Product.stats,
+ {
+ returns: {arg: 'stats', type: 'array'},
+ http: {path: '/info', verb: 'get'}
+ }
+);
+
+assert.equal(Product.stats.returns.arg, 'stats');
+assert.equal(Product.stats.returns.type, 'array');
+assert.equal(Product.stats.http.path, '/info');
+assert.equal(Product.stats.http.verb, 'get');
+assert.equal(Product.stats.shared, true);
+```
+
+>>>>>>> master
# DataSource
diff --git a/test/loopback.test.js b/test/loopback.test.js
index 2bedb14b..bedb0e1a 100644
--- a/test/loopback.test.js
+++ b/test/loopback.test.js
@@ -1,6 +1,6 @@
describe('loopback', function() {
describe('loopback.createDataSource(options)', function(){
- it('Create a data source with a connector', function() {
+ it('Create a data source with a connector.', function() {
var dataSource = loopback.createDataSource({
connector: loopback.Memory
});
@@ -9,7 +9,7 @@ describe('loopback', function() {
});
describe('loopback.remoteMethod(Model, fn, [options]);', function() {
- it("Setup a remote method", function() {
+ it("Setup a remote method.", function() {
var Product = loopback.createModel('product', {price: Number});
Product.stats = function(fn) {
@@ -31,18 +31,4 @@ describe('loopback', function() {
assert.equal(Product.stats.shared, true);
});
});
-
- describe('loopback.memory([name])', function(){
- it('Get an in-memory data source. Use one if it already exists', function() {
- var memory = loopback.memory();
- assertValidDataSource(memory);
- var m1 = loopback.memory();
- var m2 = loopback.memory('m2');
- var alsoM2 = loopback.memory('m2');
-
- assert(m1 === memory);
- assert(m1 !== m2);
- assert(alsoM2 === m2);
- });
- });
});
\ No newline at end of file