Initial commit of Usergrid Node.js SDK into its own repo
Project: http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/repo Commit: http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/commit/7b92555b Tree: http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/tree/7b92555b Diff: http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/diff/7b92555b Branch: refs/heads/master Commit: 7b92555b58c29a30e1e2f1dc638b50b6407539a7 Parents: Author: Michael Russo <[email protected]> Authored: Fri Sep 2 09:16:01 2016 -0700 Committer: Michael Russo <[email protected]> Committed: Fri Sep 2 09:16:01 2016 -0700 ---------------------------------------------------------------------- .gitignore | 2 + LICENSE | 20 + changelog.md | 43 + lib/usergrid.js | 2584 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 43 + readme.md | 720 +++++++++++++ test.js | 1077 ++++++++++++++++++++ test/client.js | 183 ++++ test/collection.js | 259 +++++ test/entity.js | 59 ++ 10 files changed, 4990 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/blob/7b92555b/.gitignore ---------------------------------------------------------------------- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c564040 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +node_modules/ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/blob/7b92555b/LICENSE ---------------------------------------------------------------------- diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3a12e26 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Apigee Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/blob/7b92555b/changelog.md ---------------------------------------------------------------------- diff --git a/changelog.md b/changelog.md new file mode 100755 index 0000000..c44ab04 --- /dev/null +++ b/changelog.md @@ -0,0 +1,43 @@ +##Change log + + +###0.10.7 +- Fixed issue where token was appeneded even when no token was present +- Updated tests + + +###0.10.5 + +- Added new class and methods for Groups +- Added serialization / restore methods for entities and collections +- Various bug fixes +- Added function for getting user feed +- Added function for creating user activities with an associated user entity +- Added public facing helper method for signing up users + +###0.10.4 + +- Added new functions for creating, getting, and deleting connections +- Added test cases for said functions +- Fixed change password error +- Added getEntity method to get existing entity from server + +###0.10.3 + +- Added set / get token methods to accomodate session storage +- Added createUserActivity method to make creating activities for logged in user easier + +###0.10.2 + +- Removed local caching of user object in client + +###0.10.1 + +- Minor refactor of the SDK to bring congruity with the App services Javascript SDK + +###0.10.0 +- Complete refactor of the entire module + +- Added Mocha based test suite + +- Added full coverage of all sample code in the readme file http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/blob/7b92555b/lib/usergrid.js ---------------------------------------------------------------------- diff --git a/lib/usergrid.js b/lib/usergrid.js new file mode 100755 index 0000000..b29dd3f --- /dev/null +++ b/lib/usergrid.js @@ -0,0 +1,2584 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +var inflection = require('inflection'); +var request = require('request'); +var Usergrid = {}; +Usergrid.USERGRID_SDK_VERSION = '0.10.07'; + +//authentication type constants +var AUTH_CLIENT_ID = 'CLIENT_ID'; +var AUTH_APP_USER = 'APP_USER'; +var AUTH_NONE = 'NONE'; + + Usergrid.Client = function(options) { + //usergrid enpoint + this.URI = options.URI || 'https://api.usergrid.com'; + + //Find your Orgname and Appname in the Admin portal (http://apigee.com/usergrid) + if (options.orgName) { + this.set('orgName', options.orgName); + } + if (options.appName) { + this.set('appName', options.appName); + } + + //authentication data + this.authType = options.authType || AUTH_NONE; + this.clientId = options.clientId; + this.clientSecret = options.clientSecret; + this.token = options.token || null; + + //other options + this.buildCurl = options.buildCurl || false; + this.logging = options.logging || false; + + //timeout and callbacks + this._callTimeout = options.callTimeout || 30000; //default to 30 seconds + this._callTimeoutCallback = options.callTimeoutCallback || null; + this.logoutCallback = options.logoutCallback || null; + }; + + /* + * Main function for making requests to the API. Can be called directly. + * + * options object: + * `method` - http method (GET, POST, PUT, or DELETE), defaults to GET + * `qs` - object containing querystring values to be appended to the uri + * `body` - object containing entity body for POST and PUT requests + * `endpoint` - API endpoint, for example 'users/fred' + * `mQuery` - boolean, set to true if running management query, defaults to false + * + * @method request + * @public + * @params {object} options + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.request = function (options, callback) { + var self = this; + var method = options.method || 'GET'; + var endpoint = options.endpoint; + var body = options.body || {}; + var qs = options.qs || {}; + var mQuery = options.mQuery || false; //is this a query to the management endpoint? + var orgName = this.get('orgName'); + var appName = this.get('appName'); + if(!mQuery && !orgName && !appName){ + if (typeof(this.logoutCallback) === 'function') { + return this.logoutCallback(true, 'no_org_or_app_name_specified'); + } + } + var uri; + if (mQuery) { + uri = this.URI + '/' + endpoint; + } else { + uri = this.URI + '/' + orgName + '/' + appName + '/' + endpoint; + } + + if (this.authType === AUTH_CLIENT_ID) { + qs['client_id'] = this.clientId; + qs['client_secret'] = this.clientSecret; + } else if (this.authType === AUTH_APP_USER && self.getToken()) { + qs['access_token'] = self.getToken(); + } + + if (this.logging) { + console.log('calling: ' + method + ' ' + uri); + } + this._start = new Date().getTime(); + var callOptions = { + method: method, + uri: uri, + json: body, + qs: qs + }; + request(callOptions, function (err, r, data) { + + r.body = r.body || {}; + data = data || {}; + + if (self.buildCurl) { + options.uri = r.request.uri.href; + self.buildCurlCall(options); + } + self._end = new Date().getTime(); + if(r.statusCode === 200) { + if (self.logging) { + console.log('success (time: ' + self.calcTimeDiff() + '): ' + method + ' ' + uri); + } + callback(err, data); + } else { + err = true; + data.statusCode = r.statusCode; + if ((r.error === 'auth_expired_session_token') || + (r.error === 'auth_missing_credentials') || + (r.error == 'auth_unverified_oath') || + (r.error === 'expired_token') || + (r.error === 'unauthorized') || + (r.error === 'auth_invalid')) { + //this error type means the user is not authorized. If a logout function is defined, call it + var error = r.body.error; + var errorDesc = r.body.error_description; + if (self.logging) { + console.log('Error (' + r.statusCode + ')(' + error + '): ' + errorDesc); + } + //if the user has specified a logout callback: + if (typeof(self.logoutCallback) === 'function') { + self.logoutCallback(err, data); + } else if (typeof(callback) === 'function') { + callback(err, data); + } + } else { + var error = r.body.error; + var errorDesc = r.body.error_description; + if (self.logging) { + console.log('Error (' + r.statusCode + ')(' + error + '): ' + errorDesc); + } + if (typeof(callback) === 'function') { + callback(err, data); + } + } + } + }); + }; + /* + * function for building asset urls + * + * @method buildAssetURL + * @public + * @params {string} uuid + * @return {string} assetURL + */ + Usergrid.Client.prototype.buildAssetURL = function(uuid) { + var self = this; + var qs = {}; + var assetURL = this.URI + '/' + this.orgName + '/' + this.appName + '/assets/' + uuid + '/data'; + + if (self.getToken()) { + qs['access_token'] = self.getToken(); + } + + //append params to the path + var encoded_params = encodeParams(qs); + if (encoded_params) { + assetURL += "?" + encoded_params; + } + + return assetURL; + } + + /* + * Main function for creating new groups. Call this directly. + * + * @method createGroup + * @public + * @params {string} path + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.createGroup = function(options, callback) { + var getOnExist = options.getOnExist || false; + delete options.getOnExist; + + var options = { + path: options.path, + client: this, + data:options + } + + var group = new Usergrid.Group(options); + group.fetch(function(err, data){ + var okToSave = (err && 'service_resource_not_found' === data.error || 'no_name_specified' === data.error || 'null_pointer' === data.error) || (!err && getOnExist); + if (okToSave) { + group.save(function(err, data){ + if (typeof(callback) === 'function') { + callback(err, group); + } + }); + } else { + if(typeof(callback) === 'function') { + callback(err, group); + } + } + }); + } + + /* + * Main function for creating new entities - should be called directly. + * + * options object: options {data:{'type':'collection_type', 'key':'value'}, uuid:uuid}} + * + * @method createEntity + * @public + * @params {object} options + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.createEntity = function (options, callback) { + // todo: replace the check for new / save on not found code with simple save + // when users PUT on no user fix is in place. + /* + var options = { + client:this, + data:options + } + var entity = new Usergrid.Entity(options); + entity.save(function(err, data) { + if (typeof(callback) === 'function') { + callback(err, entity); + } + }); + */ + var getOnExist = options.getOnExist || false; //if true, will return entity if one already exists + delete options.getOnExist; + + var options = { + client:this, + data:options + } + var entity = new Usergrid.Entity(options); + entity.fetch(function(err, data) { + //if the fetch doesn't find what we are looking for, or there is no error, do a save + var okToSave = (err && 'service_resource_not_found' === data.error || 'no_name_specified' === data.error || 'null_pointer' === data.error) || (!err && getOnExist); + if(okToSave) { + entity.set(options.data); //add the data again just in case + entity.save(function(err, data) { + if (typeof(callback) === 'function') { + callback(err, entity, data); + } + }); + } else { + if (typeof(callback) === 'function') { + callback(err, entity, data); + } + } + }); + + } + + /* + * Main function for getting existing entities - should be called directly. + * + * You must supply a uuid or (username or name). Username only applies to users. + * Name applies to all custom entities + * + * options object: options {data:{'type':'collection_type', 'name':'value', 'username':'value'}, uuid:uuid}} + * + * @method createEntity + * @public + * @params {object} options + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.getEntity = function (options, callback) { + var options = { + client:this, + data:options + } + var entity = new Usergrid.Entity(options); + entity.fetch(function(err, data) { + if (typeof(callback) === 'function') { + callback(err, entity, data); + } + }); + } + + /* + * Main function for restoring an entity from serialized data. + * + * serializedObject should have come from entityObject.serialize(); + * + * @method restoreEntity + * @public + * @param {string} serializedObject + * @return {object} Entity Object + */ + Usergrid.Client.prototype.restoreEntity = function (serializedObject) { + var data = JSON.parse(serializedObject); + var options = { + client:this, + data:data + } + var entity = new Usergrid.Entity(options); + return entity; + } + + /* + * Main function for creating new collections - should be called directly. + * + * options object: options {client:client, type: type, qs:qs} + * + * @method createCollection + * @public + * @params {object} options + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.createCollection = function (options, callback) { + options.client = this; + var collection = new Usergrid.Collection(options, function(err, data) { + if (typeof(callback) === 'function') { + callback(err, collection, data); + } + }); + } + + /* + * Main function for restoring a collection from serialized data. + * + * serializedObject should have come from collectionObject.serialize(); + * + * @method restoreCollection + * @public + * @param {string} serializedObject + * @return {object} Collection Object + */ + Usergrid.Client.prototype.restoreCollection = function (serializedObject) { + var data = JSON.parse(serializedObject); + data.client = this; + var collection = new Usergrid.Collection(data); + return collection; + } + + /* + * Main function for retrieving a user's activity feed. + * + * @method getFeedForUser + * @public + * @params {string} username + * @param {function} callback + * @return {callback} callback(err, data, activities) + */ + Usergrid.Client.prototype.getFeedForUser = function(username, callback) { + var options = { + method: "GET", + endpoint: "users/"+username+"/feed" + } + + this.request(options, function(err, data){ + if(typeof(callback) === "function") { + if(err) { + callback(err); + } else { + callback(err, data, data.entities); + } + } + }); + } + + /* + * Function for creating new activities for the current user - should be called directly. + * + * //user can be any of the following: "me", a uuid, a username + * Note: the "me" alias will reference the currently logged in user (e.g. 'users/me/activties') + * + * //build a json object that looks like this: + * var options = + * { + * "actor" : { + * "displayName" :"myusername", + * "uuid" : "myuserid", + * "username" : "myusername", + * "email" : "myemail", + * "picture": "http://path/to/picture", + * "image" : { + * "duration" : 0, + * "height" : 80, + * "url" : "http://www.gravatar.com/avatar/", + * "width" : 80 + * }, + * }, + * "verb" : "post", + * "content" : "My cool message", + * "lat" : 48.856614, + * "lon" : 2.352222 + * } + * + * @method createEntity + * @public + * @params {string} user // "me", a uuid, or a username + * @params {object} options + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.createUserActivity = function (user, options, callback) { + options.type = 'users/'+user+'/activities'; + var options = { + client:this, + data:options + } + var entity = new Usergrid.Entity(options); + entity.save(function(err, data) { + if (typeof(callback) === 'function') { + callback(err, entity); + } + }); + } + + /* + * Function for creating user activities with an associated user entity. + * + * user object: + * The user object passed into this function is an instance of Usergrid.Entity. + * + * @method createUserActivityWithEntity + * @public + * @params {object} user + * @params {string} content + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.createUserActivityWithEntity = function(user, content, callback) { + var username = user.get("username"); + var options = { + actor: { + "displayName":username, + "uuid":user.get("uuid"), + "username":username, + "email":user.get("email"), + "picture":user.get("picture"), + "image": { + "duration":0, + "height":80, + "url":user.get("picture"), + "width":80 + }, + }, + "verb":"post", + "content":content }; + + this.createUserActivity(username, options, callback); + + } + + /* + * A private method to get call timing of last call + */ + Usergrid.Client.prototype.calcTimeDiff = function () { + var time = this._end - this._start; + return (time/1000).toFixed(2); + } + + /* + * A public method to store the OAuth token for later use - uses localstorage if available + * + * @method setToken + * @public + * @params {string} token + * @return none + */ + Usergrid.Client.prototype.setToken = function (token) { + this.set('token', token); + } + + /* + * A public method to get the OAuth token + * + * @method getToken + * @public + * @return {string} token + */ + Usergrid.Client.prototype.getToken = function () { + return this.get('token'); + } + + Usergrid.Client.prototype.setObject = function(key, value) { + if (value) { + value = JSON.stringify(value); + } + this.set(key, value); + } + + Usergrid.Client.prototype.set = function (key, value) { + var keyStore = 'apigee_' + key; + this[key] = value; + if(typeof(Storage)!=="undefined"){ + if (value) { + localStorage.setItem(keyStore, value); + } else { + localStorage.removeItem(keyStore); + } + } + } + + Usergrid.Client.prototype.getObject = function(key) { + return JSON.parse(this.get(key)); + } + + Usergrid.Client.prototype.get = function (key) { + var keyStore = 'apigee_' + key; + if (this[key]) { + return this[key]; + } else if(typeof(Storage)!=="undefined") { + return localStorage.getItem(keyStore); + } + return null; + } + + /* + * A public facing helper method for signing up users + * + * @method signup + * @public + * @params {string} username + * @params {string} password + * @params {string} email + * @params {string} name + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.signup = function(username, password, email, name, callback) { + var self = this; + var options = { + type:"users", + username:username, + password:password, + email:email, + name:name + }; + + this.createEntity(options, callback); + } + + /* + * + * A public method to log in an app user - stores the token for later use + * + * @method login + * @public + * @params {string} username + * @params {string} password + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.login = function (username, password, callback) { + var self = this; + var options = { + method:'POST', + endpoint:'token', + body:{ + username: username, + password: password, + grant_type: 'password' + } + }; + this.request(options, function(err, data) { + var user = {}; + if (err && self.logging) { + console.log('error trying to log user in'); + } else { + var options = { + client:self, + data:data.user + } + user = new Usergrid.Entity(options); + self.setToken(data.access_token); + } + if (typeof(callback) === 'function') { + callback(err, data, user); + } + }); + } + + + Usergrid.Client.prototype.reAuthenticateLite = function (callback) { + var self = this; + var options = { + method:'GET', + endpoint:'management/me', + mQuery:true + }; + this.request(options, function(err, response) { + if (err && self.logging) { + console.log('error trying to re-authenticate user'); + } else { + + //save the re-authed token and current email/username + self.setToken(response.access_token); + + } + if (typeof(callback) === 'function') { + callback(err); + } + }); + } + + + Usergrid.Client.prototype.reAuthenticate = function (email, callback) { + var self = this; + var options = { + method:'GET', + endpoint:'management/users/'+email, + mQuery:true + }; + this.request(options, function(err, response) { + var organizations = {}; + var applications = {}; + var user = {}; + if (err && self.logging) { + console.log('error trying to full authenticate user'); + } else { + var data = response.data; + self.setToken(data.token); + self.set('email', data.email); + + //delete next block and corresponding function when iframes are refactored + localStorage.setItem('accessToken', data.token); + localStorage.setItem('userUUID', data.uuid); + localStorage.setItem('userEmail', data.email); + //end delete block + + + var userData = { + "username" : data.username, + "email" : data.email, + "name" : data.name, + "uuid" : data.uuid + } + var options = { + client:self, + data:userData + } + user = new Usergrid.Entity(options); + + organizations = data.organizations; + var org = ''; + try { + //if we have an org stored, then use that one. Otherwise, use the first one. + var existingOrg = self.get('orgName'); + org = (organizations[existingOrg])?organizations[existingOrg]:organizations[Object.keys(organizations)[0]]; + self.set('orgName', org.name); + } catch(e) { + err = true; + if (self.logging) { console.log('error selecting org'); } + } //should always be an org + + applications = self.parseApplicationsArray(org); + self.selectFirstApp(applications); + + self.setObject('organizations', organizations); + self.setObject('applications', applications); + + } + if (typeof(callback) === 'function') { + callback(err, data, user, organizations, applications); + } + }); + } + + /* + * A public method to log in an app user with facebook - stores the token for later use + * + * @method loginFacebook + * @public + * @params {string} username + * @params {string} password + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.loginFacebook = function (facebookToken, callback) { + var self = this; + var options = { + method:'GET', + endpoint:'auth/facebook', + qs:{ + fb_access_token: facebookToken + } + }; + this.request(options, function(err, data) { + var user = {}; + if (err && self.logging) { + console.log('error trying to log user in'); + } else { + var options = { + client: self, + data: data.user + } + user = new Usergrid.Entity(options); + self.setToken(data.access_token); + } + if (typeof(callback) === 'function') { + callback(err, data, user); + } + }); + } + + /* + * A public method to get the currently logged in user entity + * + * @method getLoggedInUser + * @public + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Client.prototype.getLoggedInUser = function (callback) { + if (!this.getToken()) { + callback(true, null, null); + } else { + var self = this; + var options = { + method:'GET', + endpoint:'users/me' + }; + this.request(options, function(err, data) { + if (err) { + if (self.logging) { + console.log('error trying to log user in'); + } + if (typeof(callback) === 'function') { + callback(err, data, null); + } + } else { + var options = { + client:self, + data:data.entities[0] + } + var user = new Usergrid.Entity(options); + if (typeof(callback) === 'function') { + callback(err, data, user); + } + } + }); + } + } + + /* + * A public method to test if a user is logged in - does not guarantee that the token is still valid, + * but rather that one exists + * + * @method isLoggedIn + * @public + * @return {boolean} Returns true the user is logged in (has token and uuid), false if not + */ + Usergrid.Client.prototype.isLoggedIn = function () { + if (this.getToken()) { + return true; + } + return false; + } + + /* + * A public method to log out an app user - clears all user fields from client + * + * @method logout + * @public + * @return none + */ + Usergrid.Client.prototype.logout = function () { + this.setToken(null); + } + + /* + * A private method to build the curl call to display on the command line + * + * @method buildCurlCall + * @private + * @param {object} options + * @return {string} curl + */ + Usergrid.Client.prototype.buildCurlCall = function (options) { + var curl = 'curl'; + var method = (options.method || 'GET').toUpperCase(); + var body = options.body || {}; + var uri = options.uri; + + //curl - add the method to the command (no need to add anything for GET) + if (method === 'POST') {curl += ' -X POST'; } + else if (method === 'PUT') { curl += ' -X PUT'; } + else if (method === 'DELETE') { curl += ' -X DELETE'; } + else { curl += ' -X GET'; } + + //curl - append the path + curl += ' ' + uri; + + //curl - add the body + body = JSON.stringify(body)//only in node module + if (body !== '"{}"' && method !== 'GET' && method !== 'DELETE') { + //curl - add in the json obj + curl += " -d '" + body + "'"; + } + + //log the curl command to the console + console.log(curl); + + return curl; + } + + Usergrid.Client.prototype.getDisplayImage = function (email, picture, size) { + try { + if (picture) { + return picture; + } + var size = size || 50; + if (email.length) { + return 'https://secure.gravatar.com/avatar/' + MD5(email) + '?s=' + size + encodeURI("&d=https://apigee.com/usergrid/images/user_profile.png"); + } else { + return 'https://apigee.com/usergrid/images/user_profile.png'; + } + } catch(e) { + return 'https://apigee.com/usergrid/images/user_profile.png'; + } + } + + /* + * A class to Model a Usergrid Entity. + * Set the type and uuid of entity in the 'data' json object + * + * @constructor + * @param {object} options {client:client, data:{'type':'collection_type', uuid:'uuid', 'key':'value'}} + */ + Usergrid.Entity = function(options) { + if (options) { + this._data = options.data || {}; + this._client = options.client || {}; + } + }; + + /* + * returns a serialized version of the entity object + * + * Note: use the client.restoreEntity() function to restore + * + * @method serialize + * @return {string} data + */ + Usergrid.Entity.prototype.serialize = function () { + return JSON.stringify(this._data); + } + + /* + * gets a specific field or the entire data object. If null or no argument + * passed, will return all data, else, will return a specific field + * + * @method get + * @param {string} field + * @return {string} || {object} data + */ + Usergrid.Entity.prototype.get = function (field) { + if (field) { + return this._data[field]; + } else { + return this._data; + } + } + + /* + * adds a specific key value pair or object to the Entity's data + * is additive - will not overwrite existing values unless they + * are explicitly specified + * + * @method set + * @param {string} key || {object} + * @param {string} value + * @return none + */ + Usergrid.Entity.prototype.set = function (key, value) { + if (typeof key === 'object') { + for(var field in key) { + this._data[field] = key[field]; + } + } else if (typeof key === 'string') { + if (value === null) { + delete this._data[key]; + } else { + this._data[key] = value; + } + } else { + this._data = {}; + } + } + + /* + * Saves the entity back to the database + * + * @method save + * @public + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Entity.prototype.save = function (callback) { + var type = this.get('type'); + var method = 'POST'; + if (isUUID(this.get('uuid'))) { + method = 'PUT'; + type += '/' + this.get('uuid'); + } + + //update the entity + var self = this; + var data = {}; + var entityData = this.get(); + var password = this.get('password'); + var oldpassword = this.get('oldpassword'); + var newpassword = this.get('newpassword'); + //remove system specific properties + for (var item in entityData) { + if (item === 'metadata' || item === 'created' || item === 'modified' || + item === 'oldpassword' || item === 'newpassword' || //old and new pw not added to data + item === 'type' || item === 'activated' || item === 'uuid') { + continue; + } + data[item] = entityData[item]; + } + var options = { + method:method, + endpoint:type, + body:data + }; + //save the entity first + this._client.request(options, function (err, retdata) { + //clear out pw info if present + self.set('password', null); + self.set('oldpassword', null); + self.set('newpassword', null); + if (err && self._client.logging) { + console.log('could not save entity'); + if (typeof(callback) === 'function') { + return callback(err, retdata, self); + } + } else { + if (retdata.entities) { + if (retdata.entities.length) { + var entity = retdata.entities[0]; + self.set(entity); + var path = retdata.path; + //for connections, API returns type + while (path.substring(0, 1) === "/") { + path = path.substring(1); + } + self.set('type', path); + } + } + //if this is a user, update the password if it has been specified; + var needPasswordChange = ((self.get('type') === 'user' || self.get('type') === 'users') && oldpassword && newpassword); + if (needPasswordChange) { + //Note: we have a ticket in to change PUT calls to /users to accept the password change + // once that is done, we will remove this call and merge it all into one + var pwdata = {}; + pwdata.oldpassword = oldpassword; + pwdata.newpassword = newpassword; + var options = { + method:'PUT', + endpoint:type+'/password', + body:pwdata + } + self._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('could not update user'); + } + //remove old and new password fields so they don't end up as part of the entity object + self.set('oldpassword', null); + self.set('newpassword', null); + if (typeof(callback) === 'function') { + callback(err, data, self); + } + }); + } else if (typeof(callback) === 'function') { + callback(err, retdata, self); + } + } + }); + } + + /* + * refreshes the entity by making a GET call back to the database + * + * @method fetch + * @public + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Entity.prototype.fetch = function (callback) { + var type = this.get('type'); + var self = this; + + //Check for an entity type, then if a uuid is available, use that, otherwise, use the name + try { + if (type === undefined) { + throw 'cannot fetch entity, no entity type specified' + } else if (this.get('uuid')) { + type += '/' + this.get('uuid'); + } else if (type === 'users' && this.get('username')) { + type += '/' + this.get('username'); + } else if (this.get('name')) { + type += '/' + encodeURIComponent(this.get('name')); + } else if (typeof(callback) === 'function') { + throw 'no_name_specified'; + } + } catch (e) { + if (self._client.logging) { + console.log(e); + } + return callback(true, { + error: e + }, self); + } + var options = { + method:'GET', + endpoint:type + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('could not get entity'); + } else { + if (data.user) { + self.set(data.user); + self._json = JSON.stringify(data.user, null, 2); + } else if (data.entities) { + if (data.entities.length) { + var entity = data.entities[0]; + self.set(entity); + } + } + } + if (typeof(callback) === 'function') { + callback(err, data, self); + } + }); + } + + /* + * deletes the entity from the database - will only delete + * if the object has a valid uuid + * + * @method destroy + * @public + * @param {function} callback + * @return {callback} callback(err, data) + * + */ + Usergrid.Entity.prototype.destroy = function (callback) { + var self = this; + var type = this.get('type'); + var id = this.getEntityId(this); + if (!id) { + if (typeof(callback) === 'function') { + var error = 'Error trying to delete object - no uuid or name specified.'; + if (self._client.logging) { + console.log(error); + } + return callback(true, error); + } + } + type += '/' + this.get('uuid'); + var options = { + method:'DELETE', + endpoint:type + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('entity could not be deleted'); + } else { + self.set(null); + } + if (typeof(callback) === 'function') { + callback(err, data); + } + }); + } + + /* + * connects one entity to another + * + * @method connect + * @public + * @param {string} connection + * @param {object} entity + * @param {function} callback + * @return {callback} callback(err, data) + * + */ + Usergrid.Entity.prototype.connect = function (connection, entity, callback) { + + var self = this; + + //connectee info + var connecteeType = entity.get('type'); + var connectee = this.getEntityId(entity); + if (!connectee) { + if (typeof(callback) === 'function') { + var error = 'Error trying to connect object - no uuid specified.'; + if (self._client.logging) { + console.log(error); + } + callback(true, error); + } + return; + } + + //connector info + var connectorType = this.get('type'); + var connector = this.getEntityId(this); + if (!connector) { + if (typeof(callback) === 'function') { + var error = 'Error in connect - no uuid specified.'; + if (self._client.logging) { + console.log(error); + } + callback(true, error); + } + return; + } + + var endpoint = connectorType + '/' + connector + '/' + connection + '/' + connecteeType + '/' + connectee; + var options = { + method:'POST', + endpoint:endpoint + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('entity could not be connected'); + } + if (typeof(callback) === 'function') { + callback(err, data); + } + }); + } + + /* + * returns a unique identifier for an entity + * + * @method connect + * @public + * @param {object} entity + * @param {function} callback + * @return {callback} callback(err, data) + * + */ + Usergrid.Entity.prototype.getEntityId = function (entity) { + return entity.get('uuid') || entity.get('username') || entity.get('name') || false; + } + + /* + * gets an entities connections + * + * @method getConnections + * @public + * @param {string} connection + * @param {opts} options (actually, just options.qs for now) + * @param {function} callback + * @return {callback} callback(err, data, connections) + * + */ + Usergrid.Entity.prototype.getConnections = function (connection, opts, callback) { + + if (typeof(opts) == "function") { callback = opts; opts = undefined; } + + var self = this; + + //connector info + var connectorType = inflection.pluralize(this.get('type')); + var connector = this.getEntityId(this); + if (!connector) { + if (typeof(callback) === 'function') { + var error = 'Error in getConnections - no uuid specified.'; + if (self._client.logging) { + console.log(error); + } + callback(true, error); + } + return; + } + + var endpoint = connectorType + '/' + connector + '/' + connection + '/'; + var options = { + method:'GET', + endpoint:endpoint + }; + if (opts && opts.qs) { options.qs = opts.qs; } + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('entity connections could not be retrieved'); + } + + self[connection] = {}; + + var length = data.entities.length; + for (var i=0;i<length;i++) + { + if (data.entities[i].type === 'user'){ + self[connection][data.entities[i].username] = data.entities[i]; + } else { + self[connection][data.entities[i].name] = data.entities[i]; + } + } + + if (typeof(callback) === 'function') { + callback(err, data, data.entities); + } + }); + + } + + Usergrid.Entity.prototype.getGroups = function (callback) { + + var self = this; + + var endpoint = 'users' + '/' + this.get('uuid') + '/groups' ; + var options = { + method:'GET', + endpoint:endpoint + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('entity could not be connected'); + } + + self['groups'] = data.entities; + + if (typeof(callback) === 'function') { + callback(err, data, data.entities); + } + }); + + } + + Usergrid.Entity.prototype.getActivities = function (callback) { + + var self = this; + + var endpoint = this.get('type') + '/' + this.get('uuid') + '/activities' ; + var options = { + method:'GET', + endpoint:endpoint + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('entity could not be connected'); + } + + for(entity in data.entities) { + data.entities[entity].createdDate = (new Date(data.entities[entity].created)).toUTCString(); + } + + self['activities'] = data.entities; + + if (typeof(callback) === 'function') { + callback(err, data, data.entities); + } + }); + + } + + Usergrid.Entity.prototype.getFollowing = function (callback) { + + var self = this; + + var endpoint = 'users' + '/' + this.get('uuid') + '/following' ; + var options = { + method:'GET', + endpoint:endpoint + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('could not get user following'); + } + + for(entity in data.entities) { + data.entities[entity].createdDate = (new Date(data.entities[entity].created)).toUTCString(); + var image = self._client.getDisplayImage(data.entities[entity].email, data.entities[entity].picture); + data.entities[entity]._portal_image_icon = image; + } + + self['following'] = data.entities; + + if (typeof(callback) === 'function') { + callback(err, data, data.entities); + } + }); + + } + + + Usergrid.Entity.prototype.getFollowers = function (callback) { + + var self = this; + + var endpoint = 'users' + '/' + this.get('uuid') + '/followers' ; + var options = { + method:'GET', + endpoint:endpoint + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('could not get user followers'); + } + + for(entity in data.entities) { + data.entities[entity].createdDate = (new Date(data.entities[entity].created)).toUTCString(); + var image = self._client.getDisplayImage(data.entities[entity].email, data.entities[entity].picture); + data.entities[entity]._portal_image_icon = image; + } + + self['followers'] = data.entities; + + if (typeof(callback) === 'function') { + callback(err, data, data.entities); + } + }); + + } + + Usergrid.Entity.prototype.getRoles = function (callback) { + + var self = this; + + var endpoint = this.get('type') + '/' + this.get('uuid') + '/roles' ; + var options = { + method:'GET', + endpoint:endpoint + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('could not get user roles'); + } + + self['roles'] = data.entities; + + if (typeof(callback) === 'function') { + callback(err, data, data.entities); + } + }); + + } + + Usergrid.Client.prototype.createRole = function(roleName, permissions, callback) { + + var self = this; + var options = { + type: 'role', + name: roleName + }; + + this.createEntity(options, function(err, entity, response) { + if (err) { + callback (err, response, self); + } else { + entity.assignPermissions(permissions, function (err, data) { + if (err) { + callback (err, response, self); + } else { + callback (err, data, data.data); + } + }) + } + }); + + }; + Usergrid.Entity.prototype.assignRole = function(roleName, callback) { + + var self = this; + var type = self.get('type'); + var collection = type + 's'; + var entityID; + + if (type == 'user' && this.get('username') != null) { + entityID = self.get('username'); + } else if (type == 'group' && this.get('name') != null) { + entityID = self.get('name'); + } else if (this.get('uuid') != null) { + entityID = self.get('uuid'); + } + + if (type != 'users' && type != 'groups') { + callback ('entity must be a group or user', null, this); + } + + var endpoint = 'roles/' + roleName + '/' + collection + '/' + entityID; + var options = { + method: 'POST', + endpoint: endpoint + }; + + this._client.request(options, function(err, response) { + if (err) { + console.log('Could not assign role.'); + } + callback (err, response, self); + }); + + }; + + Usergrid.Entity.prototype.removeRole = function(roleName, callback) { + + var self = this; + var type = self.get('type'); + var collection = type + 's'; + var entityID; + + if (type == 'user' && this.get('username') != null) { + entityID = this.get('username'); + } else if (type == 'group' && this.get('name') != null) { + entityID = this.get('name'); + } else if (this.get('uuid') != null) { + entityID = this.get('uuid'); + } + + if (type != 'users' && type != 'groups') { + callback ('entity must be a group or user', null, this); + } + + var endpoint = 'roles/' + roleName + '/' + collection + '/' + entityID; + var options = { + method: 'DELETE', + endpoint: endpoint + }; + + this._client.request(options, function(err, response) { + if (err) { + console.log('Could not assign role.'); + } + callback (err, response, self); + }); + + }; + + Usergrid.Entity.prototype.assignPermissions = function(permissions, callback) { + var self = this; + var entityID; + var type = this.get("type"); + + if (type != 'user' && type != 'users' && type != 'group' && type != 'groups') { + callback( 'entity must be a group or user', null, this); + } + + if (type == 'user' && this.get("username") != null) { + entityID = this.get("username"); + } else if (type == 'group' && this.get("name") != null) { + entityID = this.get("name"); + } else if (this.get("uuid") != null) { + entityID = this.get("uuid"); + } + + var endpoint = type + "/" + entityID + "/permissions"; + var options = { + method: "POST", + endpoint: endpoint, + body: { + 'permission': permissions + } + }; + this._client.request(options, function(err, data) { + if (err && self._client.logging) { + console.log("could not assign permissions"); + } + callback (err, data, data.data); + }); + }; + + Usergrid.Entity.prototype.removePermissions = function(permissions, callback) { + var self = this; + var entityID; + var type = this.get("type"); + + if (type != 'user' && type != 'users' && type != 'group' && type != 'groups') { + callback ('entity must be a group or user', null, this); + } + + if (type == 'user' && this.get("username") != null) { + entityID = this.get("username"); + } else if (type == 'group' && this.get("name") != null) { + entityID = this.get("name"); + } else if (this.get("uuid") != null) { + entityID = this.get("uuid"); + } + + var endpoint = type + "/" + entityID + "/permissions"; + var options = { + method: "DELETE", + endpoint: endpoint, + qs: { + 'permission': permissions + } + }; + this._client.request(options, function(err, data) { + if (err && self._client.logging) { + console.log("could not remove permissions"); + } + callback (err, data, data.params.permission); + }); + }; + Usergrid.Entity.prototype.getPermissions = function (callback) { + + var self = this; + + var endpoint = this.get('type') + '/' + this.get('uuid') + '/permissions' ; + var options = { + method:'GET', + endpoint:endpoint + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('could not get user permissions'); + } + + var permissions = []; + if (data.data) { + var perms = data.data; + var count = 0; + + for (var i in perms) { + count++; + var perm = perms[i]; + var parts = perm.split(':'); + var ops_part = ""; + var path_part = parts[0]; + + if (parts.length > 1) { + ops_part = parts[0]; + path_part = parts[1]; + } + + ops_part.replace("*", "get,post,put,delete") + var ops = ops_part.split(','); + var ops_object = {} + ops_object['get'] = 'no'; + ops_object['post'] = 'no'; + ops_object['put'] = 'no'; + ops_object['delete'] = 'no'; + for (var j in ops) { + ops_object[ops[j]] = 'yes'; + } + + permissions.push( {operations : ops_object, path : path_part, perm : perm}); + } + } + + self['permissions'] = permissions; + + if (typeof(callback) === 'function') { + callback(err, data, data.entities); + } + }); + + } + + /* + * disconnects one entity from another + * + * @method disconnect + * @public + * @param {string} connection + * @param {object} entity + * @param {function} callback + * @return {callback} callback(err, data) + * + */ + Usergrid.Entity.prototype.disconnect = function (connection, entity, callback) { + + var self = this; + + //connectee info + var connecteeType = entity.get('type'); + var connectee = this.getEntityId(entity); + if (!connectee) { + if (typeof(callback) === 'function') { + var error = 'Error trying to delete object - no uuid specified.'; + if (self._client.logging) { + console.log(error); + } + callback(true, error); + } + return; + } + + //connector info + var connectorType = this.get('type'); + var connector = this.getEntityId(this); + if (!connector) { + if (typeof(callback) === 'function') { + var error = 'Error in connect - no uuid specified.'; + if (self._client.logging) { + console.log(error); + } + callback(true, error); + } + return; + } + + var endpoint = connectorType + '/' + connector + '/' + connection + '/' + connecteeType + '/' + connectee; + var options = { + method:'DELETE', + endpoint:endpoint + }; + this._client.request(options, function (err, data) { + if (err && self._client.logging) { + console.log('entity could not be disconnected'); + } + if (typeof(callback) === 'function') { + callback(err, data); + } + }); + } + +/* + * calls delete on the database w/ the passed query + * + * @method delete + * @param {opts} options containing query (include options.qs) + * @param {function} callback + * @return {callback} callback(err, data) + * + */ +Usergrid.Client.prototype.delete = function(opts, callback) { + if (typeof(opts) == "function") { callback = opts; opts = undefined; } + + if (!opts.qs.q) { opts.qs.q = '*'; } + + var options = { + method: 'DELETE', + endpoint: opts.type, + qs: opts.qs + }; + var self = this; + this.request(options, function (err, data) { + if (err && self.logging) { + console.log('entities could not be deleted'); + } + if (typeof(callback) === 'function') { + callback(err, data); + } + }); +}; + + /* + * The Collection class models Usergrid Collections. It essentially + * acts as a container for holding Entity objects, while providing + * additional funcitonality such as paging, and saving + * + * @constructor + * @param {string} options - configuration object + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Collection = function(options, callback) { + + if (options) { + this._client = options.client; + this._type = options.type; + this.qs = options.qs || {}; + + //iteration + this._list = options.list || []; + this._iterator = options.iterator || -1; //first thing we do is increment, so set to -1 + + //paging + this._previous = options.previous || []; + this._next = options.next || null; + this._cursor = options.cursor || null; + + //restore entities if available + if (options.list) { + var count = options.list.length; + for(var i=0;i<count;i++){ + //make new entity with + var entity = this._client.restoreEntity(options.list[i]); + this._list[i] = entity; + } + } + } + if (callback) { + //populate the collection + this.fetch(callback); + } + + } + + + /* + * gets the data from the collection object for serialization + * + * @method serialize + * @return {object} data + */ + Usergrid.Collection.prototype.serialize = function () { + + //pull out the state from this object and return it + var data = {} + data.type = this._type; + data.qs = this.qs; + data.iterator = this._iterator; + data.previous = this._previous; + data.next = this._next; + data.cursor = this._cursor; + + this.resetEntityPointer(); + var i=0; + data.list = []; + while(this.hasNextEntity()) { + var entity = this.getNextEntity(); + data.list[i] = entity.serialize(); + i++; + } + + data = JSON.stringify(data); + return data; + } + + Usergrid.Collection.prototype.addCollection = function (collectionName, options, callback) { + self = this; + options.client = this._client; + var collection = new Usergrid.Collection(options, function(err, data) { + if (typeof(callback) === 'function') { + + collection.resetEntityPointer(); + while(collection.hasNextEntity()) { + var user = collection.getNextEntity(); + var email = user.get('email'); + var image = self._client.getDisplayImage(user.get('email'), user.get('picture')); + user._portal_image_icon = image; + } + + self[collectionName] = collection; + callback(err, collection); + } + }); + } + + /* + * Populates the collection from the server + * + * @method fetch + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Collection.prototype.fetch = function (callback) { + var self = this; + var qs = this.qs; + + //add in the cursor if one is available + if (this._cursor) { + qs.cursor = this._cursor; + } else { + delete qs.cursor; + } + var options = { + method:'GET', + endpoint:this._type, + qs:this.qs + }; + this._client.request(options, function (err, data) { + if(err && self._client.logging) { + console.log('error getting collection'); + } else { + //save the cursor if there is one + var cursor = data.cursor || null; + self.saveCursor(cursor); + if (data.entities) { + self.resetEntityPointer(); + var count = data.entities.length; + //save entities locally + self._list = []; //clear the local list first + for (var i=0;i<count;i++) { + var uuid = data.entities[i].uuid; + if (uuid) { + var entityData = data.entities[i] || {}; + self._baseType = data.entities[i].type; //store the base type in the collection + entityData.type = self._type;//make sure entities are same type (have same path) as parent collection. + var entityOptions = { + type:self._type, + client:self._client, + uuid:uuid, + data:entityData + }; + + var ent = new Usergrid.Entity(entityOptions); + ent._json = JSON.stringify(entityData, null, 2); + var ct = self._list.length; + self._list[ct] = ent; + } + } + } + } + if (typeof(callback) === 'function') { + callback(err, data); + } + }); + } + + /* + * Adds a new Entity to the collection (saves, then adds to the local object) + * + * @method addNewEntity + * @param {object} entity + * @param {function} callback + * @return {callback} callback(err, data, entity) + */ + Usergrid.Collection.prototype.addEntity = function (options, callback) { + var self = this; + options.type = this._type; + + //create the new entity + this._client.createEntity(options, function (err, entity) { + if (!err) { + //then add the entity to the list + var count = self._list.length; + self._list[count] = entity; + } + if (typeof(callback) === 'function') { + callback(err, entity); + } + }); + } + + Usergrid.Collection.prototype.addExistingEntity = function (entity) { + //entity should already exist in the db, so just add it to the list + var count = this._list.length; + this._list[count] = entity; + } + + /* + * Removes the Entity from the collection, then destroys the object on the server + * + * @method destroyEntity + * @param {object} entity + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Collection.prototype.destroyEntity = function (entity, callback) { + var self = this; + entity.destroy(function(err, data) { + if (err) { + if (self._client.logging) { + console.log('could not destroy entity'); + } + if (typeof(callback) === 'function') { + callback(err, data); + } + } else { + //destroy was good, so repopulate the collection + self.fetch(callback); + } + }); + //remove entity from the local store + this.removeEntity(entity); + } + + + Usergrid.Collection.prototype.removeEntity = function (entity) { + var uuid = entity.get('uuid'); + for (key in this._list) { + var listItem = this._list[key]; + if (listItem.get('uuid') === uuid) { + return this._list.splice(key, 1); + } + } + return false; + } + + /* + * Looks up an Entity by UUID + * + * @method getEntityByUUID + * @param {string} UUID + * @param {function} callback + * @return {callback} callback(err, data, entity) + */ + Usergrid.Collection.prototype.getEntityByUUID = function (uuid, callback) { + + for (key in this._list) { + var listItem = this._list[key]; + if (listItem.get('uuid') === uuid) { + return listItem; + } + } + + //get the entity from the database + var options = { + data: { + type: this._type, + uuid:uuid + }, + client: this._client + } + var entity = new Usergrid.Entity(options); + entity.fetch(callback); + } + + /* + * Returns the first Entity of the Entity list - does not affect the iterator + * + * @method getFirstEntity + * @return {object} returns an entity object + */ + Usergrid.Collection.prototype.getFirstEntity = function () { + var count = this._list.length; + if (count > 0) { + return this._list[0]; + } + return null; + } + + /* + * Returns the last Entity of the Entity list - does not affect the iterator + * + * @method getLastEntity + * @return {object} returns an entity object + */ + Usergrid.Collection.prototype.getLastEntity = function () { + var count = this._list.length; + if (count > 0) { + return this._list[count-1]; + } + return null; + } + + /* + * Entity iteration -Checks to see if there is a "next" entity + * in the list. The first time this method is called on an entity + * list, or after the resetEntityPointer method is called, it will + * return true referencing the first entity in the list + * + * @method hasNextEntity + * @return {boolean} true if there is a next entity, false if not + */ + Usergrid.Collection.prototype.hasNextEntity = function () { + var next = this._iterator + 1; + var hasNextElement = (next >=0 && next < this._list.length); + if(hasNextElement) { + return true; + } + return false; + } + + /* + * Entity iteration - Gets the "next" entity in the list. The first + * time this method is called on an entity list, or after the method + * resetEntityPointer is called, it will return the, + * first entity in the list + * + * @method hasNextEntity + * @return {object} entity + */ + Usergrid.Collection.prototype.getNextEntity = function () { + this._iterator++; + var hasNextElement = (this._iterator >= 0 && this._iterator <= this._list.length); + if(hasNextElement) { + return this._list[this._iterator]; + } + return false; + } + + /* + * Entity iteration - Checks to see if there is a "previous" + * entity in the list. + * + * @method hasPrevEntity + * @return {boolean} true if there is a previous entity, false if not + */ + Usergrid.Collection.prototype.hasPrevEntity = function () { + var previous = this._iterator - 1; + var hasPreviousElement = (previous >=0 && previous < this._list.length); + if(hasPreviousElement) { + return true; + } + return false; + } + + /* + * Entity iteration - Gets the "previous" entity in the list. + * + * @method getPrevEntity + * @return {object} entity + */ + Usergrid.Collection.prototype.getPrevEntity = function () { + this._iterator--; + var hasPreviousElement = (this._iterator >= 0 && this._iterator <= this._list.length); + if(hasPreviousElement) { + return this.list[this._iterator]; + } + return false; + } + + /* + * Entity iteration - Resets the iterator back to the beginning + * of the list + * + * @method resetEntityPointer + * @return none + */ + Usergrid.Collection.prototype.resetEntityPointer = function () { + this._iterator = -1; + } + + /* + * Method to save off the cursor just returned by the last API call + * + * @public + * @method saveCursor + * @return none + */ + Usergrid.Collection.prototype.saveCursor = function(cursor) { + //if current cursor is different, grab it for next cursor + if (this._next !== cursor) { + this._next = cursor; + } + } + + /* + * Resets the paging pointer (back to original page) + * + * @public + * @method resetPaging + * @return none + */ + Usergrid.Collection.prototype.resetPaging = function() { + this._previous = []; + this._next = null; + this._cursor = null; + } + + /* + * Paging - checks to see if there is a next page od data + * + * @method hasNextPage + * @return {boolean} returns true if there is a next page of data, false otherwise + */ + Usergrid.Collection.prototype.hasNextPage = function () { + return !!this._next; + } + + /* + * Paging - advances the cursor and gets the next + * page of data from the API. Stores returned entities + * in the Entity list. + * + * @method getNextPage + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Collection.prototype.getNextPage = function (callback) { + if (this.hasNextPage()) { + //set the cursor to the next page of data + this._previous.push(this._cursor); + this._cursor = this._next; + //empty the list + this._list = []; + this.fetch(callback); + } + } + + /* + * Paging - checks to see if there is a previous page od data + * + * @method hasPreviousPage + * @return {boolean} returns true if there is a previous page of data, false otherwise + */ + Usergrid.Collection.prototype.hasPreviousPage = function () { + return (this._previous.length > 0); + } + + /* + * Paging - reverts the cursor and gets the previous + * page of data from the API. Stores returned entities + * in the Entity list. + * + * @method getPreviousPage + * @param {function} callback + * @return {callback} callback(err, data) + */ + Usergrid.Collection.prototype.getPreviousPage = function (callback) { + if (this.hasPreviousPage()) { + this._next=null; //clear out next so the comparison will find the next item + this._cursor = this._previous.pop(); + //empty the list + this._list = []; + this.fetch(callback); + } + } + + + /* + * A class to model a Usergrid group. + * Set the path in the options object. + * + * @constructor + * @param {object} options {client:client, data: {'key': 'value'}, path:'path'} + */ + Usergrid.Group = function(options, callback) { + this._path = options.path; + this._list = []; + this._client = options.client; + this._data = options.data || {}; + this._data.type = "groups"; + } + + /* + * Inherit from Usergrid.Entity. + * Note: This only accounts for data on the group object itself. + * You need to use add and remove to manipulate group membership. + */ + Usergrid.Group.prototype = new Usergrid.Entity(); + + /* + * Fetches current group data, and members. + * + * @method fetch + * @public + * @param {function} callback + * @returns {function} callback(err, data) + */ + Usergrid.Group.prototype.fetch = function(callback) { + var self = this; + var groupEndpoint = 'groups/'+this._path; + var memberEndpoint = 'groups/'+this._path+'/users'; + + var groupOptions = { + method:'GET', + endpoint:groupEndpoint + } + + var memberOptions = { + method:'GET', + endpoint:memberEndpoint + } + + this._client.request(groupOptions, function(err, data){ + if(err) { + if(self._client.logging) { + console.log('error getting group'); + } + if(typeof(callback) === 'function') { + callback(err, data); + } + } else { + if(data.entities) { + var groupData = data.entities[0]; + self._data = groupData || {}; + self._client.request(memberOptions, function(err, data) { + if(err && self._client.logging) { + console.log('error getting group users'); + } else { + if(data.entities) { + var count = data.entities.length; + self._list = []; + for (var i = 0; i < count; i++) { + var uuid = data.entities[i].uuid; + if(uuid) { + var entityData = data.entities[i] || {}; + var entityOptions = { + type: entityData.type, + client: self._client, + uuid:uuid, + data:entityData + }; + var entity = new Usergrid.Entity(entityOptions); + self._list.push(entity); + } + + } + } + } + if(typeof(callback) === 'function') { + callback(err, data, self._list); + } + }); + } + } + }); + } + + /* + * Retrieves the members of a group. + * + * @method members + * @public + * @param {function} callback + * @return {function} callback(err, data); + */ + Usergrid.Group.prototype.members = function(callback) { + if(typeof(callback) === 'function') { + callback(null, this._list); + } + } + + /* + * Adds a user to the group, and refreshes the group object. + * + * Options object: {user: user_entity} + * + * @method add + * @public + * @params {object} options + * @param {function} callback + * @return {function} callback(err, data) + */ + Usergrid.Group.prototype.add = function(options, callback) { + var self = this; + var options = { + method:"POST", + endpoint:"groups/"+this._path+"/users/"+options.user.get('username') + } + + this._client.request(options, function(error, data){ + if(error) { + if(typeof(callback) === 'function') { + callback(error, data, data.entities); + } + } else { + self.fetch(callback); + } + }); + } + + /* + * Removes a user from a group, and refreshes the group object. + * + * Options object: {user: user_entity} + * + * @method remove + * @public + * @params {object} options + * @param {function} callback + * @return {function} callback(err, data) + */ + Usergrid.Group.prototype.remove = function(options, callback) { + var self = this; + + var options = { + method:"DELETE", + endpoint:"groups/"+this._path+"/users/"+options.user.username + } + + this._client.request(options, function(error, data){ + if(error) { + if(typeof(callback) === 'function') { + callback(error, data); + } + } else { + self.fetch(callback); + } + }); + } + + /* + * Gets feed for a group. + * + * @public + * @method feed + * @param {function} callback + * @returns {callback} callback(err, data, activities) + */ + Usergrid.Group.prototype.feed = function(callback) { + var self = this; + + var endpoint = "groups/"+this._path+"/feed"; + + var options = { + method:"GET", + endpoint:endpoint + } + + this._client.request(options, function(err, data){ + if (err && self.logging) { + console.log('error trying to log user in'); + } + if(typeof(callback) === 'function') { + callback(err, data, data.entities); + } + }); + } + + /* + * Creates activity and posts to group feed. + * + * options object: {user: user_entity, content: "activity content"} + * + * @public + * @method createGroupActivity + * @params {object} options + * @param {function} callback + * @returns {callback} callback(err, entity) + */ + Usergrid.Group.prototype.createGroupActivity = function(options, callback){ + var user = options.user; + var options = { + actor: { + "displayName":user.get("username"), + "uuid":user.get("uuid"), + "username":user.get("username"), + "email":user.get("email"), + "picture":user.get("picture"), + "image": { + "duration":0, + "height":80, + "url":user.get("picture"), + "width":80 + }, + }, + "verb":"post", + "content":options.content }; + + options.type = 'groups/'+this._path+'/activities'; + var options = { + client:this._client, + data:options + } + + var entity = new Usergrid.Entity(options); + entity.save(function(err, data) { + if (typeof(callback) === 'function') { + callback(err, entity); + } + }); + } + + /* + * Tests if the string is a uuid + * + * @public + * @method isUUID + * @param {string} uuid The string to test + * @returns {Boolean} true if string is uuid + */ + function isUUID (uuid) { + var uuidValueRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; + if (!uuid) return false; + return uuidValueRegex.test(uuid); + } + + /* + * method to encode the query string parameters + * + * @method encodeParams + * @public + * @params {object} params - an object of name value pairs that will be urlencoded + * @return {string} Returns the encoded string + */ + function encodeParams (params) { + tail = []; + var item = []; + if (params instanceof Array) { + for (i in params) { + item = params[i]; + if ((item instanceof Array) && (item.length > 1)) { + tail.push(item[0] + "=" + encodeURIComponent(item[1])); + } + } + } else { + for (var key in params) { + if (params.hasOwnProperty(key)) { + var value = params[key]; + if (value instanceof Array) { + for (i in value) { + item = value[i]; + tail.push(key + "=" + encodeURIComponent(item)); + } + } else { + tail.push(key + "=" + encodeURIComponent(value)); + } + } + } + } + return tail.join("&"); + } +/* + * A class to model a Usergrid event. + * + * @constructor + * @param {object} options {timestamp:0, category:'value', counters:{name : value}} + * @returns {callback} callback(err, event) + */ +Usergrid.Counter = function(options, callback) { + var self=this; + this._client = options.client; + this._data = options.data || {}; + this._data.category = options.category||"UNKNOWN"; + this._data.timestamp = options.timestamp||0; + this._data.type = "events"; + this._data.counters=options.counters||{}; + if(typeof(callback) === 'function') { + callback.call(self, false, self); + } + //this.save(callback); +}; +var COUNTER_RESOLUTIONS=[ + 'all', 'minute', 'five_minutes', 'half_hour', + 'hour', 'six_day', 'day', 'week', 'month' +]; +/* + * Inherit from Usergrid.Entity. + * Note: This only accounts for data on the group object itself. + * You need to use add and remove to manipulate group membership. + */ +Usergrid.Counter.prototype = new Usergrid.Entity(); + +/* + * overrides Entity.prototype.fetch. Returns all data for counters + * associated with the object as specified in the constructor + * + * @public + * @method increment + * @param {function} callback + * @returns {callback} callback(err, event) + */ +Usergrid.Counter.prototype.fetch=function(callback){ + this.getData({}, callback); +} +/* + * increments the counter for a specific event + * + * options object: {name: counter_name} + * + * @public + * @method increment + * @params {object} options + * @param {function} callback + * @returns {callback} callback(err, event) + */ +Usergrid.Counter.prototype.increment=function(options, callback){ + var self=this, + name=options.name, + value=options.value; + if(!name){ + if(typeof(callback) === 'function') { + return callback.call(self, true, "'value' for increment, decrement must be a number"); + } + }else if(isNaN(value)){ + if(typeof(callback) === 'function') { + return callback.call(self, true, "'value' for increment, decrement must be a number"); + } + }else{ + self._data.counters[name]=(parseInt(value))||1; + return self.save(callback); + } +}; +/* + * decrements the counter for a specific event + * + * options object: {name: counter_name} + * + * @public + * @method decrement + * @params {object} options + * @param {function} callback + * @returns {callback} callback(err, event) + */ + +Usergrid.Counter.prototype.decrement=function(options, callback){ + var self=this, + name=options.name, + value=options.value; + self.increment({name:name, value:-((parseInt(value))||1)}, callback); +}; +/* + * resets the counter for a specific event + * + * options object: {name: counter_name} + * + * @public + * @method reset + * @params {object} options + * @param {function} callback + * @returns {callback} callback(err, event) + */ + +Usergrid.Counter.prototype.reset=function(options, callback){ + var self=this, + name=options.name; + self.increment({name:name, value:0}, callback); +}; + +/* + * gets data for one or more counters over a given + * time period at a specified resolution + * + * options object: { + * counters: ['counter1', 'counter2', ...], + * start: epoch timestamp or ISO date string, + * end: epoch timestamp or ISO date string, + * resolution: one of ('all', 'minute', 'five_minutes', 'half_hour', 'hour', 'six_day', 'day', 'week', or 'month') + * } + * + * @public + * @method getData + * @params {object} options + * @param {function} callback + * @returns {callback} callback(err, event) + */ +Usergrid.Counter.prototype.getData=function(options, callback){ + var start_time, + end_time, + start=options.start||0, + end=options.end||Date.now(), + resolution=(options.resolution||'all').toLowerCase(), + counters=options.counters||Object.keys(this._data.counters), + res=(resolution||'all').toLowerCase(); + if(COUNTER_RESOLUTIONS.indexOf(res)===-1){ + res='all'; + } + if(start){ + switch(typeof start){ + case "undefined": + start_time=0; + break; + case "number": + start_time=start; + break; + case "string": + start_time=(isNaN(start))?Date.parse(start):parseInt(start); + break; + default: + start_time=Date.parse(start.toString()); + } + } + if(end){ + switch(typeof end){ + case "undefined": + end_time=Date.now(); + break; + case "number": + end_time=end; + break; + case "string": + end_time=(isNaN(end))?Date.parse(end):parseInt(end); + break; + default: + end_time=Date.parse(end.toString()); + } + } + var self=this; + //https://api.usergrid.com/yourorgname/sandbox/counters?counter=test_counter + var params=Object.keys(counters).map(function(counter){ + return ["counter", encodeURIComponent(counters[counter])].join('='); + }); + params.push('resolution='+res) + params.push('start_time='+String(start_time)) + params.push('end_time='+String(end_time)) + + var endpoint="counters?"+params.join('&'); + this._client.request({endpoint:endpoint}, function(err, data){ + if(data.counters && data.counters.length){ + data.counters.forEach(function(counter){ + self._data.counters[counter.name]=counter.value||counter.values; + }) + } + if(typeof(callback) === 'function') { + callback.call(self, err, data); + } + }) +}; + +exports.client = Usergrid.Client; +exports.entity = Usergrid.Entity; +exports.collection = Usergrid.Collection; +exports.group = Usergrid.Group; +exports.counter = Usergrid.Counter; +exports.AUTH_CLIENT_ID = AUTH_CLIENT_ID; +exports.AUTH_APP_USER = AUTH_APP_USER; +exports.AUTH_NONE = AUTH_NONE; http://git-wip-us.apache.org/repos/asf/usergrid-nodejs/blob/7b92555b/package.json ---------------------------------------------------------------------- diff --git a/package.json b/package.json new file mode 100644 index 0000000..2d2579d --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "usergrid", + "version": "0.10.8", + "description": "A Node.js module for making API calls to App Services (Usergrid) from within Node.js", + "main": "./lib/usergrid.js", + "dependencies": { + "request": "2.12.x", + "inflection": "1.3.x" + }, + "devDependencies": { + "mocha": "1.7.x", + "should": "1.2.1" + }, + "scripts": { + "test": "mocha", + "start": "node example/server.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/apigee/usergrid-node-module.git" + }, + "bugs": { + "url": "http://github.com/apigee/usergrid-node-module.git/issues" + }, + "keywords": [ + "Node", + "Usergrid", + "Apigee", + "API" + ], + "tags": [ + "usergrid", + "API", + "Apigee" + ], + "author": { + "name": "Rod Simpson", + "email": "[email protected]", + "url" : "http://rodsimpson.com" + }, + "license": "Apache 2.0" +} +
