diff --git a/lib/Parse.js b/lib/Parse.js index a384836..125d002 100644 --- a/lib/Parse.js +++ b/lib/Parse.js @@ -5,7 +5,7 @@ qs.escape = function(q){ return q; }; module.exports = Parse; function Parse(options_or_application_id, master_key) { - this._options = {} + this._options = {}; if (master_key) { this._options.app_id = options_or_application_id; this._options.master_key = master_key; @@ -29,39 +29,62 @@ Parse.prototype = { parseRequest.call(this, 'POST', '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/1/files/' + fileName, data, callback, contentType); }, + // create a new user + insertUser: function (data, callback) { + parseRequest.call(this, 'POST', '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/1/users/', data, callback); + }, + // get an object from the class store find: function (className, query, callback) { - if (typeof query === 'string') { - parseRequest.call(this, 'GET', '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/1/classes/' + className + '/' + query, null, callback); - } else { - parseRequest.call(this, 'GET', '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/1/classes/' + className, { where: JSON.stringify(query) }, callback); + var url = '/1/' + (className === '_User' ? 'users' : 'classes/' + className); + var queryType = typeof query; + if ( queryType === 'string' ) { + url += '/' + query; + } else if ( queryType === 'object' ) { + + // if the user wants to add 'include' or 'key' (or other types of) constraints while getting only one object + // objectId can be added to the query object and is deleted after it's appended to the url + if ( query.hasOwnProperty('objectId') ) { + url += '/' + query.objectId; + delete query.objectId; + } + + // check to see if there is a 'where' object in the query object + // the 'where' object need to be stringified by JSON.stringify(), not querystring + if ( query.hasOwnProperty('where') ) { + url += '?where=' + JSON.stringify(query.where); + delete query.where; + } + + // if there are no more constraints left in the query object 'remainingQuery' will be an empty string + var remainingQuery = qs.stringify(query); + if ( remainingQuery ) { + url += ( url.indexOf('?') === -1 ? '?' : '&' ) + remainingQuery; + } + } + parseRequest.call(this, 'GET', url, null, callback); }, // get a collection of objects findMany: function (className, query, order, limit, skip, callback) { - // also supports the following api - findMany: function (className, query, callback) - - // if only 3 arguments exist, third argument is the callback, order and limit are undefined - if (arguments.length === 3) { - var callback = order; - var order = undefined; - var limit = 9999; + console.warn('"findMany" is deprecated, use "find" instead.'); + + switch ( arguments.length ) { + case 3: callback = order; + break; + case 5: query.order = order; + query.limit = limit; + callback = skip; + break; + case 6: query.order = order; + query.limit = limit; + query.skip = skip; + break; + default: throw new Error('Unexpected number of arguments.'); } - // if only 5 arguments exist, fifth argument is the callback, skip is undefined - if (arguments.length === 5) { - var callback = skip; - skip = undefined; - } - - if (typeof(query) === 'string') { - parseRequest.call(this, 'GET', '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/1/classes/' + className + '/' + query, null, callback); - } else if (skip !== undefined) { - parseRequest.call(this, 'GET', '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/1/classes/' + className, { limit: limit, order:order, skip:skip, where: JSON.stringify(query) }, callback); - } else { - parseRequest.call(this, 'GET', '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/1/classes/' + className, { limit: limit, order:order, where: JSON.stringify(query) }, callback); - } + this.find(className, query, callback); }, // do a batch of requests at once @@ -69,16 +92,46 @@ Parse.prototype = { parseRequest.call(this,'POST','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/1/batch',{requests:requests},callback); }, - // get a user from the Parse's special User class. See https://parse.com/questions/why-does-querying-for-a-user-create-a-second-user-class - getUser: function (userName, passWord, callback) { - parseRequest.call(this, 'GET', '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/1/login/?username=' + userName + '&password=' + passWord, null, callback); + // user login + loginUser: function (username, password, callback) { + parseRequest.call(this, 'GET', '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/1/login/?username=' + username + '&password=' + password, null, callback); + }, + + // retrieve current user + me: function (sessionToken, callback) { + parseRequest.call(this, 'GET', '/1/users/me', null, callback, null, sessionToken); + }, + + // retrieve contents of one or more user objects + findUser: function (query, callback) { + if ( arguments.length !== 2 ) { + throw new Error('Unexpected number of arguments.'); + } else { + this.find('_User', query, callback); + } }, // get an object belonging to a certain User - getFileByUser: function(userId, className, callback) { - queryString = 'where={"user":' + '"' + userId + '"' + '}' + findFileByUser: function(userId, className, callback) { + queryString = 'where={"user":' + '"' + userId + '"' + '}'; encodedString = encodeURIComponent(queryString); - parseRequest.call(this, 'GET', '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/1/classes/' + className + '?' + encodedString, null, callback) + parseRequest.call(this, 'GET', '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/1/classes/' + className + '?' + encodedString, null, callback); + }, + + getUser: function (query, callback, deprecatedCallback) { + console.warn('getUser is deprecated, user findUser instead.'); + if ( arguments.length === 3 ) { + console.warn('Logging in with "getUser" is deprecated, use loginUser instead.'); + console.warn('Use "findUser" to retrieve one or more user object contents.'); + this.loginUser(query, callback, deprecatedCallback); + } else { + this.findUser(query, callback); + } + }, + + getFileByUser: function(userId, className, callback) { + console.warn('getFileByUser is deprecated, user findFileByUser instead.'); + this.findFileByUser(userId, className, callback); }, // insert an object into Parse @@ -92,21 +145,44 @@ Parse.prototype = { }, // update a User object's email address - updateUserEmail: function(objectId, data, callback) { - data = { email: data } - parseRequest.call(this, 'PUT', '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/1/users/' + objectId, data, callback) + updateUserEmail: function(objectId, data, sessionToken, callback) { + switch ( arguments.length ) { + case 3: callback = sessionToken; + break; + case 4: break; + default: throw new Error('Unexpected number of arguments.'); + } + data = { email: data }; + parseRequest.call(this, 'PUT', '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/1/users/' + objectId, data, callback, null, sessionToken); }, // update a User object's username* - updateUserName: function(objectId, data, callback) { - data = { username: data } - parseRequest.call(this, 'PUT', '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/1/users/' + objectId, data, callback) + updateUserName: function(objectId, data, sessionToken, callback) { + switch ( arguments.length ) { + case 3: callback = sessionToken; + break; + case 4: break; + default: throw new Error('Unexpected number of arguments.'); + } + data = { username: data }; + parseRequest.call(this, 'PUT', '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/1/users/' + objectId, data, callback, null, sessionToken); + }, + + // update any keys of a User object + updateUser: function (objectId, data, sessionToken, callback) { + switch ( arguments.length ) { + case 3: callback = sessionToken; + break; + case 4: break; + default: throw new Error('Unexpected number of arguments.'); + } + parseRequest.call(this, 'PUT', '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/1/users/' + objectId, data, callback, null, sessionToken); }, // reset a User object's password passwordReset: function (data, callback) { - data = { email: data } - parseRequest.call(this, 'POST', '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/1/requestPasswordReset/', data, callback) + data = { email: data }; + parseRequest.call(this, 'POST', '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/1/requestPasswordReset/', data, callback); }, // remove an object from the class store @@ -114,43 +190,70 @@ Parse.prototype = { parseRequest.call(this, 'DELETE', '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/1/classes/' + className + '/' + objectId, null, callback); }, + // remove an object from the class store + deleteUser: function (objectId, sessionToken, callback) { + switch ( arguments.length ) { + case 2: callback = sessionToken; + break; + case 3: break; + default: throw new Error('Unexpected number of arguments'); + } + parseRequest.call(this, 'DELETE', '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/1/users/' + objectId, null, callback, null, sessionToken); + }, + + deleteAll: function(className, callback){ + var that = this; + this.find(className, '', function (err, response) { + var requests = toDeleteOps(className, response.results); + that.batch(requests, callback); + }); + }, + + deleteAllUsers: function(callback){ + var that = this; + this.find('_User', '', function (err, response) { + var requests = toDeleteOps('_User', response.results); + that.batch(requests, callback); + }); + }, + // upload installation data insertInstallationData: function (deviceType, deviceToken, callback) { if (deviceType === 'ios'){ - data = { deviceType: deviceType, deviceToken: deviceToken } + data = { deviceType: deviceType, deviceToken: deviceToken }; } else { - data = { deviceType: deviceType, installationId: deviceToken } + data = { deviceType: deviceType, installationId: deviceToken }; } parseRequest.call(this, 'POST', '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/1/installations/', data, callback); }, insertInstallationDataWithTimeZone: function (deviceType, deviceToken, timeZone, callback) { if (deviceType === 'ios'){ - data = { deviceType: deviceType, deviceToken: deviceToken, timeZone: timeZone } + data = { deviceType: deviceType, deviceToken: deviceToken, timeZone: timeZone }; } else { - data = { deviceType: deviceType, installationId: deviceToken, timeZone: timeZone } + data = { deviceType: deviceType, installationId: deviceToken, timeZone: timeZone }; } parseRequest.call(this, 'POST', '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/1/installations/', data, callback); }, insertInstallationDataWithChannels: function (deviceType, deviceToken, channels, callback) { if (deviceType === 'ios'){ - data = { deviceType: deviceType, deviceToken: deviceToken, channels: channels } + data = { deviceType: deviceType, deviceToken: deviceToken, channels: channels }; } else { - data = { deviceType: deviceType, installationId: deviceToken, channels: channels } + data = { deviceType: deviceType, installationId: deviceToken, channels: channels }; } parseRequest.call(this, 'POST', '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/1/installations/', data, callback); }, insertInstallationDataWithTimeZoneAndChannels: function (deviceType, deviceToken, timeZone, channels, callback) { if (deviceType === 'ios'){ - data = { deviceType: deviceType, deviceToken: deviceToken, timeZone: timeZone, channels: channels } + data = { deviceType: deviceType, deviceToken: deviceToken, timeZone: timeZone, channels: channels }; } else { - data = { deviceType: deviceType, installationId: deviceToken, timeZone: timeZone, channels: channels } + data = { deviceType: deviceType, installationId: deviceToken, timeZone: timeZone, channels: channels }; } parseRequest.call(this, 'POST', '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/1/installations/', data, callback); }, @@ -192,13 +295,13 @@ Parse.prototype = { }, addRelation: function( relationName, className1, objectId1, className2, objectId2, callback) { - data = {} + data = {}; data[relationName] = { __op:"AddRelation",objects:[{__type:"Pointer",className:className2,objectId:objectId2}]}; parseRequest.call(this,'PUT','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/1/classes/' + className1+'/'+objectId1,data,callback); }, removeRelation: function( relationName, className1, objectId1, className2, objectId2, callback) { - data = {} + data = {}; data[relationName] = { __op:"RemoveRelation",objects:[{__type:"Pointer",className:className2,objectId:objectId2}]}; parseRequest.call(this,'PUT','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/1/classes/' + className1+'/'+objectId1,data,callback); }, @@ -234,19 +337,11 @@ Parse.prototype = { sendPush: function (data, callback) { parseRequest.call(this, 'POST', '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/1/push/', data, callback); - }, - - deleteAll: function(modelName, callback){ - var that = this; - this.findMany(modelName, '', function(err, response){ - var requests = toDeleteOps(modelName, response.results); - that.batch(requests, callback); - }); } }; // Parse.com https api request -function parseRequest(method, path, data, callback, contentType) { +function parseRequest(method, path, data, callback, contentType, sessionToken) { var headers = { Connection: 'Keep-alive' }; @@ -254,9 +349,15 @@ function parseRequest(method, path, data, callback, contentType) { if(this._options.master_key){ var auth = 'Basic ' + new Buffer(this._options.app_id + ':' + this._options.master_key).toString('base64'); headers.Authorization = auth; + if ( sessionToken ) { + throw new Error('Can\'t use session tokens while using the master key.'); + } }else if(this._options.api_key){ headers['X-Parse-Application-Id'] = this._options.app_id; headers['X-Parse-REST-API-Key'] = this._options.api_key; + if ( sessionToken ) { + headers['X-Parse-Session-Token'] = sessionToken; + } } @@ -269,11 +370,11 @@ function parseRequest(method, path, data, callback, contentType) { } break; case 'POST': - body = typeof data === 'object' ? JSON.stringify(data) : data; - headers['Content-length'] = Buffer.byteLength(body); case 'PUT': - body = typeof data === 'object' ? JSON.stringify(data) : data; - headers['Content-length'] = Buffer.byteLength(body); + body = contentType ? data : typeof data === 'object' ? JSON.stringify(data) : data; + if ( !contentType ) { + headers['Content-length'] = Buffer.byteLength(body); + } headers['Content-type'] = contentType || 'application/json'; break; case 'DELETE': @@ -296,19 +397,6 @@ function parseRequest(method, path, data, callback, contentType) { return; } - if (res.statusCode < 200 || res.statusCode >= 300) { - var err = new Error('HTTP error ' + res.statusCode); - err.arguments = arguments; - err.type = res.statusCode; - err.options = options; - err.body = body; - return callback(err); - } - - // if ((!err) && (res.statusCode === 200 || res.statusCode === 201)) { - // res.success = res.statusCode; - // } - var json = ''; res.setEncoding('utf8'); @@ -317,13 +405,17 @@ function parseRequest(method, path, data, callback, contentType) { }); res.on('end', function () { - var err = null; - var data = null; + var data; try { - var data = JSON.parse(json); - } catch (err) { + data = JSON.parse(json); + if ( data.code || data.error ) { + throw (data); + } + callback(null, data); + } + catch (err) { + callback(err); } - callback(err, data); }); res.on('close', function (err) { @@ -343,8 +435,7 @@ function toDeleteOps(className, objects){ return objects.map(function(object){ return { method: 'DELETE', - path: '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/1/classes/' + className + '/' + object.objectId, - body: {} - } + path: '/1/' + ( className === '_User' ? 'users/' : 'classes/' + className + '/' ) + object.objectId + }; }); } diff --git a/package.json b/package.json index f665eb5..1dcfd03 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,22 @@ "author": "Chris Johnson , Michael Leveton , Seth Gholson", "contributors": [ "Daniel Gasienica ", - "Jakub Knejzlík" + "Jakub Knejzlík", + "Sam Saccone", + "Arnaud Rinquin", + "Charles Julian Knight", + "Barrington Haynes", + "Christian Monaghan", + "Christoffer Niska", + "Rodrigo Martell", + "Joe Bruggeman", + "Omar A", + "Andrey", + "Rafael Galdêncio", + "Akhmad Fathonih", + "Kody J. Peterson", + "Jo Jordens", + "Appsaloon" ], "main": "index", "scripts": { diff --git a/readme.md b/readme.md index d670d22..df2d79f 100644 --- a/readme.md +++ b/readme.md @@ -4,274 +4,526 @@ Node Parse API install ------- - npm install node-parse-api - +``` +npm install node-parse-api +``` + examples -------- -### setup with MASTER_KEY (old way) +### setup with MASTER_KEY - var Parse = require('node-parse-api').Parse; +```javascript +var Parse = require('node-parse-api').Parse; - var APP_ID = ...; - var MASTER_KEY = ...; +var APP_ID = ...; +var MASTER_KEY = ...; - var app = new Parse(APP_ID, MASTER_KEY); +var app = new Parse(APP_ID, MASTER_KEY); +``` -### setup with API_KEY (new way) +### setup with API_KEY - var Parse = require('node-parse-api').Parse; +```javascript +var Parse = require('node-parse-api').Parse; - var options = { - app_id:'...', - api_key:'...' // master_key:'...' could be used too - } +var options = { + app_id:'...', + api_key:'...' // master_key:'...' could be used too +} - var app = new Parse(options); +var app = new Parse(options); +``` ### insert an object - // add a Foo object, { foo: 'bar' } - app.insert('Foo', { foo: 'bar' }, function (err, response) { - console.log(response); - }); +* insert(className `string`, data `object`, callback `function`) + +```javascript +// add a Foo object, { foo: 'bar' } +app.insert('Foo', { foo: 'bar' }, function (err, response) { + console.log(response); +}); +``` ### insert a User - app.insertCustom('users', { - username: 'tom@j.com', - password: 'wow' - }, function (err, response) { - console.log(response); - }); +* insertUser(data `object`, callback `function`) + +```javascript +app.insertUser({ + username: 'foo', + password: 'bar' +}, function (err, response) { + console.log(response); +}); +``` + +More properties can be provided, but username and password are required. + +### insert a User with a Pointer + +```javascript +app.insertUser({ + username: 'foo', + password: 'bar', + pointer/*can have any name*/: { + __type: 'Pointer', + className: , + objectId: + } + }, function (err, response) { + console.log(response); +}); +``` ### insert a User with GeoPoints - app.insertCustom('users', { foo: 'bar', location: {__type: 'GeoPoint', latitude: , longitude: } }, function (err, response) { - console.log(response); - }); +```javascript +app.insertUser({ + username: 'foo', + password: 'bar', + location: { + __type: 'GeoPoint', + latitude: , + longitude: + } + }, function (err, response) { + console.log(response); +}); +``` + +### user login + +* loginUser(username `string`, password `string`, callback `function`) + +Response contains all of the user fields except password, also includes a sessionToken for this user. + +```javascript +app.loginUser('foo', 'bar', function (error, response) { + // response = {sessionToken: '', createdAt: '', ... } + }); +``` + +### me + +* me(sessionToken `string`, callback `function`) + +```javascript +app.me('sessionToken', function (error, response) { + // response is same as getUser response +}); +``` ### insert a file - app.insertFile(fileName, data, fileType, function (err, response) { - fileLink = response.url; - parseName = response.name; - app.insert('Foo', { "foo" : fileLink, "bar" : parseName }, function(erro, res){ - }) - }); +* insertFile(fileName `string`, data `string/buffer`, contentType `string`, callback `function`) + +```javascript +// first upload the file to the parse cloud +app.insertFile('foo.txt', 'bar', 'text/plain', function (err, response) { + // then insert a new object with the link to the new file + app.insert('MyFile', {__type: 'File', "name": response.name }, function (error, response) { + }); +}); +``` ### find one - // the Foo with id = 'someId' - app.find('Foo', 'someId', function (err, response) { - console.log(response); - }); +* find(className `string`, query `object`, callback `function`) -### find many +```javascript +// the Foo with id = 'someId' +app.find('Foo', {objectId: 'someId'}, function (err, response) { + console.log(response); +}); +``` - // all Foo objects with foo = 'bar' - app.findMany('Foo', { foo: 'bar' }, function (err, response) { - console.log(response); - }); +Returned fields can be restricted with the 'keys' query. +```javascript +var query = { + objectId: 'someId', + keys: 'foo,bar' +}; +app.find('Foo', query, function (error, response) { + //response object will only contain foo and bar fields, as well as the special built-in fields (objectId, createdAt and updatedAt) + }); +``` - // all Foo objects - app.findMany('Foo', '', function (err, response) { - console.log(response); - }): +### find many + +* find(className `string`, query `object`, callback `function`) + +```javascript +// all Foo objects with foo = 'bar' +app.find('Foo', {where: {foo: 'bar'}}, function (err, response) { + console.log(response); +}); + +// all Foo objects +// '', null, undefined or any other falsy value will work +app.find('Foo', '', function (err, response) { + console.log(response); +}): +``` + +All types of query constraints Parse provides can be added to the query object as properties. (order, limit, keys, count, include...) + +```javascript +var query = { + where: { + foo: 'bar', + baz: 'qux' + }, + limit: 10, + skip: 5, + order: '-createdAt' +}; +app.find('Foo', query, function (error, response ) { + // the first 5 results will be ignored and the next 10 results will be returned + // response.results will contain up to 10 objects with foo = 'bar' and baz = 'qux', sorted from latest to oldest + }); +``` + +### find one user + +* getUser(query `object`, callback `function`) + +```javascript +app.find({objectId: 'someId'}, function (err, response) { + console.log(response); +}); +``` + +### find many users + +* getUser(query `object`, callback `function`) + +```javascript +// all users with foo = 'bar' +app.find({where: {foo: 'bar'}}, function (err, response) { + console.log(response); +}); + +// all users +// '', null, undefined or any other falsy value will work +app.find('', function (err, response) { + console.log(response); +}): +``` ### count the number of objects - // just use findMany, and call results.length on the response - app.findMany('Foo', { user: '' }, function (err, response) { - console.log(response.results.length); - }); +```javascript + +var query = { + count: 1, + limit: 0 +}; +app.find('Foo', query, function (error, response) { + // { + // results: [], + // count: 123 + // } +}); +``` + +### edit an object + +* update(className `string`, objectId `string`, callback `function`) + +```javascript +app.update('Foo', 'someId', {foo: 'bar'}, function (err, response) { + console.log(response); +}); +``` + +### delete an object + +* delete(className `string`, objectId `string`, callback `function`) + +```javascript +app.delete('Foo', 'someId', function (err, response) { + // response = {} +}); +``` + +### delete all objects in a class + +* deleteAll(className `string`, callback `function`) + +```javascript +app.deleteAll('Foo', function (err, response) { + // response = [{success: {}, success: {}, ... }] +}); +``` + +### delete user -### update +* deleteUser(objectId `string`, [sessionToken `string`], callback `function`) - app.update('Foo', 'someId', { foo: 'fubar' }, function (err, response) { - console.log(response); - }); +If you are using the master key you don't need any session tokens. -### delete +```javascript +app.deleteUser('someId', function (err, response) { + // response = {} +}); +``` - app.delete('Foo', 'someId', function (err) { - // nothing to see here - }); +If you're using the rest api key you will need a session token and will only be able to delete the user object of the matching user. -### deleteAll +```javascript +app.deleteUser('someId', 'sessionToken', function (error, response) { + // response = {} +}); +``` - app.deleteAll('Foo', function (err) { - // nothing to see here - }); +### delete all users + +* deleteAllUsers(callback `function`) + +This will only work when using the master key. + +```javascript +app.deleteAllUsers(function (err, response) { + // response = [{success: {}, success: {}, ... }] +}); +``` ### reset a password - //email is built into Parse's special User class - app.passwordReset(email, function(err, response){ - console.log(response); - }); +* passwordReset(data `string`, callback `function`) -### update User email +```javascript +//email is built into Parse's special User class +app.passwordReset(email, function(err, response){ + console.log(response); +}); +``` - //email is built into Parse's special User class - app.updateUserEmail(objectId, email, function(err, response){ - if (err) { - console.log(err); - } else { - console.log(response); - } - }); +### edit a user object + +* updateUser(objectId `string`, data `object`, [sessionToken `string`], callback `function`) + +With master key + +```javascript +app.updateUser('someId', {email: 'foo@example.com'}, function(err, response){ + console.log(response); +}); +``` + +or with rest api key + +```javascript +app.updateUser('someId', {email: 'foo@example.com'}, 'sesstionToken', function(err, response){ + console.log(response); +}); +``` + +### batch requests + +* batch(requests `array`, callback `function`) + +```javascript +var requests = [ + { + method: 'POST', + path: '/1/classes/Foo', + body: { + foo: 'bar1', + baz: 'qux1' + } + }, + { + method: 'POST', + path: '/1/classes/Foo', + body: { + foo: 'bar2', + baz: 'qux2' + } + } +]; +app.batch(requests, function (error, response) { + // response = [{success: {createdAt: '', objectId: ''}, {success: {...}}}] +}); +``` ### insert installation data - //first arg is either 'ios' or 'android'. second arg is either the Apple deviceToken or the Android installationId. - app.insertInstallationData("ios", "0123456784abcdef0123456789abcdef0123456789abcdef0123456789abcdef", function(err, response){ - if (err) { - console.log(err); - } else { - console.log(response); - } - }); +```javascript +//first arg is either 'ios' or 'android'. second arg is either the Apple deviceToken or the Android installationId. +app.insertInstallationData("ios", "0123456784abcdef0123456789abcdef0123456789abcdef0123456789abcdef", function(err, response){ + if (err) { + console.log(err); + } else { + console.log(response); + } +}); +``` ### insert installation data with timeZone - //first arg is either 'ios' or 'android'. second arg is either the Apple deviceToken or the Android installationId. Third arg is the timezone string. - app.insertInstallationDataWithTimeZone("ios", "0123456784abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "EST", function(err, response){ - if (err) { - console.log(err); - } else { - console.log(response); - } - }); +```javascript +//first arg is either 'ios' or 'android'. second arg is either the Apple deviceToken or the Android installationId. Third arg is the timezone string. +app.insertInstallationDataWithTimeZone("ios", "0123456784abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "EST", function(err, response){ + if (err) { + console.log(err); + } else { + console.log(response); + } +}); +``` ### insert installation data with channels - //first arg is either 'ios' or 'android'. second arg is either the Apple deviceToken or the Android installationId. Third arg is the channels array. - arr = ["news", "sports"]; - app.insertInstallationDataWithChannels("ios", "0123456784abcdef0123456789abcdef0123456789abcdef0123456789abcdef", arr, function(err, response){ - if (err) { - console.log(err); - } else { - console.log(response); - } - }); +```javascript +//first arg is either 'ios' or 'android'. second arg is either the Apple deviceToken or the Android installationId. Third arg is the channels array. +arr = ["news", "sports"]; +app.insertInstallationDataWithChannels("ios", "0123456784abcdef0123456789abcdef0123456789abcdef0123456789abcdef", arr, function(err, response){ + if (err) { + console.log(err); + } else { + console.log(response); + } +}); +``` ### insert installation data with timeZone and channels - //first arg is either 'ios' or 'android'. second arg is either the Apple deviceToken or the Android installationId. Third arg is the timezone string. 4th is the channels array. - arr = ["news", "sports"]; - app.insertInstallationDataWithTimeZoneAndChannels("ios", "0123456784abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "EST", arr, function(err, response){ - if (err) { - console.log(err); - } else { - console.log(response); - } - }); +```javascript +//first arg is either 'ios' or 'android'. second arg is either the Apple deviceToken or the Android installationId. Third arg is the timezone string. 4th is the channels array. +arr = ["news", "sports"]; +app.insertInstallationDataWithTimeZoneAndChannels("ios", "0123456784abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "EST", arr, function(err, response){ + if (err) { + console.log(err); + } else { + console.log(response); + } +}); +``` ### create a role for a particular user - //create a data object that links the user object's objectId to the role - - var data = { - name: 'Administrator', - ACL: { - "*": { - "read": true - } - }, - roles: { - "__op": "AddRelation", - "objects": [ - { - "__type": "Pointer", - "className": "_Role", - "objectId": "" - } - ] - }, - users: { - "__op": "AddRelation", - "objects": [ - { - "__type": "Pointer", - "className": "_User", - "objectId": "" - } - ] +```javascript +//create a data object that links the user object's objectId to the role + +var data = { + name: 'Administrator', + ACL: { + "*": { + "read": true + } + }, + roles: { + "__op": "AddRelation", + "objects": [ + { + "__type": "Pointer", + "className": "_Role", + "objectId": "" } - }; + ] + }, + users: { + "__op": "AddRelation", + "objects": [ + { + "__type": "Pointer", + "className": "_User", + "objectId": "" + } + ] + } +}; - app.insertRole(data, function(err, resp){ - console.log(resp); - }); + app.insertRole(data, function(err, resp){ + console.log(resp); + }); +``` ### get a role - //pass the role object's objectId - app.getRole("", function(err, resp){ - console.log(resp); - }); +```javascript +//pass the role object's objectId +app.getRole("", function(err, resp){ + console.log(resp); +}); +``` ### update a role - //pass the objectId of the role, data contains the user's objectId - - var data = { - users: { - "__op": "RemoveRelation", - "objects": [ - { - "__type": "Pointer", - "className": "_User", - "objectId": "" - } - ] + +```javascript +//pass the objectId of the role, data contains the user's objectId + +var data = { + users: { + "__op": "RemoveRelation", + "objects": [ + { + "__type": "Pointer", + "className": "_User", + "objectId": "" } - }; + ] + } +}; - app.updateRole("", data, function(err, resp){ - console.log(resp); - }); + app.updateRole("", data, function(err, resp){ + console.log(resp); + }); +``` ### delete a role - //pass the objectId of the role - app.deleteRole("", function(err, resp){}); +```javascript +//pass the objectId of the role +app.deleteRole("", function(err, resp){}); +``` ### get all the roles - app.getRoles(function(err, resp){}); +```javascript +app.getRoles(function(err, resp){}); +``` ### get a role against a cetain param - var params = { - where: { name: "Administrator" } - }; +```javascript +var params = { + where: { name: "Administrator" } +}; - app.getRoles(params, function(err, resp){ - console.log(resp); - }); + app.getRoles(params, function(err, resp){ + console.log(resp); + }); +``` ### send a push notification - //The data param has to follow the data structure as described in the [Parse REST API](https://www.parse.com/docs/rest#push) - var notification = { - channels: [''], - data: { - alert: "sending too many push notifications is obnoxious" - } - }; - app.sendPush(notification, function(err, resp){ - console.log(resp); - }); +```javascript +//The data param has to follow the data structure as described in the [Parse REST API](https://www.parse.com/docs/rest#push) +var notification = { + channels: [''], + data: { + alert: "sending too many push notifications is obnoxious" + } +}; +app.sendPush(notification, function(err, resp){ + console.log(resp); +}); +``` ### note on sending dates - //when inserting a data, you must use the Parse date object structure, i.e.: - { - "__type": "Date", - "iso": new Date("", "", "").toJSON() - } +```javascript +//when inserting a data, you must use the Parse date object structure, i.e.: +{ + "__type": "Date", + "iso": new Date("", "", "").toJSON() +} +``` # License @@ -283,4 +535,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of 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. \ No newline at end of file +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. diff --git a/test/Parse.test.js b/test/Parse.test.js index e9c8ee1..342f61c 100644 --- a/test/Parse.test.js +++ b/test/Parse.test.js @@ -1,3 +1,4 @@ +// this file runs tests against the master key var Parse = require('../index').Parse; // use environment variables APPLICATION_ID and MASTER_KEY to test against @@ -16,126 +17,262 @@ if (!application_id || !master_key) { var parse = new Parse(application_id, master_key); var className = 'NodeParseApiTest'; var className2 = 'NodeParseApiRelationTest'; -var object = { foo: Math.floor(Math.random() * 10000) }; // ERROR: if you change the type -var object2 = { foo: Math.floor(Math.random() * 10000) }; // ERROR: if you change the type -var object3 = { foo: Math.floor(Math.random() * 10000) }; // ERROR: if you change the type -var object4 = { foo: Math.floor(Math.random() * 10000) }; // ERROR: if you change the type -var stub; +var stub1; var stub2; +var stub3; +var stubRelation; -exports.insert = function (assert) { - parse.insert(className, object, function (err, response) { - err && console.log(err); - assert.ok(response); - stub = response; - assert.done(); +exports.insert = function (test) { + var data = { + foo: 'bar0', + baz: 'qux0', + quux: 'quuux' + }; + parse.insert(className, data, function (error, response) { + test.expect(1); + test.ok(!error, 'There shoudn\'t be an error object.'); + stub1 = response; + test.done(); }); - - }; -exports['insert 2'] = function(assert){ - parse.insert(className2,object2, function(err,response){ - err && console.log(err); - assert.ok(response); - stub2 = response; - assert.done(); - }); -} - - -exports.find = function (assert) { - parse.find(className, stub.objectId, function (err, response) { - assert.equal(object.foo, response.foo); - assert.done(); +exports.batchInsert = function (test) { + var batchRequests = [ + { + method: 'POST', + path: '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/1/classes/' + className, + body: { + foo: 'bar1', + baz: 'qux1', + quux: 'quuux' + } + }, + { + method: 'POST', + path: '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/1/classes/' + className, + body: { + foo: 'bar2', + baz: 'qux2', + quux: 'quuux' + } + } + ]; + parse.batch(batchRequests, function (error, response) { + test.expect(1); + test.ok(!error, 'There shoudn\'t be an error object.'); + stub2 = response[0].success; + stub3 = response[1].success; + test.done(); }); }; -exports['find with limit and order'] = function (assert) { - parse.findMany(className,{},'-createdAt',1, function (err1,res1){ - assert.equal(1, res1.results.length); - assert.equal(stub.objectId, res1.results[0].objectId); - assert.equal(stub.createdAt, res1.results[0].createdAt); - assert.done(); +exports.find = function (test) { + parse.find(className, stub1.objectId, function (err, response) { + test.equal(stub1.objectId, response.objectId); + test.done(); }); -} +}; -exports['find many'] = function (assert) { - parse.find(className, stub, function (err, response) { - assert.equal(1, response.results.length); - assert.equal(stub.objectId, response.results[0].objectId); - assert.equal(stub.createdAt, response.results[0].createdAt); - assert.equal(object.foo, response.results[0].foo); - assert.done(); +exports.findManyNoConstraints = function (test) { + parse.find(className, '', function (error, response) { + test.expect(3); + test.ok(!error, 'There shoudn\'t be an error object.'); + test.ok(response.results.length === 3, 'There should be 3 objects in response.results.'); + test.equal(stub1.objectId, response.results[0].objectId, 'The first object should have the same objectId as the stub object.'); + test.done(); }); }; +// order limit skip keys include +exports.findManyWithConstraints = { + order: function (test) { + var query = { + order: '-foo' + }; + parse.find(className, query, function (error, response) { + test.expect(4); + test.ok(!error, 'There shoudn\'t be an error object.'); + test.equal('bar0', response.results[2].foo, 'response.results[2].foo should be "bar0".'); + test.equal('bar1', response.results[1].foo, 'response.results[1].foo should be "bar1".'); + test.equal('bar2', response.results[0].foo, 'response.results[0].foo should be "bar2".'); + test.done(); + }); + }, + 'order keys skip': function (test) { + var query = { + order: 'foo', + keys: 'baz', + skip: 2 + }; + parse.find(className, query, function (error, response) { + test.ok(!error, 'There shoudn\'t be an error object.'); + test.equal('qux2', response.results[0].baz, 'response.results[0].baz should be "qux2".'); + test.done(); + }); + }, + 'order limit': function (test) { + var query = { + order: '-foo', + limit: 2 + }; + parse.find(className, query, function (error, response) { + test.expect(4); + test.ok(!error, 'There shoudn\'t be an error object.'); + test.ok(response.results.length === 2, 'There should be 2 objects in response.results.'); + test.equal('bar1', response.results[1].foo, 'response.results[1].foo should be "bar1".'); + test.equal('qux1', response.results[1].baz, 'response.results[1].baz should be "qux1".'); + test.done(); + }); + } +}; +exports.deprecatedFindMany = { + setUp: function (callback) { + this.query = { + quux: 'quuux' + }; + callback(); + }, + '3 arguments': function (test) { + parse.findMany(className, this.query, function (error, response) { + test.expect(2); + test.ok(!error, 'There shoudn\'t be an error object.'); + test.ok(response.results.length === 3, 'There should be 3 objects in response.results.'); + test.done(); + }); + }, + '5 arguments': function (test) { + parse.findMany(className, this.query, 'foo', 2, function (error, response) { + test.expect(3); + test.ok(!error, 'There shoudn\'t be an error object.'); + test.ok(response.results.length === 2, 'There should be 2 objects in response.results.'); + test.equal('bar0', response.results[0].foo, 'response.results[0].foo should be "bar0".'); + test.done(); + }); + }, + '6 arguments': function (test) { + parse.findMany(className, this.query, 'foo', 2, 1, function (error, response) { + test.expect(3); + test.ok(!error, 'There shoudn\'t be an error object.'); + test.ok(response.results.length === 2, 'There should be 2 objects in response.results.'); + test.equal('bar1', response.results[0].foo, 'response.results[0].foo should be "bar1".'); + test.done(); + }); + }, + 'invalid number of arguments': function (test) { + test.expect(1); + test.throws(function () {parse.findMany('foo', 'bar', 'baz', 'qux');}); + test.done(); + } +}; - -exports.update = function (assert) { - do { - var num = Math.floor(Math.random() * 10000); - } while (num == object.foo); - object.foo = num; - - parse.update(className, stub.objectId, object, function (err, response) { - err && console.log(err); - assert.ok(response); - exports.find(assert); // retest find on the updated object +exports.update = function (test) { + stub1.foo = 'bar00'; + parse.update(className, stub1.objectId, {foo: 'bar00'}, function (error, response) { + test.expect(4); + test.ok(!error, 'There shoudn\'t be an error object.'); + test.ok(response); + parse.find(className, stub1.objectId, function (error, response) { + test.ok(!error, 'There shoudn\'t be an error object.'); + test.equal(stub1.foo, response.foo, 'response.foo should be "bar00".'); + test.done(); + }); }); }; +exports.insertClass2 = function (test) { + parse.insert(className2, {foo: 'bar'}, function (error, response) { + test.expect(1); + test.ok(!error, 'There shoudn\'t be an error object.'); + stubRelation = response; + test.done(); + }); +}; -exports['add relation'] = function (assert) { - parse.addRelation("secondObject",className,stub.objectId,className2,stub2.objectId, function (err ,response){ - err && console.log(response); - assert.ok(response); - assert.done(); - }); -} +exports.addRelation = function (test) { + parse.addRelation("secondObject", className2, stubRelation.objectId, className, stub1.objectId, function (error, response) { + test.expect(3); + test.ok(!error, 'There shoudn\'t be an error object.'); -exports['batch'] = function (assert) { - requests = [{"method":"PUT","path": "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/1/classes/"+className+'/'+stub.objectId, "body": object3},{"method":"PUT","path": "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/1/classes/"+className2+'/'+stub2.objectId, "body": object4} ]; - parse.batch(requests, function(err,response){ - err && console.log(response); - assert.ok(response); - assert.done(); + var query = { + where: { + $relatedTo: { + object: { + __type: 'Pointer', + className: className2, + objectId: stubRelation.objectId + }, + key: 'secondObject' + } + } + }; + parse.find(className, query, function (error, response) { + test.ok(!error, 'There shoudn\'t be an error object.'); + test.equal(stub1.foo, response.results[0].foo, 'The response object should contain the related object.'); + test.done(); + }); }); -} +}; -exports['delete'] = function (assert) { - parse['delete'](className, stub.objectId, function (err) { - err && console.log(err); - assert.ok(!err); - parse.find(className, stub.objectId, function (err, response) { - assert.equal(404, err.type); - assert.done(); +exports.deleteOne = function (test) { + parse.delete(className2, stubRelation.objectId, function (error, response) { + test.expect(3); + test.ok(!error, 'There shouldn\'t be an error object.'); + parse.find(className2, stubRelation.objectId, function (error, response) { + test.ok(error, 'There should be an error object.'); + test.equal(101, error.code, 'error.code should be 101.'); + test.done(); }); }); }; -exports['delete all'] = function(assert){ - parse.insert(className2,object2, function(err,response){ - parse.deleteAll(className2, function(){ - parse.findMany(className2, '', function(err, response){ - assert.equal(0, response.results.length); - assert.done(); - }); - }); +exports.deleteAll = function (test) { + parse.deleteAll(className, function (error, response) { + test.expect(2); + test.ok(!error, 'There shoudn\'t be an error object.'); + test.ok(response[0].hasOwnProperty('success')); + test.done(); }); -} +}; -exports['push notification error'] = function (assert) { +exports.userTests = { + insertUser : function (test) { + test.expect(1); + parse.insertUser({username: 'foo', password: 'bar'}, function (error, response) { + test.ok(!error, 'There shoudn\'t be an error object.'); + test.done(); + }); + }, + getUser: function (test) { + test.expect(2); + parse.getUser({where:{username: 'foo'}}, function (error, response) { + test.ok(!error, 'There shoudn\'t be an error object.'); + test.equal('foo', response.results[0].username, 'response.results[0].username should be foo.'); + test.done(); + }); + }, + deleteUser: function (test) { + test.expect(1); + parse.getUser({where:{username: 'foo'}}, function (error, response) { + parse.deleteUser(response.results[0].objectId, function (error, response) { + test.ok(!error, 'There shoudn\'t be an error object.'); + test.done(); + }); + }); + } +}; + +exports.pushNotificationError = function (test) { parse.sendPush({ channels: ['foobar'], data2: { alert: 'test message' } - }, function (error, result) { - assert.ok(error); - assert.equal(result, null); - assert.equal(error.message, 'Missing the push data. (Code: 115)'); - assert.done(); + }, function (error, response) { + test.expect(3); + test.ok(error); + test.equal(response, null); + test.equal(error.code, 115, 'error.code should be 115.'); + test.done(); }); -} +}; diff --git a/test/testRestApiKey.js b/test/testRestApiKey.js new file mode 100644 index 0000000..01d95da --- /dev/null +++ b/test/testRestApiKey.js @@ -0,0 +1,78 @@ +// this file runs tests against the rest api key +var Parse = require('../index').Parse; + +// use environment variables APPLICATION_ID and MASTER_KEY to test against +var application_id = process.env.APPLICATION_ID; +var rest_api_key = process.env.REST_API_KEY; + +// require the environment variables, or exit with explanation +if (!application_id || !rest_api_key) { + console.log('Set the following environment variables for the test Parse app'); + console.log(' export APPLICATION_ID=...'); + console.log(' export REST_API_KEY=...'); + process.exit(1); +} + +// global objects to test against +var parse = new Parse({app_id: application_id, api_key: rest_api_key}); +var user = {username: 'foo', password: 'bar'}; +var user2 = {username: 'baz', password: 'qux'}; +var userObject; +var user2Object; + +exports.userLogin = function (test) { + test.expect(4); + parse.insertUser(user2, function (error, response) { + test.ok(!error, 'Failed creating user.'); + user2Object = response; + parse.insertUser(user, function (error, response) { + test.ok(!error, 'Failed creating user.'); + userObject = response; + parse.loginUser(user.username, user.password, function (error, response) { + test.ok(!error, 'Login failed.'); + test.equal(user.username, response.username, 'Should be the same username.'); + test.done(); + }); + }); + }); +}; + +exports.editUser = function (test) { + test.expect(3); + parse.updateUser(userObject.objectId, {username: 'foo0'}, userObject.sessionToken, function (error, response) { + userObject.username = 'foo0'; + test.ok(!error); + parse.getUser({objectId: userObject.objectId}, function (error, response) { + test.ok(!error); + test.equal(userObject.username, response.username, 'usernames should be the same.'); + test.done(); + }); + }); +}; + +exports.editUserFail = function (test) { + test.expect(1); + parse.updateUser(user2Object.objectId, {username: 'baz0'}, userObject.sessionToken, function (error, response) { + test.ok(error); + test.done(); + }); +}; + +exports.me = function (test) { + test.expect(1); + parse.me(userObject.sessionToken, function (error, response) { + test.ok(!error); + test.done(); + }); +}; + +exports.deleteUser = function (test) { + test.expect(2); + parse.deleteUser(userObject.objectId, userObject.sessionToken, function (error, response) { + test.ok(!error); + parse.deleteUser(user2Object.objectId, user2Object.sessionToken, function (error, response) { + test.ok(!error); + test.done(); + }); + }); +};