/*! * Module Dependencies. */ var loopback = require('../loopback'); var compat = require('../compat'); var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder; var modeler = new ModelBuilder(); var assert = require('assert'); /** * The built in loopback.Model. * * @class * @param {Object} data */ var Model = module.exports = modeler.define('Model'); Model.shared = true; /*! * Called when a model is extended. */ Model.setup = function () { var ModelCtor = this; ModelCtor.sharedCtor = function (data, id, fn) { if(typeof data === 'function') { fn = data; data = null; id = null; } else if (typeof id === 'function') { fn = id; if(typeof data !== 'object') { id = data; data = null; } else { id = null; } } if(id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if(data) { fn(null, new ModelCtor(data)); } else if(id) { ModelCtor.findById(id, function (err, model) { if(err) { fn(err); } else if(model) { fn(null, model); } else { err = new Error('could not find a model with id ' + id); err.statusCode = 404; fn(err); } }); } else { fn(new Error('must specify an id or data')); } } // before remote hook ModelCtor.beforeRemote = function (name, fn) { var self = this; if(this.app) { var remotes = this.app.remotes(); var className = compat.getClassNameForRemoting(self); remotes.before(className + '.' + name, function (ctx, next) { fn(ctx, ctx.result, next); }); } else { var args = arguments; this.once('attached', function () { self.beforeRemote.apply(self, args); }); } }; // after remote hook ModelCtor.afterRemote = function (name, fn) { var self = this; if(this.app) { var remotes = this.app.remotes(); var className = compat.getClassNameForRemoting(self); remotes.after(className + '.' + name, function (ctx, next) { fn(ctx, ctx.result, next); }); } else { var args = arguments; this.once('attached', function () { self.afterRemote.apply(self, args); }); } }; // Map the prototype method to /:id with data in the body var idDesc = ModelCtor.modelName + ' id'; ModelCtor.sharedCtor.accepts = [ {arg: 'id', type: 'any', http: {source: 'path'}, description: idDesc} // {arg: 'instance', type: 'object', http: {source: 'body'}} ]; ModelCtor.sharedCtor.http = [ {path: '/:id'} ]; ModelCtor.sharedCtor.returns = {root: true}; return ModelCtor; }; /*! * Get the reference to ACL in a lazy fashion to avoid race condition in require */ var _aclModel = null; Model._ACL = function getACL(ACL) { if(ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if(_aclModel) { return _aclModel; } var aclModel = require('./acl').ACL; _aclModel = loopback.getModelByType(aclModel); return _aclModel; }; /** * Check if the given access token can invoke the method * * @param {AccessToken} token The access token * @param {*} modelId The model ID. * @param {SharedMethod} sharedMethod The method in question * @callback {Function} callback The callback function * @param {String|Error} err The error object * @param {Boolean} allowed True if the request is allowed; false otherwise. */ Model.checkAccess = function(token, modelId, sharedMethod, callback) { var ANONYMOUS = require('./access-token').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod) }, function(err, accessRequest) { if(err) return callback(err); callback(null, accessRequest.isAllowed()); }); }; /*! * Determine the access type for the given `RemoteMethod`. * * @api private * @param {RemoteMethod} method */ Model._getAccessTypeForMethod = function(method) { if(typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); switch(method.name) { case'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; break; default: return ACL.EXECUTE; break; } } /** * Get the `Application` the Model is attached to. * * @callback {Function} callback * @param {Error} err * @param {Application} app * @end */ Model.getApp = function(callback) { var Model = this; if(this.app) { callback(null, this.app); } else { Model.once('attached', function() { assert(Model.app); callback(null, Model.app); }); } } /** * Get the Model's `RemoteObjects`. * * @callback {Function} callback * @param {Error} err * @param {RemoteObjects} remoteObjects * @end */ Model.remotes = function(callback) { this.getApp(function(err, app) { callback(null, app.remotes()); }); } /*! * Create a proxy function for invoking remote methods. * * @param {SharedMethod} sharedMethod */ Model.createProxyMethod = function createProxyFunction(remoteMethod) { var Model = this; var scope = remoteMethod.isStatic ? Model : Model.prototype; var original = scope[remoteMethod.name]; var fn = scope[remoteMethod.name] = function proxy() { var args = Array.prototype.slice.call(arguments); var lastArgIsFunc = typeof args[args.length - 1] === 'function'; var callback; if(lastArgIsFunc) { callback = args.pop(); } Model.remotes(function(err, remotes) { remotes.invoke(remoteMethod.stringName, args, callback); }); } for(var key in original) { fn[key] = original[key]; } fn._delegate = true; } // setup the initial model Model.setup();