http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/examples/resources/css/styles.css ---------------------------------------------------------------------- diff --git a/examples/resources/css/styles.css b/examples/resources/css/styles.css new file mode 100755 index 0000000..7492f93 --- /dev/null +++ b/examples/resources/css/styles.css @@ -0,0 +1,91 @@ +/** +* All Calls is a Node.js sample app that is powered by Usergrid +* This app shows how to make the 4 REST calls (GET, POST, +* PUT, DELETE) against the usergrid API. +* +* Learn more at http://Usergrid.com/docs +* +* Copyright 2012 Apigee Corporation +* +* Licensed 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. +*/ + +/** +* @file styles.css +* @author Rod Simpson ([email protected]) +* +*/ + +body { + background-color: #fff; + min-height: 800px; +} + +/* buttons ================================================================= */ +.btn-primary{border-color:#1455ab #1455ab #0c3367;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);background-color:#146cab;background-image:-moz-linear-gradient(top, #147bab, #1455ab);background-image:-ms-linear-gradient(top, #147bab, #1455ab);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#147bab), to(#1455ab));background-image:-webkit-linear-gradient(top, #147bab, #1455ab);background-image:-o-linear-gradient(top, #147bab, #1455ab);background-image:linear-gradient(top, #147bab, #1455ab);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#147bab', endColorstr='#1455ab', GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#1455ab;} + +.header{ + padding: 10px; + width: 100%; + height: 40px; + background-color: #ff4200; + color: #fff; + text-align: left; + font-size: 16px; + font-weight: 800; +} +.breadcrumb{ + font-size: 16px; +} +.info{ + padding: 0px 30px 30px 30px; + font-size: 16px; +} +h3{ + padding-bottom: 20px; +} +.main{ + display: block; + padding: 0 30px 30px 30px ; + background-color: #fff; +} +.form-block{ + display: block; + display: none; + padding: 10px 0; + min-height: 210px; + background-color: #fff; +} +.section-header{ + font-size: 20px; + font-weight: 200; + padding-bottom: 20px; +} +.note { + padding-bottom: 20px; +} +.response-box{ + margin: 0 auto; + padding: 10px; + width: 640px; + border: 1px solid silver; + background-color: #ddd; + font-weight: bold; +} +pre{ + border: none; + padding: 0; +} +.left{ + float: left; +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/examples/resources/images/apigee.png ---------------------------------------------------------------------- diff --git a/examples/resources/images/apigee.png b/examples/resources/images/apigee.png new file mode 100755 index 0000000..c0d0f84 Binary files /dev/null and b/examples/resources/images/apigee.png differ http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/examples/resources/js/json2.js ---------------------------------------------------------------------- diff --git a/examples/resources/js/json2.js b/examples/resources/js/json2.js new file mode 100755 index 0000000..c7745df --- /dev/null +++ b/examples/resources/js/json2.js @@ -0,0 +1,486 @@ +/* + json2.js + 2012-10-08 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (typeof JSON !== 'object') { + JSON = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/examples/test/test.html ---------------------------------------------------------------------- diff --git a/examples/test/test.html b/examples/test/test.html new file mode 100755 index 0000000..e66fcb3 --- /dev/null +++ b/examples/test/test.html @@ -0,0 +1,54 @@ +<!-- + 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. +--> + +<!DOCTYPE html> +<html> + <head> + <title>Readme File Tests</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" href="../resources/css/bootstrap-combined.min.css" /> + <link rel="stylesheet" href="../resources/css/styles.css" /> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" type="text/javascript"></script> + <script src="../../usergrid.js" type="text/javascript"></script> + <script src="test.js" type="text/javascript"></script> + <script type="text/javascript"> + + </script> + </head> + <body> + <div class="header"> + <img src="../resources/images/apigee.png"> App Services (Usergrid) Javascript SDK + </div> + <div class="info"> + This sample application runs tests on the sample code examples in the readme file. Tests are run against App Services (Usergrid) using the Usergrid Javascript SDK. + </div> + <div id="main" class="main"> + <div class="section-header">README sample code tests</div> + <div class="well"> + <div id="name-control" class="control-group"> + <div class="controls"> + <button class="btn btn-primary" id="start-button" style="width: 90px;">Start</button> + <span style="clear: both;"> </span> + </div> + </div> + </div> + <div class="section-header"><b>Test Output</b></div> + <div class="well"> + <pre id="test-output">// Press Start button to begin</pre> + </div> + </body> +</html> http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/examples/test/test.js ---------------------------------------------------------------------- diff --git a/examples/test/test.js b/examples/test/test.js new file mode 100755 index 0000000..fb65d98 --- /dev/null +++ b/examples/test/test.js @@ -0,0 +1,978 @@ +/** +* Test suite for all the examples in the readme +* +* NOTE: No, this test suite doesn't use the traditional format for +* a test suite. This is because the goal is to require as little +* alteration as possible during the copy / paste operation from this test +* suite to the readme file. +* +* @author rod simpson ([email protected]) +*/ + +$(document).ready(function () { + +//call the runner function to start the process +$('#start-button').bind('click', function() { + $('#start-button').attr("disabled", "disabled"); + $('#test-output').html(''); + runner(0); +}); + +var logSuccess = true; +var successCount = 0; +var logError = true; +var errorCount = 0; +var logNotice = true; +var _unique = new Date().getTime(); +var _username = 'marty'+_unique; +var _email = 'marty'+_unique+'@timetravel.com'; +var _password = 'password2'; +var _newpassword = 'password3'; + +var client = new Usergrid.Client({ + orgName:'yourorgname', + appName:'sandbox', + logging: true, //optional - turn on logging, off by default + buildCurl: true //optional - turn on curl commands, off by default +}); + +client.logout(); + +function runner(step, arg, arg2){ + step++; + switch(step) + { + case 1: + notice('-----running step '+step+': DELETE user from DB to prep test'); + clearUser(step); + break; + case 2: + notice('-----running step '+step+': GET test'); + testGET(step); + break; + case 3: + notice('-----running step '+step+': POST test'); + testPOST(step); + break; + case 4: + notice('-----running step '+step+': PUT test'); + testPUT(step); + break; + case 5: + notice('-----running step '+step+': DELETE test'); + testDELETE(step); + break; + case 6: + notice('-----running step '+step+': prepare database - remove all dogs (no real dogs harmed here!!)'); + cleanupAllDogs(step); + break; + case 7: + notice('-----running step '+step+': make a new dog'); + makeNewDog(step); + break; + case 8: + notice('-----running step '+step+': update our dog'); + updateDog(step, arg); + break; + case 9: + notice('-----running step '+step+': refresh our dog'); + refreshDog(step, arg); + break; + case 10: + notice('-----running step '+step+': remove our dog from database (no real dogs harmed here!!)'); + removeDogFromDatabase(step, arg); + break; + case 11: + notice('-----running step '+step+': make lots of dogs!'); + makeSampleData(step, arg); + break; + case 12: + notice('-----running step '+step+': make a dogs collection and show each dog'); + testDogsCollection(step); + break; + case 13: + notice('-----running step '+step+': get the next page of the dogs collection and show each dog'); + getNextDogsPage(step, arg); + break; + case 14: + notice('-----running step '+step+': get the previous page of the dogs collection and show each dog'); + getPreviousDogsPage(step, arg); + break; + case 15: + notice('-----running step '+step+': remove all dogs from the database (no real dogs harmed here!!)'); + cleanupAllDogs(step); + break; + case 16: + notice('-----running step '+step+': prepare database (remove existing user if present)'); + prepareDatabaseForNewUser(step); + break; + case 17: + notice('-----running step '+step+': create a new user'); + createUser(step); + break; + case 18: + notice('-----running step '+step+': update the user'); + updateUser(step, arg); + break; + case 19: + notice('-----running step '+step+': get the existing user'); + getExistingUser(step, arg); + break; + case 20: + notice('-----running step '+step+': refresh the user from the database'); + refreshUser(step, arg); + break; + case 21: + notice('-----running step '+step+': log user in'); + loginUser(step, arg); + break; + case 22: + notice('-----running step '+step+': change users password'); + changeUsersPassword(step, arg); + break; + case 23: + notice('-----running step '+step+': log user out'); + logoutUser(step, arg); + break; + case 24: + notice('-----running step '+step+': relogin user'); + reloginUser(step, arg); + break; + case 25: + notice('-----running step '+step+': logged in user creates dog'); + createDog(step, arg); + break; + case 26: + notice('-----running step '+step+': logged in user likes dog'); + userLikesDog(step, arg, arg2); + break; + case 27: + notice('-----running step '+step+': logged in user removes likes connection to dog'); + removeUserLikesDog(step, arg, arg2); + break; + case 28: + notice('-----running step '+step+': user removes dog'); + removeDog(step, arg, arg2); + break; + case 29: + notice('-----running step '+step+': log the user out'); + logoutUser(step, arg); + break; + case 30: + notice('-----running step '+step+': remove the user from the database'); + destroyUser(step, arg); + break; + case 31: + notice('-----running step '+step+': try to create existing entity'); + createExistingEntity(step, arg); + break; + case 32: + notice('-----running step '+step+': try to create new entity with no name'); + createNewEntityNoName(step, arg); + break; + case 33: + notice('-----running step '+step+': clean up users'); + cleanUpUsers(step, arg); + break; + case 34: + notice('-----running step '+step+': clean up dogs'); + cleanUpDogs(step, arg); + break; + default: + notice('-----test complete!-----'); + notice('Success count= ' + successCount); + notice('Error count= ' + errorCount); + notice('-----thank you for playing!-----'); + $('#start-button').removeAttr("disabled"); + } +} + +//logging functions +function success(message){ + successCount++; + if (logSuccess) { + console.log('SUCCESS: ' + message); + var html = $('#test-output').html(); + html += ('SUCCESS: ' + message + '\r\n'); + $('#test-output').html(html); + } +} + +function error(message){ + errorCount++ + if (logError) { + console.log('ERROR: ' + message); + var html = $('#test-output').html(); + html += ('ERROR: ' + message + '\r\n'); + $('#test-output').html(html); + } +} + +function notice(message){ + if (logNotice) { + console.log('NOTICE: ' + message); + var html = $('#test-output').html(); + html += (message + '\r\n'); + $('#test-output').html(html); + } +} + +//tests +function clearUser(step) { + var options = { + method:'DELETE', + endpoint:'users/fred' + }; + client.request(options, function (err, data) { + //data will contain raw results from API call + success('User cleared from DB'); + runner(step); + }); +} + +function testGET(step) { + var options = { + method:'GET', + endpoint:'users' + }; + client.request(options, function (err, data) { + if (err) { + error('GET failed'); + } else { + //data will contain raw results from API call + success('GET worked'); + runner(step); + } + }); +} + +function testPOST(step) { + var options = { + method:'POST', + endpoint:'users', + body:{ username:'fred', password:'secret' } + }; + client.request(options, function (err, data) { + if (err) { + error('POST failed'); + } else { + //data will contain raw results from API call + success('POST worked'); + runner(step); + } + }); +} + +function testPUT(step) { + var options = { + method:'PUT', + endpoint:'users/fred', + body:{ newkey:'newvalue' } + }; + client.request(options, function (err, data) { + if (err) { + error('PUT failed'); + } else { + //data will contain raw results from API call + success('PUT worked'); + runner(step); + } + }); +} + +function testDELETE(step) { + var options = { + method:'DELETE', + endpoint:'users/fred' + }; + client.request(options, function (err, data) { + if (err) { + error('DELETE failed'); + } else { + //data will contain raw results from API call + success('DELETE worked'); + runner(step); + } + }); +} + +function makeNewDog(step) { + + var options = { + type:'dogs', + name:'Ralph'+_unique + } + + client.createEntity(options, function (err, response, dog) { + if (err) { + error('dog not created'); + } else { + success('dog is created'); + + //once the dog is created, you can set single properties: + dog.set('breed','Dinosaur'); + + //the set function can also take a JSON object: + var data = { + master:'Fred', + state:'hungry' + } + //set is additive, so previously set properties are not overwritten + dog.set(data); + + //finally, call save on the object to save it back to the database + dog.save(function(err){ + if (err){ + error('dog not saved'); + } else { + success('new dog is saved'); + runner(step, dog); + } + }); + } + }); + +} + +function updateDog(step, dog) { + + //change a property in the object + dog.set("state", "fed"); + //and save back to the database + dog.save(function(err){ + if (err){ + error('dog not saved'); + } else { + success('dog is saved'); + runner(step, dog); + } + }); + +} + +function refreshDog(step, dog){ + + //call fetch to refresh the data from the server + dog.fetch(function(err){ + if (err){ + error('dog not refreshed from database'); + } else { + //dog has been refreshed from the database + //will only work if the UUID for the entity is in the dog object + success('dog entity refreshed from database'); + runner(step, dog); + } + }); + +} + +function removeDogFromDatabase(step, dog){ + + //the destroy method will delete the entity from the database + dog.destroy(function(err){ + if (err){ + error('dog not removed from database'); + } else { + success('dog removed from database'); // no real dogs were harmed! + dog = null; //no real dogs were harmed! + runner(step, 1); + } + }); + +} + +function makeSampleData(step, i) { + notice('making dog '+i); + + var options = { + type:'dogs', + name:'dog'+_unique+i, + index:i + } + + client.createEntity(options, function (err, dog) { + if (err) { + error('dog ' + i + ' not created'); + } else { + if (i >= 30) { + //data made, ready to go + success('all dogs made'); + runner(step); + } else { + success('dog ' + i + ' made'); + //keep making dogs + makeSampleData(step, ++i); + } + } + }); +} + +function testDogsCollection(step) { + + var options = { + type:'dogs', + qs:{ql:'order by index'} + } + + client.createCollection(options, function (err, response, dogs) { + if (err) { + error('could not make collection'); + } else { + + success('new Collection created'); + + //we got the dogs, now display the Entities: + while(dogs.hasNextEntity()) { + //get a reference to the dog + dog = dogs.getNextEntity(); + var name = dog.get('name'); + notice('dog is called ' + name); + } + + success('looped through dogs'); + + //create a new dog and add it to the collection + var options = { + name:'extra-dog', + fur:'shedding' + } + //just pass the options to the addEntity method + //to the collection and it is saved automatically + dogs.addEntity(options, function(err, dog, data) { + if (err) { + error('extra dog not saved or added to collection'); + } else { + success('extra dog saved and added to collection'); + runner(step, dogs); + } + }); + } + }); + +} + +function getNextDogsPage(step, dogs) { + + if (dogs.hasNextPage()) { + //there is a next page, so get it from the server + dogs.getNextPage(function(err){ + if (err) { + error('could not get next page of dogs'); + } else { + success('got next page of dogs'); + //we got the next page of data, so do something with it: + var i = 11; + while(dogs.hasNextEntity()) { + //get a reference to the dog + var dog = dogs.getNextEntity(); + var index = dog.get('index'); + if(i !== index) { + error('wrong dog loaded: wanted' + i + ', got ' + index); + } + notice('got dog ' + i); + i++ + } + success('looped through dogs') + runner(step, dogs); + } + }); + } else { + getPreviousDogsPage(dogs); + } + +} + +function getPreviousDogsPage(step, dogs) { + + if (dogs.hasPreviousPage()) { + //there is a previous page, so get it from the server + dogs.getPreviousPage(function(err){ + if(err) { + error('could not get previous page of dogs'); + } else { + success('got next page of dogs'); + //we got the previous page of data, so do something with it: + var i = 1; + while(dogs.hasNextEntity()) { + //get a reference to the dog + var dog = dogs.getNextEntity(); + var index = dog.get('index'); + if(i !== index) { + error('wrong dog loaded: wanted' + i + ', got ' + index); + } + notice('got dog ' + i); + i++ + } + success('looped through dogs'); + runner(step); + } + }); + } else { + getAllDogs(); + } +} + +function cleanupAllDogs(step){ + + var options = { + type:'dogs', + qs:{limit:50} //limit statement set to 50 + } + + client.createCollection(options, function (err, response, dogs) { + if (err) { + error('could not get all dogs'); + } else { + success('got at most 50 dogs'); + //we got 50 dogs, now display the Entities: + while(dogs.hasNextEntity()) { + //get a reference to the dog + var dog = dogs.getNextEntity(); + var name = dog.get('name'); + notice('dog is called ' + name); + } + dogs.resetEntityPointer(); + //do doggy cleanup + while(dogs.hasNextEntity()) { + //get a reference to the dog + var dog = dogs.getNextEntity(); + var dogname = dog.get('name'); + notice('removing dog ' + dogname + ' from database'); + dog.destroy(function(err, data) { + if (err) { + error('dog not removed'); + } else { + success('dog removed'); + } + }); + } + + //no need to wait around for dogs to be removed, so go on to next test + runner(step); + } + }); +} + + +function prepareDatabaseForNewUser(step) { + var options = { + method:'DELETE', + endpoint:'users/', + qs:{ql:"select * where username ='marty*'"} + }; + client.request(options, function (err, data) { + if (err) { + notice('database ready - no user to delete'); + runner(step); + } else { + //data will contain raw results from API call + success('database ready - user deleted worked'); + runner(step); + } + }); +} + +function createUser(step) { + client.signup(_username, _password, _email, 'Marty McFly', + function (err, response, marty) { + if (err){ + error('user not created'); + runner(step, marty); + } else { + success('user created'); + runner(step, marty); + } + } + ); +} + +function updateUser(step, marty) { + + //add properties cumulatively + marty.set('state', 'California'); + marty.set("girlfriend","Jennifer"); + marty.save(function(err){ + if (err){ + error('user not updated'); + } else { + success('user updated'); + runner(step, marty); + } + }); + +} + +function getExistingUser(step, marty) { + + var options = { + type:'users', + username:_username + } + client.getEntity(options, function(err, response, existingUser){ + if (err){ + error('existing user not retrieved'); + } else { + success('existing user was retrieved'); + + var username = existingUser.get('username'); + if (username === _username){ + success('got existing user username'); + } else { + error('could not get existing user username'); + } + runner(step, marty); + } + }); + +} + + +function refreshUser(step, marty) { + + marty.fetch(function(err){ + if (err){ + error('not refreshed'); + } else { + success('user refreshed'); + runner(step, marty); + } + }); + +} + +function loginUser(step, marty) { + username = _username; + password = _password; + client.login(username, password, + function (err, response, user) { + if (err) { + error('could not log user in'); + } else { + success('user has been logged in'); + + //the login call will return an OAuth token, which is saved + //in the client. Any calls made now will use the token. + //once a user has logged in, their user object is stored + //in the client and you can access it this way: + var token = client.token; + + //Then make calls against the API. For example, you can + //get the user entity this way: + client.getLoggedInUser(function(err, data, user) { + console.error(err, data, user); + if(err) { + error('could not get logged in user'); + } else { + success('got logged in user'); + + //you can then get info from the user entity object: + var username = user.get('username'); + notice('logged in user was: ' + username); + + runner(step, user); + } + }); + + } + } + ); +} + +function changeUsersPassword(step, marty) { + + marty.set('oldpassword', _password); + marty.set('password', _newpassword); + marty.set('newpassword', _newpassword); + marty.changePassword(function(err, response){ + if (err){ + error('user password not updated'); + } else { + success('user password updated'); + runner(step, marty); + } + }); + +} + +function logoutUser(step, marty) { + + //to log the user out, call the logout() method + client.logout(); + + //verify the logout worked + if (client.isLoggedIn()) { + error('logout failed'); + } else { + success('user has been logged out'); + } + + runner(step, marty); +} + +function reloginUser(step, marty) { + + username = _username + password = _newpassword; + client.login(username, password, + function (err, response, marty) { + if (err) { + error('could not relog user in'); + } else { + success('user has been re-logged in'); + runner(step, marty); + } + } + ); +} + + + +//TODO: currently, this code assumes permissions have been set to support user actions. need to add code to show how to add new role and permission programatically +// +//first create a new permission on the default role: +//POST "https://api.usergrid.com/yourorgname/yourappname/roles/default/permissions" -d '{"permission":"get,post,put,delete:/dogs/**"}' +//then after user actions, delete the permission on the default role: +//DELETE "https://api.usergrid.com/yourorgname/yourappname/roles/default/permissions?permission=get%2Cpost%2Cput%2Cdelete%3A%2Fdogs%2F**" + + +function createDog(step, marty) { + + var options = { + type:'dogs', + name:'einstein', + breed:'mutt' + } + + client.createEntity(options, function (err, response, dog) { + if (err) { + error('POST new dog by logged in user failed'); + } else { + success('POST new dog by logged in user succeeded'); + runner(step, marty, dog); + } + }); + +} + +function userLikesDog(step, marty, dog) { + + marty.connect('likes', dog, function (err, response, data) { + if (err) { + error('connection not created'); + runner(step, marty); + } else { + + //call succeeded, so pull the connections back down + marty.getConnections('likes', function (err, response, data) { + if (err) { + error('could not get connections'); + } else { + //verify that connection exists + if (marty.likes.einstein) { + success('connection exists'); + } else { + error('connection does not exist'); + } + + runner(step, marty, dog); + } + }); + } + }); + +} + +function removeUserLikesDog(step, marty, dog) { + + marty.disconnect('likes', dog, function (err, data) { + if (err) { + error('connection not deleted'); + runner(step, marty); + } else { + + //call succeeded, so pull the connections back down + marty.getConnections('likes', function (err, data) { + if (err) { + error('error getting connections'); + } else { + //verify that connection exists + if (marty.likes.einstein) { + error('connection still exists'); + } else { + success('connection deleted'); + } + + runner(step, marty, dog); + } + }); + } + }); + +} + +function removeDog(step, marty, dog) { + + //now delete the dog from the database + dog.destroy(function(err, data) { + if (err) { + error('dog not removed'); + } else { + success('dog removed'); + } + }); + runner(step, marty); +} + +function destroyUser(step, marty) { + + marty.destroy(function(err){ + if (err){ + error('user not deleted from database'); + } else { + success('user deleted from database'); + marty = null; //blow away the local object + runner(step); + } + }); + +} + +function createExistingEntity(step, marty) { + + var options = { + type:'dogs', + name:'einstein' + } + + client.createEntity(options, function (err, response, dog) { + if (err) { + error('Create new entity to use for existing entity failed'); + } else { + success('Create new entity to use for existing entity succeeded'); + + var uuid = dog.get('uuid'); + //now create new entity, but use same entity name of einstein. This means that + //the original einstein entity now exists. Thus, the new einstein entity should + //be the same as the original + any data differences from the options var: + + options = { + type:'dogs', + name:'einstein', + breed:'mutt' + } + client.createEntity(options, function (err, response, newdog) { + if (err) { + success('Create new entity to use for existing entity failed'); + } else { + error('Create new entity to use for existing entity succeeded'); + } + dog.destroy(function(err){ + if (err){ + error('existing entity not deleted from database'); + } else { + success('existing entity deleted from database'); + dog = null; //blow away the local object + newdog = null; //blow away the local object + runner(step); + } + }); + }); + } + }); + +} + +function createNewEntityNoName(step, marty) { + + var options = { + type:"something", + othervalue:"something else" + } + + client.createEntity(options, function (err, response, entity) { + if (err) { + error('Create new entity with no name failed'); + } else { + success('Create new entity with no name succeeded'); + + entity.destroy(); + runner(step); + } + }); + +} + +function cleanUpUsers(step){ + + var options = { + type:'users', + qs:{limit:50} //limit statement set to 50 + } + + client.createCollection(options, function (err, response, users) { + if (err) { + error('could not get all users'); + } else { + success('got users'); + //do doggy cleanup + while(users.hasNextEntity()) { + //get a reference to the dog + var user = users.getNextEntity(); + var username = user.get('name'); + notice('removing dog ' + username + ' from database'); + user.destroy(function(err, data) { + if (err) { + error('user not removed'); + } else { + success('user removed'); + } + }); + } + + runner(step); + } + }); + +} + +function cleanUpDogs(step){ + + var options = { + type:'dogs', + qs:{limit:50} //limit statement set to 50 + } + + client.createCollection(options, function (err, response, dogs) { + if (err) { + error('could not get all dogs'); + } else { + success('got at most 50 dogs'); + //we got 50 dogs, now display the Entities: + while(dogs.hasNextEntity()) { + //get a reference to the dog + var dog = dogs.getNextEntity(); + var name = dog.get('name'); + notice('dog is called ' + name); + } + dogs.resetEntityPointer(); + //do doggy cleanup + while(dogs.hasNextEntity()) { + //get a reference to the dog + var dog = dogs.getNextEntity(); + var dogname = dog.get('name'); + notice('removing dog ' + dogname + ' from database'); + dog.destroy(function(err, data) { + if (err) { + error('dog not removed'); + } else { + success('dog removed'); + } + }); + } + + //no need to wait around for dogs to be removed, so go on to next test + runner(step); + } + }); + } +}); http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/extensions/usergrid.validation.js ---------------------------------------------------------------------- diff --git a/extensions/usergrid.validation.js b/extensions/usergrid.validation.js new file mode 100755 index 0000000..42c1564 --- /dev/null +++ b/extensions/usergrid.validation.js @@ -0,0 +1,267 @@ +/* + * 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. + */ + +/** + * validation is a Singleton that provides methods for validating common field types + * + * @class Usergrid.validation + * @author Rod Simpson ([email protected]) +**/ +Usergrid.validation = (function () { + + var usernameRegex = new RegExp("^([0-9a-zA-Z\.\-])+$"); + var nameRegex = new RegExp("^([0-9a-zA-Z@#$%^&!?;:.,'\"~*-=+_\[\\](){}/\\ |])+$"); + var emailRegex = new RegExp("^(([0-9a-zA-Z]+[_\+.-]?)+@[0-9a-zA-Z]+[0-9,a-z,A-Z,.,-]*(.){1}[a-zA-Z]{2,4})+$"); + var passwordRegex = new RegExp("^([0-9a-zA-Z@#$%^&!?<>;:.,'\"~*-=+_\[\\](){}/\\ |])+$"); + var pathRegex = new RegExp("^([0-9a-z./-])+$"); + var titleRegex = new RegExp("^([0-9a-zA-Z.!-?/])+$"); + + /** + * Tests the string against the allowed chars regex + * + * @public + * @method validateUsername + * @param {string} username - The string to test + * @param {function} failureCallback - (optional), the function to call on a failure + * @return {boolean} Returns true if string passes regex, false if not + */ + function validateUsername(username, failureCallback) { + if (usernameRegex.test(username) && checkLength(username, 4, 80)) { + return true; + } else { + if (failureCallback && typeof(failureCallback) === "function") { + failureCallback(this.getUsernameAllowedChars()); + } + return false; + } + } + + /** + * Returns the regex of allowed chars + * + * @public + * @method getUsernameAllowedChars + * @return {string} Returns a string with the allowed chars + */ + function getUsernameAllowedChars(){ + return 'Length: min 4, max 80. Allowed: A-Z, a-z, 0-9, dot, and dash'; + } + + /** + * Tests the string against the allowed chars regex + * + * @public + * @method validateName + * @param {string} name - The string to test + * @param {function} failureCallback - (optional), the function to call on a failure + * @return {boolean} Returns true if string passes regex, false if not + */ + function validateName(name, failureCallback) { + if (nameRegex.test(name) && checkLength(name, 4, 80)) { + return true; + } else { + if (failureCallback && typeof(failureCallback) === "function") { + failureCallback(this.getNameAllowedChars()); + } + return false; + } + } + + /** + * Returns the regex of allowed chars + * + * @public + * @method getNameAllowedChars + * @return {string} Returns a string with the allowed chars + */ + function getNameAllowedChars(){ + return 'Length: min 4, max 80. Allowed: A-Z, a-z, 0-9, ~ @ # % ^ & * ( ) - _ = + [ ] { } \\ | ; : \' " , . / ? !'; + } + + /** + * Tests the string against the allowed chars regex + * + * @public + * @method validatePassword + * @param {string} password - The string to test + * @param {function} failureCallback - (optional), the function to call on a failure + * @return {boolean} Returns true if string passes regex, false if not + */ + function validatePassword(password, failureCallback) { + if (passwordRegex.test(password) && checkLength(password, 5, 16)) { + return true; + } else { + if (failureCallback && typeof(failureCallback) === "function") { + failureCallback(this.getPasswordAllowedChars()); + } + return false; + } + } + + /** + * Returns the regex of allowed chars + * + * @public + * @method getPasswordAllowedChars + * @return {string} Returns a string with the allowed chars + */ + function getPasswordAllowedChars(){ + return 'Length: min 5, max 16. Allowed: A-Z, a-z, 0-9, ~ @ # % ^ & * ( ) - _ = + [ ] { } \\ | ; : \' " , . < > / ? !'; + } + + /** + * Tests the string against the allowed chars regex + * + * @public + * @method validateEmail + * @param {string} email - The string to test + * @param {function} failureCallback - (optional), the function to call on a failure + * @return {boolean} Returns true if string passes regex, false if not + */ + function validateEmail(email, failureCallback) { + if (emailRegex.test(email) && checkLength(email, 4, 80)) { + return true; + } else { + if (failureCallback && typeof(failureCallback) === "function") { + failureCallback(this.getEmailAllowedChars()); + } + return false; + } + } + + /** + * Returns the regex of allowed chars + * + * @public + * @method getEmailAllowedChars + * @return {string} Returns a string with the allowed chars + */ + function getEmailAllowedChars(){ + return 'Email must be in standard form: e.g. [email protected]'; + } + + /** + * Tests the string against the allowed chars regex + * + * @public + * @method validatePath + * @param {string} path - The string to test + * @param {function} failureCallback - (optional), the function to call on a failure + * @return {boolean} Returns true if string passes regex, false if not + */ + function validatePath(path, failureCallback) { + if (pathRegex.test(path) && checkLength(path, 4, 80)) { + return true; + } else { + if (failureCallback && typeof(failureCallback) === "function") { + failureCallback(this.getPathAllowedChars()); + } + return false; + } + } + + /** + * Returns the regex of allowed chars + * + * @public + * @method getPathAllowedChars + * @return {string} Returns a string with the allowed chars + */ + function getPathAllowedChars(){ + return 'Length: min 4, max 80. Allowed: /, a-z, 0-9, dot, and dash'; + } + + /** + * Tests the string against the allowed chars regex + * + * @public + * @method validateTitle + * @param {string} title - The string to test + * @param {function} failureCallback - (optional), the function to call on a failure + * @return {boolean} Returns true if string passes regex, false if not + */ + function validateTitle(title, failureCallback) { + if (titleRegex.test(title) && checkLength(title, 4, 80)) { + return true; + } else { + if (failureCallback && typeof(failureCallback) === "function") { + failureCallback(this.getTitleAllowedChars()); + } + return false; + } + } + + /** + * Returns the regex of allowed chars + * + * @public + * @method getTitleAllowedChars + * @return {string} Returns a string with the allowed chars + */ + function getTitleAllowedChars(){ + return 'Length: min 4, max 80. Allowed: space, A-Z, a-z, 0-9, dot, dash, /, !, and ?'; + } + + /** + * Tests if the string is the correct length + * + * @public + * @method checkLength + * @param {string} string - The string to test + * @param {integer} min - the lower bound + * @param {integer} max - the upper bound + * @return {boolean} Returns true if string is correct length, false if not + */ + function checkLength(string, min, max) { + if (string.length > max || string.length < min) { + return false; + } + return true; + } + + /** + * 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); + } + + return { + validateUsername:validateUsername, + getUsernameAllowedChars:getUsernameAllowedChars, + validateName:validateName, + getNameAllowedChars:getNameAllowedChars, + validatePassword:validatePassword, + getPasswordAllowedChars:getPasswordAllowedChars, + validateEmail:validateEmail, + getEmailAllowedChars:getEmailAllowedChars, + validatePath:validatePath, + getPathAllowedChars:getPathAllowedChars, + validateTitle:validateTitle, + getTitleAllowedChars:getTitleAllowedChars, + isUUID:isUUID + } +})(); http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/index.html ---------------------------------------------------------------------- diff --git a/index.html b/index.html new file mode 100755 index 0000000..309d90b --- /dev/null +++ b/index.html @@ -0,0 +1,70 @@ +<!-- + 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. +--> + +<!DOCTYPE html> +<html> + <head> + <title></title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" href="examples/resources/css/bootstrap-combined.min.css" /> + <link rel="stylesheet" href="examples/resources/css/styles.css" /> + </head> + <body> + <div class="header"> + <img src="examples/resources/images/apigee.png"> Usergrid Javascript SDK + </div> + <div id="main" class="main"> + <br/> + <h2>Javascript SDK</h2> + This SDK is designed to enable you to connect your apps to the Usergrid API. + <br/> + <br/> + <h2>Read me</h2> + View the read me file for the SDK: + <a href="https://github.com/usergrid/usergrid/blob/master/sdks/html5-javascript/README.md">https://github.com/usergrid/usergrid/blob/master/sdks/html5-javascript/README.md</a>. + <br/> + <br/> + <h2>API documentation</h2> + For more information on Usergrid, see the Apache docs: + <a href="http://usergrid.apache.org/">http://usergrid.apache.org/</a> + <br/> + <br/> + + <h2>Example Apps:</h2> + <br/> + <a href="examples/all-calls/all-calls.html" style="font-size: 20px;">All Calls example app</a> + <br/> + This app shows the basic method for making all call types against the API. It also shows how to log in an app user. + <br/> + <br/> + <a href="examples/dogs/dogs.html" style="font-size: 20px;">Dogs example app</a> + <br/> + This app shows you how to use the Entity and Collection objects + <br/> + <br/> + <a href="examples/facebook/facebook.html" style="font-size: 20px;">Facebook Login Example</a> + <br/> + This app shows you how to use Facebook to log into Usergrid. + <br/> + <br/> + <a href="examples/test/test.html" style="font-size: 20px;">Tests for examples in readme file</a> + <br/> + This is a test script that runs all the sample code shown in the readme file. See the samples in action! + </div> + + </body> +</html> http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/Module.js ---------------------------------------------------------------------- diff --git a/lib/Module.js b/lib/Module.js new file mode 100644 index 0000000..38f5047 --- /dev/null +++ b/lib/Module.js @@ -0,0 +1,49 @@ +/* + * 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. + */ + + //noinspection ThisExpressionReferencesGlobalObjectJS +(function (global) { + var name = 'Module', + short = '_m', + _name = global[name], + _short = (short !== undefined) ? global[short] : undefined; + + function Module() { + /* put code in here */ + + } + + global[name] = Module; + if (short !== undefined) { + global[short] = Module; + } + global[name].global[name].noConflict = function () { + if (_name) { + global[name] = _name; + } + if (short !== undefined) { + global[short] = _short; + } + return Module; + }; + if( typeof module !== "undefined" && ('exports' in module)){ + module.exports = global[name] + } + return global[name]; +}(this)); http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/Usergrid.js ---------------------------------------------------------------------- diff --git a/lib/Usergrid.js b/lib/Usergrid.js new file mode 100644 index 0000000..f1bb218 --- /dev/null +++ b/lib/Usergrid.js @@ -0,0 +1,323 @@ +/* + * 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. + */ + +//Hack around IE console.log +window.console = window.console || {}; +window.console.log = window.console.log || function() {}; + + +function extend(subClass, superClass) { + var F = function() {}; + F.prototype = superClass.prototype; + subClass.prototype = new F(); + subClass.prototype.constructor = subClass; + + subClass.superclass = superClass.prototype; + if (superClass.prototype.constructor == Object.prototype.constructor) { + superClass.prototype.constructor = superClass; + } + return subClass; +} + +function propCopy(from, to) { + for (var prop in from) { + if (from.hasOwnProperty(prop)) { + if ("object" === typeof from[prop] && "object" === typeof to[prop]) { + to[prop] = propCopy(from[prop], to[prop]); + } else { + to[prop] = from[prop]; + } + } + } + return to; +} + +function NOOP() {} + +function isValidUrl(url) { + if (!url) return false; + var doc, base, anchor, isValid = false; + try { + doc = document.implementation.createHTMLDocument(''); + base = doc.createElement('base'); + base.href = base || window.lo; + doc.head.appendChild(base); + anchor = doc.createElement('a'); + anchor.href = url; + doc.body.appendChild(anchor); + isValid = !(anchor.href === '') + } catch (e) { + console.error(e); + } finally { + doc.head.removeChild(base); + doc.body.removeChild(anchor); + base = null; + anchor = null; + doc = null; + return isValid; + } +} + +/* + * Tests if the string is a uuid + * + * @public + * @method isUUID + * @param {string} uuid The string to test + * @returns {Boolean} true if string is 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}$/; + +function isUUID(uuid) { + return (!uuid) ? false : 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) { + var queryString; + if (params && Object.keys(params)) { + queryString = [].slice.call(arguments) + .reduce(function(a, b) { + return a.concat((b instanceof Array) ? b : [b]); + }, []) + .filter(function(c) { + return "object" === typeof c + }) + .reduce(function(p, c) { + (!(c instanceof Array)) ? p = p.concat(Object.keys(c).map(function(key) { + return [key, c[key]] + })) : p.push(c); + return p; + }, []) + .reduce(function(p, c) { + ((c.length === 2) ? p.push(c) : p = p.concat(c)); + return p; + }, []) + .reduce(function(p, c) { + (c[1] instanceof Array) ? c[1].forEach(function(v) { + p.push([c[0], v]) + }) : p.push(c); + return p; + }, []) + .map(function(c) { + c[1] = encodeURIComponent(c[1]); + return c.join('=') + }) + .join('&'); + } + return queryString; +} + + +/* + * method to determine whether or not the passed variable is a function + * + * @method isFunction + * @public + * @params {any} f - any variable + * @return {boolean} Returns true or false + */ +function isFunction(f) { + return (f && f !== null && typeof(f) === 'function'); +} + +/* + * a safe wrapper for executing a callback + * + * @method doCallback + * @public + * @params {Function} callback - the passed-in callback method + * @params {Array} params - an array of arguments to pass to the callback + * @params {Object} context - an optional calling context for the callback + * @return Returns whatever would be returned by the callback. or false. + */ +function doCallback(callback, params, context) { + var returnValue; + if (isFunction(callback)) { + if (!params) params = []; + if (!context) context = this; + params.push(context); + //try { + returnValue = callback.apply(context, params); + /*} catch (ex) { + if (console && console.error) { + console.error("Callback error:", ex); + } + }*/ + } + return returnValue; +} + +//noinspection ThisExpressionReferencesGlobalObjectJS +(function(global) { + var name = 'Usergrid', + overwrittenName = global[name]; + var VALID_REQUEST_METHODS = ['GET', 'POST', 'PUT', 'DELETE']; + + function Usergrid() { + this.logger = new Logger(name); + } + + Usergrid.isValidEndpoint = function(endpoint) { + //TODO actually implement this + return true; + }; + + Usergrid.Request = function(method, endpoint, query_params, data, callback) { + var p = new Promise(); + /* + Create a logger + */ + this.logger = new global.Logger("Usergrid.Request"); + this.logger.time("process request " + method + " " + endpoint); + /* + Validate our input + */ + this.endpoint = endpoint + '?' + encodeParams(query_params); + this.method = method.toUpperCase(); + //this.query_params = query_params; + this.data = ("object" === typeof data) ? JSON.stringify(data) : data; + + if (VALID_REQUEST_METHODS.indexOf(this.method) === -1) { + throw new UsergridInvalidHTTPMethodError("invalid request method '" + this.method + "'"); + } + + /* + Prepare our request + */ + if (!isValidUrl(this.endpoint)) { + this.logger.error(endpoint, this.endpoint, /^https:\/\//.test(endpoint)); + throw new UsergridInvalidURIError("The provided endpoint is not valid: " + this.endpoint); + } + /* a callback to make the request */ + var request = function() { + return Ajax.request(this.method, this.endpoint, this.data) + }.bind(this); + /* a callback to process the response */ + var response = function(err, request) { + return new Usergrid.Response(err, request) + }.bind(this); + /* a callback to clean up and return data to the client */ + var oncomplete = function(err, response) { + p.done(err, response); + this.logger.info("REQUEST", err, response); + doCallback(callback, [err, response]); + this.logger.timeEnd("process request " + method + " " + endpoint); + }.bind(this); + /* and a promise to chain them all together */ + Promise.chain([request, response]).then(oncomplete); + + return p; + }; + //TODO more granular handling of statusCodes + Usergrid.Response = function(err, response) { + var p = new Promise(); + var data = null; + try { + data = JSON.parse(response.responseText); + } catch (e) { + //this.logger.error("Error parsing response text: ",this.text); + //this.logger.error("Caught error ", e.message); + data = {} + } + Object.keys(data).forEach(function(key) { + Object.defineProperty(this, key, { + value: data[key], + enumerable: true + }); + }.bind(this)); + Object.defineProperty(this, "logger", { + enumerable: false, + configurable: false, + writable: false, + value: new global.Logger(name) + }); + Object.defineProperty(this, "success", { + enumerable: false, + configurable: false, + writable: true, + value: true + }); + Object.defineProperty(this, "err", { + enumerable: false, + configurable: false, + writable: true, + value: err + }); + Object.defineProperty(this, "status", { + enumerable: false, + configurable: false, + writable: true, + value: parseInt(response.status) + }); + Object.defineProperty(this, "statusGroup", { + enumerable: false, + configurable: false, + writable: true, + value: (this.status - this.status % 100) + }); + switch (this.statusGroup) { + case 200: //success + this.success = true; + break; + case 400: //user error + case 500: //server error + case 300: //cache and redirects + case 100: //upgrade + default: + //server error + this.success = false; + break; + } + if (this.success) { + p.done(null, this); + } else { + p.done(UsergridError.fromResponse(data), this); + } + return p; + }; + Usergrid.Response.prototype.getEntities = function() { + var entities; + if (this.success) { + entities = (this.data) ? this.data.entities : this.entities; + } + return entities || []; + } + Usergrid.Response.prototype.getEntity = function() { + var entities = this.getEntities(); + return entities[0]; + } + Usergrid.VERSION = Usergrid.USERGRID_SDK_VERSION = '0.11.0'; + + global[name] = Usergrid; + global[name].noConflict = function() { + if (overwrittenName) { + global[name] = overwrittenName; + } + return Usergrid; + }; + return global[name]; +}(this)); http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/modules/Asset.js ---------------------------------------------------------------------- diff --git a/lib/modules/Asset.js b/lib/modules/Asset.js new file mode 100644 index 0000000..862313a --- /dev/null +++ b/lib/modules/Asset.js @@ -0,0 +1,248 @@ +/* + *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. + */ + + + +/* + * XMLHttpRequest.prototype.sendAsBinary polyfill + * from: https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#sendAsBinary() + * + * @method sendAsBinary + * @param {string} sData + */ +if (!XMLHttpRequest.prototype.sendAsBinary) { + XMLHttpRequest.prototype.sendAsBinary = function(sData) { + var nBytes = sData.length, + ui8Data = new Uint8Array(nBytes); + for (var nIdx = 0; nIdx < nBytes; nIdx++) { + ui8Data[nIdx] = sData.charCodeAt(nIdx) & 0xff; + } + this.send(ui8Data); + }; +} + + +/* + * A class to model a Usergrid asset. + * + * @constructor + * @param {object} options {name:"photo.jpg", path:"/user/uploads", "content-type":"image/jpeg", owner:"F01DE600-0000-0000-0000-000000000000" } + * @returns {callback} callback(err, asset) + */ +Usergrid.Asset = function(options, callback) { + var self = this, + messages = []; + self._client = options.client; + self._data = options.data || {}; + self._data.type = "assets"; + var missingData = ["name", "owner", "path"].some(function(required) { + return !(required in self._data); + }); + if (missingData) { + doCallback(callback, [new UsergridError("Invalid asset data: 'name', 'owner', and 'path' are required properties."), null, self], self); + } else { + self.save(function(err, data) { + if (err) { + doCallback(callback, [new UsergridError(data), data, self], self); + } else { + if (data && data.entities && data.entities.length) { + self.set(data.entities[0]); + } + doCallback(callback, [null, data, self], self); + } + }); + } +}; + +/* + * Inherit from Usergrid.Entity. + */ +Usergrid.Asset.prototype = new Usergrid.Entity(); + +/* + * Add an asset to a folder. + * + * @method connect + * @public + * @param {object} options {folder:"F01DE600-0000-0000-0000-000000000000"} + * @returns {callback} callback(err, asset) + */ + +Usergrid.Asset.prototype.addToFolder = function(options, callback) { + var self = this, + error = null; + if (('folder' in options) && isUUID(options.folder)) { + //we got a valid UUID + var folder = Usergrid.Folder({ + uuid: options.folder + }, function(err, folder) { + if (err) { + doCallback(callback, [UsergridError.fromResponse(folder), folder, self], self); + } else { + var endpoint = ["folders", folder.get("uuid"), "assets", self.get("uuid")].join('/'); + var options = { + method: 'POST', + endpoint: endpoint + }; + this._client.request(options, function(err, response) { + if (err) { + doCallback(callback, [UsergridError.fromResponse(folder), response, self], self); + } else { + doCallback(callback, [null, folder, self], self); + } + + + }); + } + }); + } else { + doCallback(callback, [new UsergridError('folder not specified'), null, self], self); + } +}; + +Usergrid.Entity.prototype.attachAsset = function (file, callback) { + + if (!(window.File && window.FileReader && window.FileList && window.Blob)) { + doCallback(callback, [ new UsergridError("The File APIs are not fully supported by your browser."), null, this ], this); + return; + } + var self = this; + var args = arguments; + var type = this._data.type; + var attempts = self.get("attempts"); + if (isNaN(attempts)) { + attempts = 3; + } + + if(type != 'assets' && type != 'asset') { + var endpoint = [ this._client.URI, this._client.orgName, this._client.appName, type, self.get("uuid") ].join("/"); + } else { + self.set("content-type", file.type); + self.set("size", file.size); + var endpoint = [ this._client.URI, this._client.orgName, this._client.appName, "assets", self.get("uuid"), "data" ].join("/"); + } + + var xhr = new XMLHttpRequest(); + xhr.open("POST", endpoint, true); + xhr.onerror = function(err) { + doCallback(callback, [ new UsergridError("The File APIs are not fully supported by your browser.") ], xhr, self); + }; + xhr.onload = function(ev) { + if (xhr.status >= 500 && attempts > 0) { + self.set("attempts", --attempts); + setTimeout(function() { + self.attachAsset.apply(self, args); + }, 100); + } else if (xhr.status >= 300) { + self.set("attempts"); + doCallback(callback, [ new UsergridError(JSON.parse(xhr.responseText)), xhr, self ], self); + } else { + self.set("attempts"); + self.fetch(); + doCallback(callback, [ null, xhr, self ], self); + } + }; + var fr = new FileReader(); + fr.onload = function() { + var binary = fr.result; + if (type === 'assets' || type === 'asset') { + xhr.overrideMimeType("application/octet-stream"); + xhr.setRequestHeader("Content-Type", "application/octet-stream"); + } + xhr.sendAsBinary(binary); + }; + fr.readAsBinaryString(file); + +}; + +/* + * Upload Asset data + * + * @method upload + * @public + * @param {object} data Can be a javascript Blob or File object + * @returns {callback} callback(err, asset) + */ +Usergrid.Asset.prototype.upload = function(data, callback) { + this.attachAsset(data, function(err, response) { + if(!err){ + doCallback(callback, [ null, response, self ], self); + } else { + doCallback(callback, [ new UsergridError(err), response, self ], self); + } + }); +}; + +/* + * Download Asset data + * + * @method download + * @public + * @returns {callback} callback(err, blob) blob is a javascript Blob object. + */ +Usergrid.Entity.prototype.downloadAsset = function(callback) { + var self = this; + var endpoint; + var type = this._data.type; + + var xhr = new XMLHttpRequest(); + if(type != "assets" && type != 'asset') { + endpoint = [ this._client.URI, this._client.orgName, this._client.appName, type, self.get("uuid") ].join("/"); + } else { + endpoint = [ this._client.URI, this._client.orgName, this._client.appName, "assets", self.get("uuid"), "data" ].join("/"); + } + xhr.open("GET", endpoint, true); + xhr.responseType = "blob"; + xhr.onload = function(ev) { + var blob = xhr.response; + if(type != "assets" && type != 'asset') { + doCallback(callback, [ null, blob, xhr ], self); + } else { + doCallback(callback, [ null, xhr, self ], self); + } + }; + xhr.onerror = function(err) { + callback(true, err); + doCallback(callback, [ new UsergridError(err), xhr, self ], self); + }; + + if(type != "assets" && type != 'asset') { + xhr.setRequestHeader("Accept", self._data["file-metadata"]["content-type"]); + } else { + xhr.overrideMimeType(self.get("content-type")); + } + xhr.send(); +}; + +/* + * Download Asset data + * + * @method download + * @public + * @returns {callback} callback(err, blob) blob is a javascript Blob object. + */ +Usergrid.Asset.prototype.download = function(callback) { + this.downloadAsset(function(err, response) { + if(!err){ + doCallback(callback, [ null, response, self ], self); + } else { + doCallback(callback, [ new UsergridError(err), response, self ], self); + } + }); +}; \ No newline at end of file
