Compare commits

...
113 Commits
Author SHA1 Message Date
Fosco Marotto b6a67e213c Updated to 2.0.7 2016-02-04 19:14:37 -08:00
Nikita Lutsenko d9b547a5e4 Merge pull request #252 from laullon/correct_function_response_encoding
Correct Could Functions response encoding
2016-02-04 18:35:43 -08:00
German Laullon 36675a9598 Correct function response encoding
This will encode all ParseObject on the correct way so they can be translated into PFObject by the IOS SDK.
2016-02-04 23:42:01 +01:00
Fosco Marotto c106ac6499 Merge pull request #239 from montymxb/patch-1
Update express app to use 'mountPath' instead of /
2016-02-04 08:04:53 -08:00
Benjamin Wilson Friedman 585ba8dcf8 Update express app to use 'mountPath' instead of / 2016-02-04 01:24:51 -08:00
Nikita Lutsenko 72fa1f28a8 Merge pull request #233 from ParsePlatform/nlutsenko.transform.cleaner
Split basic type conversions from other logic in transform.js.
2016-02-04 00:13:32 -08:00
Fosco Marotto 27560e14c7 Merge pull request #132 from r4zzz4k/master
Added ability to set server url for Parse JS client SDK
2016-02-03 23:48:07 -08:00
Fosco Marotto 4b1132ad40 Merge pull request #225 from flovilmart/standalone-server
Adds standalone parse server
2016-02-03 23:45:47 -08:00
Nikita Lutsenko 21aef055b0 Split basic type conversions from other logic in transform.js. 2016-02-03 22:04:27 -08:00
Nikita Lutsenko ee5e06cff9 Merge pull request #231 from ParsePlatform/nlutsenko.transform.geopoint
Fixed storage of GeoPoints in nested arrays/maps.
2016-02-03 21:17:59 -08:00
Nikita Lutsenko 437e7726ed Fix storage of GeoPoints in nested arrays/maps. 2016-02-03 20:06:31 -08:00
Nikita Lutsenko 3cd7f7dd73 Add geo point in subobject and array end-to-end tests. 2016-02-03 20:06:31 -08:00
Nikita Lutsenko 0acd8c6c0f Add transform rest to mongo tests for geo points. 2016-02-03 20:06:30 -08:00
Nikita Lutsenko 808d2ccbec Merge pull request #204 from ParsePlatform/nlutsenko.dependencies 2016-02-03 14:31:54 -08:00
Florent Vilmart 098d671004 Adds standalone parse server, configurable by environment, and that can start with npm start 2016-02-03 17:00:10 -05:00
Nikita Lutsenko b2a5e67978 Update all dependencies. 2016-02-03 13:41:36 -08:00
Drew 39fb0d02dd Merge pull request #221 from drew-gross/master
Implment GET /parse/schemas
2016-02-03 13:02:01 -08:00
Drew Gross 33a881764d Tabs -> spaces 2016-02-03 12:10:50 -08:00
Drew Gross 3d989501b2 Get all schemas, with tests, and matches parse.com schemas API 2016-02-03 12:06:45 -08:00
Drew Gross 0547592952 Implement GET /parse/schemas 2016-02-03 12:05:48 -08:00
Fosco Marotto c866afd016 Merge pull request #217 from flovilmart/fix-216
Adds proper http request to match parse original implementation
2016-02-03 10:59:16 -08:00
Nikita Lutsenko bf1b886c85 Merge pull request #205 from ParsePlatform/nlutsenko.oldmongo
Add Mongo 2.6.11 to Travis-CI.
2016-02-03 10:58:25 -08:00
Fosco Marotto 6946d8670a Merge pull request #209 from taylorstine/cloud_master
Added master option to cloud code functions
2016-02-03 10:56:47 -08:00
Fosco Marotto e44106f3ed Merge pull request #210 from taylorstine/regexr_comment
updated regexr link for updated regex
2016-02-03 10:53:22 -08:00
Florent Vilmart 5a37c8307c Adds proper http request to match parse original implementation 2016-02-03 12:00:16 -05:00
Taylor Stine 33f8cdcd39 updated regexr link for updated regex 2016-02-03 07:44:23 -05:00
Taylor Stine fec751b5e6 Added master option to cloud code functions 2016-02-03 07:24:57 -05:00
Nikita Lutsenko ba9d1f9eef Add Mongo 2.6.11 to Travis-CI. 2016-02-03 02:53:12 -08:00
Nikita Lutsenko c37db7b6ff Merge pull request #203 from ParsePlatform/nlutsenko.cloud.reject
Fixed Cloud functions/triggers async rejection crashing server.
2016-02-03 02:14:11 -08:00
Nikita Lutsenko f035307984 Fixed Cloud functions/triggers async rejection crashing server. 2016-02-03 01:47:05 -08:00
Fosco Marotto 26a60f2341 Merge pull request #200 from bohemima/master
Don't force port to be specified in mongoURI
2016-02-03 01:43:50 -08:00
Henrik Malmberg 456392df4a Don't force port to be specified in mongoURI
http://regexr.com/3cncm
2016-02-03 10:09:57 +01:00
Nikita Lutsenko 51dfc62ba8 Merge pull request #199 from ParsePlatform/nlutsenko.readme
Add npm badge to README.
2016-02-03 00:57:01 -08:00
Nikita Lutsenko d8b143379c Add npm badge to README. 2016-02-03 00:47:23 -08:00
Andrew Mihailov a65f2d13ab Added ability to set server url for Parse JS client SDK whithin ParseServer constructor 2016-02-03 10:42:16 +02:00
Fosco Marotto 4d4d2a2ce0 Updating to 2.0.6 2016-02-03 00:37:09 -08:00
Nikita Lutsenko 0a1237326d Merge pull request #196 from ParsePlatform/nlutsenko.travis
Add Travis-CI and CodeCov.io integrations.
2016-02-03 00:33:40 -08:00
Nikita Lutsenko e5911d3cb9 Exclude tests from code coverage. 2016-02-03 00:21:55 -08:00
Nikita Lutsenko c73d9f1e27 Add Travis-CI, Codecov badges to README. 2016-02-03 00:21:55 -08:00
Nikita Lutsenko 4bc921480f Add code coverage via CodeCov.io. 2016-02-03 00:21:55 -08:00
Nikita Lutsenko ca2ac2195d Add Travis-CI configuration. 2016-02-03 00:21:55 -08:00
Fosco Marotto e5140deaed Marked flaky client test notWorking 2016-02-03 00:06:14 -08:00
Fosco Marotto 190dc5eb75 Fixed tests and some undefined value handling. 2016-02-03 00:02:54 -08:00
Fosco Marotto 4a787654d9 Updating to 2.0.5 2016-02-02 20:32:33 -08:00
Fosco Marotto ef33575811 Merge branch 'master' of github.com:ParsePlatform/parse-server 2016-02-02 20:29:56 -08:00
Fosco Marotto 8c8fceec05 Fixing cross domain issues with /files for #169 2016-02-02 20:29:46 -08:00
Fosco Marotto f04656e828 Merge pull request #173 from taylorstine/facebook_login
Fixed invalid facebook login sessionToken
2016-02-02 20:27:39 -08:00
Fosco Marotto c290070b7d Merge pull request #193 from ParsePlatform/fosco.logout
Updated user tests, added /logout
2016-02-02 20:16:54 -08:00
Fosco Marotto 373f4f06a6 Updated user tests, added /logout 2016-02-02 20:16:06 -08:00
Taylor Stine a54828d9f5 Added facebook signup username and fixed schema parse error for default expired at 2016-02-02 22:50:43 -05:00
Taylor Stine 7ab05372ea Merge remote-tracking branch 'upstream/master' into facebook_login 2016-02-02 21:58:01 -05:00
Fosco Marotto 3d3a97d151 Merge branch 'master' of github.com:ParsePlatform/parse-server 2016-02-02 18:08:37 -08:00
Fosco Marotto c430ee94da Updated test Parse.User testing user.isCurrent, and raised testing time limit. 2016-02-02 18:08:25 -08:00
Fosco Marotto 3c3f1e5365 Merge pull request #190 from ParsePlatform/nlutsenko.ignorance
Add .idea folder to ignore list.
2016-02-02 17:35:13 -08:00
Nikita Lutsenko 3e46efc2e3 Add .idea folder to ignore list. 2016-02-02 17:28:10 -08:00
Fosco Marotto 89bd2f737f Merge pull request #189 from ParsePlatform/wangmengyan.add_coverage
Add istanbul for code coverage
2016-02-02 16:49:35 -08:00
wangmengyan95 adccb2869c Add istanbul for code coverage 2016-02-02 16:38:56 -08:00
Taylor Stine 54e8285577 Merge remote-tracking branch 'upstream/master' into facebook_login 2016-02-02 17:57:17 -05:00
Fosco Marotto f82122f4a1 Merge pull request #179 from maysale01/hotfix-fix-iterator-syntax-for-future-es-syntax
Hotfix: Missing variable declarations on iterators. This makes runn…
2016-02-02 13:51:49 -08:00
Fosco Marotto 96609e35d5 Merge pull request #178 from maysale01/hotfix-pass-the-correct-scope-to-rest-write-handle-followup
hotfix: issue exposed during testing with es6 where the scope o…
2016-02-02 13:50:47 -08:00
Fosco Marotto a78adfe533 Merge pull request #172 from taylorstine/cloud_shim
Added req.auth.user to parse cloud functions
2016-02-02 13:39:49 -08:00
Taylor Stine 35b6b16918 Removed user todo from cloud function 2016-02-02 16:14:42 -05:00
Fosco Marotto 15edb0cf95 Merge pull request #148 from taylorstine/encode_uri
Added encoding to user name and password
2016-02-02 12:07:47 -08:00
Fosco Marotto 8fda77c986 Merge pull request #145 from taylorstine/cloud_function
Added cloud funciton argument
2016-02-02 11:51:42 -08:00
Fosco Marotto 044fb52a2f Merge pull request #180 from maysale01/hotfix-use-symlinked-jasmine-binary-instead-of-global
Updated package.json to use the symlinked jasmine binary
2016-02-02 11:47:59 -08:00
Alexander Mays fa66ac551f Updated package.json to use the symlinked jasmine binary
Signed-off-by: Alexander Mays <maysale01@gmail.com>
2016-02-02 10:12:25 -05:00
Alexander Mays 52769cce29 Fixed the missing variable declarations on iterators. This makes running the tests in ES6 pass!
Signed-off-by: Alexander Mays <maysale01@gmail.com>
2016-02-02 10:03:54 -05:00
Alexander Mays 5d05c742c9 This fixes an issue exposed during testing with es6 where the scope on the 2nd invocation of handleFollowup isn't set
Signed-off-by: Alexander Mays <maysale01@gmail.com>
2016-02-02 09:59:20 -05:00
Taylor Stine ffa7d1b1f1 Added req.auth.user to parse cloud functions 2016-02-02 08:13:13 -05:00
Taylor Stine 95cea9230b Added detail comment to the ParseServer constructor for cloud function 2016-02-02 07:51:19 -05:00
Taylor Stine 97cf00b4d0 added regex to validate mongo uri and remove unused variables 2016-02-02 07:44:36 -05:00
Taylor Stine 6b91b98854 Merge remote-tracking branch 'upstream/master' into encode_uri 2016-02-02 07:14:53 -05:00
Fosco Marotto 477a091f88 Merge pull request #147 from maysale01/hotfix-default-session-schema-expects-date-not-iso-string
Bugfix: Correctly uses date instead of string for expiresAt when creating a s…
2016-02-01 21:44:57 -08:00
Taylor Stine 6546610567 Fixed invalid facebook login sessionToken 2016-02-01 22:20:17 -05:00
Fosco Marotto 3978e41f02 Merge pull request #156 from skinp/patp.expose-s3
S3Adapter: expose the object and use => for s3.upload callback
2016-02-01 16:08:08 -08:00
Patrick Pelletier e3c5ea959f S3Adapter: expose the object and use => for s3.upload callback 2016-02-01 14:47:02 -08:00
Fosco Marotto e9f82632b7 Updating to 2.0.4 with S3 and lots of other fixes. 2016-02-01 13:10:43 -08:00
Alexander Mays 8e1f632e31 Correctly uses date instead of string for expiresAt when creating a session
Signed-off-by: Alexander Mays <maysale01@gmail.com>
2016-02-01 16:03:44 -05:00
Taylor Stine 15ab25940e Added encoding to user name and password 2016-02-01 16:03:31 -05:00
Fosco Marotto c2373cfe7f Merge pull request #146 from taylorstine/file_fix
Fixed bug with null file field
2016-02-01 12:54:08 -08:00
Taylor Stine f9a5255e32 Fixed bug with null file field 2016-02-01 15:46:56 -05:00
Taylor Stine 4bda6e49e4 Added cloud funciton argument 2016-02-01 15:43:04 -05:00
Fosco Marotto 319179387b Merge pull request #113 from skinp/s3-files
Add support for saving files to AWS S3
2016-02-01 12:19:23 -08:00
Patrick Pelletier 2008c4dce9 Use ES6 => instead of self = this ack 2016-02-01 12:00:57 -08:00
Patrick Pelletier 05c2246f7b Add support for saving files to AWS S3 2016-02-01 12:00:57 -08:00
Fosco Marotto a814655b15 Merge pull request #142 from ParsePlatform/fosco.coc
Adding CONTRIBUTING.md and Open Code of Conduct.
2016-02-01 11:44:36 -08:00
Fosco Marotto 3f2124410a Adding CONTRIBUTING.md and Open Code of Conduct. 2016-02-01 11:44:03 -08:00
Fosco Marotto 86e42976f9 Some updates to fix tests 2016-02-01 11:35:20 -08:00
Fosco Marotto 5dc66fd8fa Merge pull request #139 from jamiechapman/master
Added multi-app support in DatabaseAdapter.js
2016-02-01 11:27:24 -08:00
Fosco Marotto 5216c93071 Merge pull request #141 from ParsePlatform/fosco.crypto
Addressed bugs with bcrypt-nodejs and changed crypto.js to password.js
2016-02-01 11:25:41 -08:00
Fosco Marotto cb1079a6d3 Addressed bugs with bcrypt-nodejs and changed crypto.js to password.js 2016-02-01 11:24:26 -08:00
Jamie Chapman 40bfc4ae95 Added multi-app support in DatabaseAdapter.js 2016-02-01 19:11:02 +00:00
Fosco Marotto 7f01a67c0f Merge pull request #108 from felixrieseberg/bcrypt-node
Add Windows Support (bcrypt > bcrypt-node)
2016-02-01 11:06:29 -08:00
Felix Rieseberg 1cddbb0a37 🏁 Add Windows Support (bcrypt > bcrypt-node)
- This commit replaces bcrypt with bcrypt-node, which has the same functionality as bcrypy - except that it is a pure Node implementation. This change is required to run parse-server on Windows (one can get bcrypt to compile on Windows, but it requires a few Gigabytes of dependencies).
2016-02-01 10:30:12 -08:00
Fosco Marotto 6051e3a35c Merge pull request #133 from maysale01/hotfix-session-validation-fails-if-installation-id-is-undefined
Bugfix: session validation fails if the request does not include installationId
2016-02-01 10:28:37 -08:00
Fosco Marotto 18b5a30f1e Merge pull request #129 from spekulatius/trailing-spaces
remove trailing spaces
2016-02-01 10:10:39 -08:00
Fosco Marotto 9eb4e4c0e7 Merge pull request #121 from JordanVincent/master
Fix untransform null objects
2016-02-01 10:09:53 -08:00
Fosco Marotto 952ccd4ef3 Merge pull request #91 from natanrolnik/master
Converts httpRequest body to string if needed
2016-02-01 10:08:47 -08:00
Fosco Marotto 72a956a3cf Merge pull request #120 from GigabyteTheOne/master
Fix #119: Empty find options for GET requests
2016-02-01 10:08:26 -08:00
Fosco Marotto 251ec4d701 Merge pull request #73 from dchest/csprng
Use CSPRNG to generate objectIds
2016-02-01 10:02:12 -08:00
Alexander Mays 07b0235349 installationId only gets set on the session if passed in to the request
Signed-off-by: Alexander Mays <maysale01@gmail.com>
2016-02-01 12:22:41 -05:00
Peter 956fd0840f remove trailing spaces 2016-02-02 03:09:57 +13:00
Jordan e8e7f4ba6b fix untransform null objects 2016-02-01 00:40:01 -08:00
Konstantin Simakov 234c405530 Fix #119: remove 'GET' condition and replace it with assign 2016-02-01 13:36:42 +05:00
Konstantin Simakov 1328270014 Fix #119: Empty find options for GET requests 2016-02-01 12:48:39 +05:00
Fosco Marotto bce6244e31 Merge pull request #105 from r4zzz4k/master
Parse request.where from string into JSON
2016-01-31 21:29:25 -08:00
Andrew Mihailov 25a0fbeaee parse request.where from string into JSON 2016-01-31 20:51:32 +02:00
Fosco Marotto 522abdf606 Updated to 2.0.3 2016-01-30 17:01:13 -08:00
Fosco Marotto 740332bc81 Fixed facebook login issue. 2016-01-30 17:00:51 -08:00
Natan Rolnik 03302459e2 Converts httpRequest body to string if needed 2016-01-31 02:52:16 +02:00
Fosco Marotto 82e183cbc9 Merge pull request #89 from ParsePlatform/fosco.schema
Added another map/object fix
2016-01-30 16:23:26 -08:00
Fosco Marotto 44ee117327 Added another map/object fix 2016-01-30 16:22:35 -08:00
Dmitry Chestnykh c13b61f07e Use CSPRNG to generate objectIds 2016-01-30 20:58:37 +01:00
33 changed files with 958 additions and 239 deletions
+3
View File
@@ -28,3 +28,6 @@ node_modules
# Emacs
*~
# WebStorm/IntelliJ
.idea
+11
View File
@@ -0,0 +1,11 @@
branches:
only:
- master
language: node_js
node_js:
- "4.1"
- "4.2"
env:
- MONGODB_VERSION=2.6.11
- MONGODB_VERSION=3.0.8
after_success: ./node_modules/.bin/codecov
+1
View File
@@ -64,6 +64,7 @@ var getAuthForSessionToken = function(config, sessionToken) {
var obj = results[0]['user'];
delete obj.password;
obj['className'] = '_User';
obj['sessionToken'] = sessionToken;
var userObject = Parse.Object.fromJSON(obj);
cache.setUser(sessionToken, userObject);
return new Auth(config, false, userObject);
+17
View File
@@ -0,0 +1,17 @@
### Contributing to Parse Server
#### Pull Requests Welcome!
We really want Parse to be yours, to see it grow and thrive in the open source community.
##### Please Do's
* Please write tests to cover new methods.
* Please run the tests and make sure you didn't break anything.
##### Code of Conduct
This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
[code-of-conduct]: http://todogroup.org/opencodeofconduct/#Parse Server/fjm@fb.com
+10 -2
View File
@@ -20,6 +20,7 @@ var adapter = ExportAdapter;
var cache = require('./cache');
var dbConnections = {};
var databaseURI = 'mongodb://localhost:27017/parse';
var appDatabaseURIs = {};
function setAdapter(databaseAdapter) {
adapter = databaseAdapter;
@@ -29,11 +30,17 @@ function setDatabaseURI(uri) {
databaseURI = uri;
}
function setAppDatabaseURI(appId, uri) {
appDatabaseURIs[appId] = uri;
}
function getDatabaseConnection(appId) {
if (dbConnections[appId]) {
return dbConnections[appId];
}
dbConnections[appId] = new adapter(databaseURI, {
var dbURI = (appDatabaseURIs[appId] ? appDatabaseURIs[appId] : databaseURI);
dbConnections[appId] = new adapter(dbURI, {
collectionPrefix: cache.apps[appId]['collectionPrefix']
});
dbConnections[appId].connect();
@@ -44,5 +51,6 @@ module.exports = {
dbConnections: dbConnections,
getDatabaseConnection: getDatabaseConnection,
setAdapter: setAdapter,
setDatabaseURI: setDatabaseURI
setDatabaseURI: setDatabaseURI,
setAppDatabaseURI: setAppDatabaseURI
};
+15 -2
View File
@@ -34,8 +34,21 @@ ExportAdapter.prototype.connect = function() {
return this.connectionPromise;
}
//http://regexr.com/3cncm
if (!this.mongoURI.match(/^mongodb:\/\/((.+):(.+)@)?([^:@]+):{0,1}([^:]+)\/(.+?)$/gm)) {
throw new Error("Invalid mongoURI: " + this.mongoURI)
}
var usernameStart = this.mongoURI.indexOf('://') + 3;
var lastAtIndex = this.mongoURI.lastIndexOf('@');
var encodedMongoURI = this.mongoURI;
var split = null;
if (lastAtIndex > 0) {
split = this.mongoURI.slice(usernameStart, lastAtIndex).split(':');
encodedMongoURI = this.mongoURI.slice(0, usernameStart) + encodeURIComponent(split[0]) + ':' + encodeURIComponent(split[1]) + this.mongoURI.slice(lastAtIndex);
}
this.connectionPromise = Promise.resolve().then(() => {
return MongoClient.connect(this.mongoURI);
return MongoClient.connect(encodedMongoURI, {uri_decode_auth:true});
}).then((db) => {
this.db = db;
});
@@ -232,7 +245,7 @@ ExportAdapter.prototype.handleRelationUpdates = function(className,
}
if (op.__op == 'Batch') {
for (x of op.ops) {
for (var x of op.ops) {
process(x, key);
}
}
+1
View File
@@ -5,6 +5,7 @@
// Adapter classes must implement the following functions:
// * create(config, filename, data)
// * get(config, filename)
// * location(config, req, filename)
//
// Default is GridStoreAdapter, which requires mongo
// and for the API server to be using the ExportAdapter
+11 -1
View File
@@ -4,6 +4,7 @@
// Requires the database adapter to be based on mongoclient
var GridStore = require('mongodb').GridStore;
var path = require('path');
// For a given config object, filename, and data, store a file
// Returns a promise
@@ -32,7 +33,16 @@ function get(config, filename) {
});
}
// Generates and returns the location of a file stored in GridStore for the
// given request and filename
function location(config, req, filename) {
return (req.protocol + '://' + req.get('host') +
path.dirname(req.originalUrl) + '/' + req.config.applicationId +
'/' + encodeURIComponent(filename));
}
module.exports = {
create: create,
get: get
get: get,
location: location
};
+45 -2
View File
@@ -1,5 +1,9 @@
## parse-server
[![Build Status](https://img.shields.io/travis/ParsePlatform/parse-server/master.svg?style=flat)](https://travis-ci.org/ParsePlatform/parse-server)
[![Coverage Status](https://img.shields.io/codecov/c/github/ParsePlatform/parse-server/master.svg)](https://codecov.io/github/ParsePlatform/parse-server?branch=master)
[![npm version](https://img.shields.io/npm/v/parse-server.svg?style=flat)](https://www.npmjs.com/package/parse-server)
A Parse.com API compatible router package for Express
Read the announcement blog post here: http://blog.parse.com/announcements/introducing-parse-server-and-the-database-migration-tool/
@@ -18,6 +22,7 @@ There is a development wiki here on GitHub: https://github.com/ParsePlatform/par
* cloud - The absolute path to your cloud code main.js file
* fileKey - For migrated apps, this is necessary to provide access to files already hosted on Parse.
* facebookAppIds - An array of valid Facebook application IDs.
* serverURL - URL which will be used by Cloud Code functions to make requests against.
#### Client key options:
@@ -45,6 +50,8 @@ var ParseServer = require('parse-server').ParseServer;
var app = express();
var port = process.env.PORT || 1337;
// Specify the connection string for your mongodb database
// and the location to your Parse cloud code
var api = new ParseServer({
@@ -52,7 +59,8 @@ var api = new ParseServer({
cloud: '/home/myApp/cloud/main.js', // Provide an absolute path
appId: 'myAppId',
masterKey: 'mySecretMasterKey',
fileKey: 'optionalFileKey'
fileKey: 'optionalFileKey',
serverURL: 'http://localhost:' + port + '/parse' // Don't forget to change to https if needed
});
// Serve the Parse API on the /parse URL prefix
@@ -63,13 +71,48 @@ app.get('/', function(req, res) {
res.status(200).send('Express is running here.');
});
var port = process.env.PORT || 1337;
app.listen(port, function() {
console.log('parse-server-example running on port ' + port + '.');
});
```
#### Standalone usage
You can configure the Parse Server with environment variables:
```js
PARSE_SERVER_DATABASE_URI
PARSE_SERVER_CLOUD_CODE_MAIN
PARSE_SERVER_COLLECTION_PREFIX
PARSE_SERVER_APPLICATION_ID // required
PARSE_SERVER_CLIENT_KEY
PARSE_SERVER_REST_API_KEY
PARSE_SERVER_DOTNET_KEY
PARSE_SERVER_JAVASCRIPT_KEY
PARSE_SERVER_DOTNET_KEY
PARSE_SERVER_MASTER_KEY // required
PARSE_SERVER_FILE_KEY
PARSE_SERVER_FACEBOOK_APP_IDS // string of comma separated list
```
Alernatively, you can use the `PARSE_SERVER_OPTIONS` environment variable set to the JSON of your configuration (see Usage).
To start the server, just run `npm start`.
##### Global installation
You can install parse-server globally
`$ npm install -g parse-server`
Now you can just run `$ parse-server` from your command line.
### Supported
* CRUD operations
+1 -1
View File
@@ -434,7 +434,7 @@ function includePath(config, auth, response, path) {
function findPointers(object, path) {
if (object instanceof Array) {
var answer = [];
for (x of object) {
for (var x of object) {
answer = answer.concat(findPointers(x, path));
}
return answer;
+28 -12
View File
@@ -2,13 +2,14 @@
// that writes to the database.
// This could be either a "create" or an "update".
var crypto = require('crypto');
var deepcopy = require('deepcopy');
var rack = require('hat').rack();
var Auth = require('./Auth');
var cache = require('./cache');
var Config = require('./Config');
var crypto = require('./crypto');
var passwordCrypto = require('./password');
var facebook = require('./facebook');
var Parse = require('parse/node');
var triggers = require('./triggers');
@@ -228,6 +229,7 @@ RestWrite.prototype.handleFacebookAuthData = function() {
this.className,
{'authData.facebook.id': facebookData.id}, {});
}).then((results) => {
this.storage['authProvider'] = "facebook";
if (results.length > 0) {
if (!this.query) {
// We're signing up, but this user already exists. Short-circuit
@@ -236,6 +238,7 @@ RestWrite.prototype.handleFacebookAuthData = function() {
response: results[0],
location: this.location()
};
this.data.objectId = results[0].objectId;
return;
}
@@ -248,6 +251,8 @@ RestWrite.prototype.handleFacebookAuthData = function() {
// We're trying to create a duplicate FB auth. Forbid it
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
'this auth is already used');
} else {
this.data.username = rack();
}
// This FB auth does not already exist, so transform it to a
@@ -261,7 +266,7 @@ RestWrite.prototype.handleFacebookAuthData = function() {
// The non-third-party parts of User transformation
RestWrite.prototype.transformUser = function() {
if (this.response || this.className !== '_User') {
if (this.className !== '_User') {
return;
}
@@ -271,7 +276,8 @@ RestWrite.prototype.transformUser = function() {
var token = 'r:' + rack();
this.storage['token'] = token;
promise = promise.then(() => {
// TODO: Proper createdWith options, pass installationId
var expiresAt = new Date();
expiresAt.setFullYear(expiresAt.getFullYear() + 1);
var sessionData = {
sessionToken: token,
user: {
@@ -281,10 +287,15 @@ RestWrite.prototype.transformUser = function() {
},
createdWith: {
'action': 'login',
'authProvider': 'password'
'authProvider': this.storage['authProvider'] || 'password'
},
restricted: false
restricted: false,
installationId: this.data.installationId,
expiresAt: Parse._encode(expiresAt)
};
if (this.response && this.response.response) {
this.response.response.sessionToken = token;
}
var create = new RestWrite(this.config, Auth.master(this.config),
'_Session', null, sessionData);
return create.execute();
@@ -299,7 +310,7 @@ RestWrite.prototype.transformUser = function() {
if (this.query) {
this.storage['clearSessions'] = true;
}
return crypto.hash(this.data.password).then((hashedPassword) => {
return passwordCrypto.hash(this.data.password).then((hashedPassword) => {
this.data._hashed_password = hashedPassword;
delete this.data.password;
});
@@ -361,7 +372,7 @@ RestWrite.prototype.handleFollowup = function() {
};
delete this.storage['clearSessions'];
return this.config.database.destroy('_Session', sessionQuery)
.then(this.handleFollowup);
.then(this.handleFollowup.bind(this));
}
};
@@ -403,6 +414,8 @@ RestWrite.prototype.handleSession = function() {
if (!this.query && !this.auth.isMaster) {
var token = 'r:' + rack();
var expiresAt = new Date();
expiresAt.setFullYear(expiresAt.getFullYear() + 1);
var sessionData = {
sessionToken: token,
user: {
@@ -414,7 +427,7 @@ RestWrite.prototype.handleSession = function() {
'action': 'create'
},
restricted: true,
expiresAt: 0
expiresAt: Parse._encode(expiresAt)
};
for (var key in this.data) {
if (key == 'objectId') {
@@ -701,15 +714,18 @@ RestWrite.prototype.objectId = function() {
return this.data.objectId || this.query.objectId;
};
// Returns a string that's usable as an object id.
// Probably unique. Good enough? Probably!
// Returns a unique string that's usable as an object id.
function newObjectId() {
var chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'abcdefghijklmnopqrstuvwxyz' +
'0123456789');
var objectId = '';
for (var i = 0; i < 10; ++i) {
objectId += chars[Math.floor(Math.random() * chars.length)];
var bytes = crypto.randomBytes(10);
for (var i = 0; i < bytes.length; ++i) {
// Note: there is a slight modulo bias, because chars length
// of 62 doesn't divide the number of all bytes (256) evenly.
// It is acceptable for our purposes.
objectId += chars[bytes.readUInt8(i) % chars.length];
}
return objectId;
}
+77
View File
@@ -0,0 +1,77 @@
// S3Adapter
//
// Stores Parse files in AWS S3.
var AWS = require('aws-sdk');
var path = require('path');
var DEFAULT_REGION = "us-east-1";
var DEFAULT_BUCKET = "parse-files";
// Creates an S3 session.
// Providing AWS access and secret keys is mandatory
// Region and bucket will use sane defaults if omitted
function S3Adapter(accessKey, secretKey, options) {
options = options || {};
this.region = options.region || DEFAULT_REGION;
this.bucket = options.bucket || DEFAULT_BUCKET;
this.bucketPrefix = options.bucketPrefix || "";
this.directAccess = options.directAccess || false;
s3Options = {
accessKeyId: accessKey,
secretAccessKey: secretKey,
params: {Bucket: this.bucket}
};
AWS.config.region = this.region;
this.s3 = new AWS.S3(s3Options);
}
// For a given config object, filename, and data, store a file in S3
// Returns a promise containing the S3 object creation response
S3Adapter.prototype.create = function(config, filename, data) {
var params = {
Key: this.bucketPrefix + filename,
Body: data,
};
if (this.directAccess) {
params.ACL = "public-read"
}
return new Promise((resolve, reject) => {
this.s3.upload(params, (err, data) => {
if (err !== null) return reject(err);
resolve(data);
});
});
}
// Search for and return a file if found by filename
// Returns a promise that succeeds with the buffer result from S3
S3Adapter.prototype.get = function(config, filename) {
var params = {Key: this.bucketPrefix + filename};
return new Promise((resolve, reject) => {
this.s3.getObject(params, (err, data) => {
if (err !== null) return reject(err);
resolve(data.Body);
});
});
}
// Generates and returns the location of a file stored in S3 for the given request and
// filename
// The location is the direct S3 link if the option is set, otherwise we serve
// the file through parse-server
S3Adapter.prototype.location = function(config, req, filename) {
if (this.directAccess) {
return ('https://' + this.bucket + '.s3.amazonaws.com' + '/' +
this.bucketPrefix + filename);
}
return (req.protocol + '://' + req.get('host') +
path.dirname(req.originalUrl) + '/' + req.config.applicationId +
'/' + encodeURIComponent(filename));
}
module.exports = S3Adapter;
+4
View File
@@ -138,6 +138,7 @@ Schema.prototype.validateField = function(className, key, type, freeze) {
var expected = this.data[className][key];
if (expected) {
expected = (expected === 'map' ? 'object' : expected);
if (expected === type) {
return Promise.resolve(this);
} else {
@@ -211,6 +212,9 @@ Schema.prototype.validateObject = function(className, object) {
var geocount = 0;
var promise = this.validateClassName(className);
for (var key in object) {
if (object[key] === undefined) {
continue;
}
var expected = getType(object[key]);
if (expected === 'geopoint') {
geocount++;
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env node
var express = require('express');
var ParseServer = require("../index").ParseServer;
var app = express();
var options = {};
if (process.env.PARSE_SERVER_OPTIONS) {
options = JSON.parse(process.env.PARSE_SERVER_OPTIONS);
} else {
options.databaseURI = process.env.PARSE_SERVER_DATABASE_URI;
options.cloud = process.env.PARSE_SERVER_CLOUD_CODE_MAIN;
options.collectionPrefix = process.env.PARSE_SERVER_COLLECTION_PREFIX;
// Keys and App ID
options.appId = process.env.PARSE_SERVER_APPLICATION_ID;
options.clientKey = process.env.PARSE_SERVER_CLIENT_KEY;
options.restAPIKey = process.env.PARSE_SERVER_REST_API_KEY;
options.dotNetKey = process.env.PARSE_SERVER_DOTNET_KEY;
options.javascriptKey = process.env.PARSE_SERVER_JAVASCRIPT_KEY;
options.dotNetKey = process.env.PARSE_SERVER_DOTNET_KEY;
options.masterKey = process.env.PARSE_SERVER_MASTER_KEY;
options.fileKey = process.env.PARSE_SERVER_FILE_KEY;
// Comma separated list of facebook app ids
var facebookAppIds = process.env.PARSE_SERVER_FACEBOOK_APP_IDS;
if (facebookAppIds) {
facebookAppIds = facebookAppIds.split(",");
options.facebookAppIds = facebookAppIds;
}
}
var mountPath = process.env.PARSE_SERVER_MOUNT_PATH || "/";
var api = new ParseServer(options);
app.use(mountPath, api);
var port = process.env.PORT || 1337;
app.listen(port, function() {
console.log('parse-server-example running on http://localhost:'+ port + mountPath);
});
+27 -14
View File
@@ -10,32 +10,45 @@ var router = new PromiseRouter();
// Returns a promise that resolves to a {response} object.
function handleFind(req) {
var body = Object.assign(req.body, req.query);
var options = {};
if (req.body.skip) {
options.skip = Number(req.body.skip);
if (body.skip) {
options.skip = Number(body.skip);
}
if (req.body.limit) {
options.limit = Number(req.body.limit);
if (body.limit) {
options.limit = Number(body.limit);
}
if (req.body.order) {
options.order = String(req.body.order);
if (body.order) {
options.order = String(body.order);
}
if (req.body.count) {
if (body.count) {
options.count = true;
}
if (typeof req.body.keys == 'string') {
options.keys = req.body.keys;
if (typeof body.keys == 'string') {
options.keys = body.keys;
}
if (req.body.include) {
options.include = String(req.body.include);
if (body.include) {
options.include = String(body.include);
}
if (req.body.redirectClassNameForKey) {
options.redirectClassNameForKey = String(req.body.redirectClassNameForKey);
if (body.redirectClassNameForKey) {
options.redirectClassNameForKey = String(body.redirectClassNameForKey);
}
if(typeof body.where === 'string') {
body.where = JSON.parse(body.where);
}
return rest.find(req.config, req.auth,
req.params.className, req.body.where, options)
req.params.className, body.where, options)
.then((response) => {
if (response && response.results) {
for (result of response.results) {
if (result.sessionToken) {
result.sessionToken = req.info.sessionToken || result.sessionToken;
}
}
response.results.sessionToken
}
return {response: response};
});
}
+19 -1
View File
@@ -4,10 +4,19 @@ Parse.Cloud.define('hello', function(req, res) {
res.success('Hello world!');
});
Parse.Cloud.beforeSave('BeforeSaveFailure', function(req, res) {
Parse.Cloud.beforeSave('BeforeSaveFail', function(req, res) {
res.error('You shall not pass!');
});
Parse.Cloud.beforeSave('BeforeSaveFailWithPromise', function (req, res) {
var query = new Parse.Query('Yolo');
query.find().then(() => {
res.error('Nope');
}, () => {
res.success();
});
});
Parse.Cloud.beforeSave('BeforeSaveUnchanged', function(req, res) {
res.success();
});
@@ -27,6 +36,15 @@ Parse.Cloud.beforeDelete('BeforeDeleteFail', function(req, res) {
res.error('Nope');
});
Parse.Cloud.beforeSave('BeforeDeleteFailWithPromise', function (req, res) {
var query = new Parse.Query('Yolo');
query.find().then(() => {
res.error('Nope');
}, () => {
res.success();
});
});
Parse.Cloud.beforeDelete('BeforeDeleteTest', function(req, res) {
res.success();
});
+1 -1
View File
@@ -24,7 +24,7 @@ function validateAppId(appIds, access_token) {
}
return graphRequest('app?access_token=' + access_token)
.then((data) => {
if (data && appIds.contains(data.id)) {
if (data && appIds.indexOf(data.id) != -1) {
return;
}
throw new Parse.Error(
+2 -6
View File
@@ -7,7 +7,6 @@ var bodyParser = require('body-parser'),
middlewares = require('./middlewares.js'),
mime = require('mime'),
Parse = require('parse/node').Parse,
path = require('path'),
rack = require('hat').rack();
var router = express.Router();
@@ -44,10 +43,7 @@ var processCreate = function(req, res, next) {
FilesAdapter.getAdapter().create(req.config, filename, req.body)
.then(() => {
res.status(201);
var location = (req.protocol + '://' + req.get('host') +
path.dirname(req.originalUrl) + '/' +
req.config.applicationId + '/' +
encodeURIComponent(filename));
var location = FilesAdapter.getAdapter().location(req.config, req, filename);
res.set('Location', location);
res.json({ url: location, name: filename });
}).catch((error) => {
@@ -78,8 +74,8 @@ router.post('/files', function(req, res, next) {
'Filename not provided.'));
});
// TODO: do we need to allow crossdomain and method override?
router.post('/files/:filename',
middlewares.allowCrossDomain,
bodyParser.raw({type: '*/*', limit: '20mb'}),
middlewares.handleParseHeaders,
processCreate);
+4 -3
View File
@@ -8,12 +8,13 @@ var express = require('express'),
var router = new PromiseRouter();
function handleCloudFunction(req) {
// TODO: set user from req.auth
if (Parse.Cloud.Functions[req.params.functionName]) {
return new Promise(function (resolve, reject) {
var response = createResponseObject(resolve, reject);
var request = {
params: req.body || {}
params: req.body || {},
master: req.auth && req.auth.isMaster,
user: req.auth && req.auth.user,
};
Parse.Cloud.Functions[req.params.functionName](request, response);
});
@@ -27,7 +28,7 @@ function createResponseObject(resolve, reject) {
success: function(result) {
resolve({
response: {
result: result
result: Parse._encode(result)
}
});
},
+43
View File
@@ -0,0 +1,43 @@
var request = require("request"),
Parse = require('parse/node').Parse;
module.exports = function(options) {
var promise = new Parse.Promise();
var callbacks = {
success: options.success,
error: options.error
};
delete options.success;
delete options.error;
if (options.uri && !options.url) {
options.uri = options.url;
delete options.url;
}
if (typeof options.body === 'object') {
options.body = JSON.stringify(options.body);
}
request(options, (error, response, body) => {
var httpResponse = {};
httpResponse.status = response.statusCode;
httpResponse.headers = response.headers;
httpResponse.buffer = new Buffer(response.body);
httpResponse.cookies = response.headers["set-cookie"];
httpResponse.text = response.body;
try {
httpResponse.data = JSON.parse(response.body);
} catch (e) {}
// Consider <200 && >= 400 as errors
if (error || httpResponse.status <200 || httpResponse.status >=400) {
if (callbacks.error) {
return callbacks.error(httpResponse);
}
return promise.reject(httpResponse);
} else {
if (callbacks.success) {
return callbacks.success(httpResponse);
}
return promise.resolve(httpResponse);
}
});
return promise;
};
+21 -33
View File
@@ -6,11 +6,12 @@ var batch = require('./batch'),
DatabaseAdapter = require('./DatabaseAdapter'),
express = require('express'),
FilesAdapter = require('./FilesAdapter'),
S3Adapter = require('./S3Adapter'),
middlewares = require('./middlewares'),
multer = require('multer'),
Parse = require('parse/node').Parse,
PromiseRouter = require('./PromiseRouter'),
request = require('request');
httpRequest = require('./httpRequest');
// Mutate the Parse object to add the Cloud Code handlers
addParseCloud();
@@ -23,7 +24,9 @@ addParseCloud();
// and delete
// "databaseURI": a uri like mongodb://localhost:27017/dbname to tell us
// what database this Parse API connects to.
// "cloud": relative location to cloud code to require
// "cloud": relative location to cloud code to require, or a function
// that is given an instance of Parse as a parameter. Use this instance of Parse
// to register your cloud code hooks and functions.
// "appId": the application id to host
// "masterKey": the master key for requests to this app
// "facebookAppIds": an array of valid Facebook Application IDs, required
@@ -47,11 +50,18 @@ function ParseServer(args) {
FilesAdapter.setAdapter(args.filesAdapter);
}
if (args.databaseURI) {
DatabaseAdapter.setDatabaseURI(args.databaseURI);
DatabaseAdapter.setAppDatabaseURI(args.appId, args.databaseURI);
}
if (args.cloud) {
addParseCloud();
require(args.cloud);
if (typeof args.cloud === 'function') {
args.cloud(Parse)
} else if (typeof args.cloud === 'string') {
require(args.cloud);
} else {
throw "argument 'cloud' must either be a string or a function";
}
}
cache.apps[args.appId] = {
@@ -72,6 +82,9 @@ function ParseServer(args) {
// Initialize the node client SDK automatically
Parse.initialize(args.appId, args.javascriptKey || '', args.masterKey);
if(args.serverURL) {
Parse.serverURL = args.serverURL;
}
// This app serves the Parse API directly.
// It's the equivalent of https://api.parse.com/1 in the hosted Parse API.
@@ -101,6 +114,7 @@ function ParseServer(args) {
router.merge(require('./push'));
router.merge(require('./installations'));
router.merge(require('./functions'));
router.merge(require('./schemas'));
batch.mountOnto(router);
@@ -138,33 +152,7 @@ function addParseCloud() {
var className = getClassName(parseClass);
Parse.Cloud.Triggers.afterDelete[className] = handler;
};
Parse.Cloud.httpRequest = function(options) {
var promise = new Parse.Promise();
var callbacks = {
success: options.success,
error: options.error
};
delete options.success;
delete options.error;
if (options.uri && !options.url) {
options.uri = options.url;
delete options.url;
}
request(options, (error, response, body) => {
if (error) {
if (callbacks.error) {
return callbacks.error(error);
}
return promise.reject(error);
} else {
if (callbacks.success) {
return callbacks.success(body);
}
return promise.resolve(body);
}
});
return promise;
};
Parse.Cloud.httpRequest = httpRequest;
global.Parse = Parse;
}
@@ -176,6 +164,6 @@ function getClassName(parseClass) {
}
module.exports = {
ParseServer: ParseServer
ParseServer: ParseServer,
S3Adapter: S3Adapter
};
+21 -10
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "2.0.2",
"version": "2.0.7",
"description": "An express module providing a Parse-compatible API server",
"main": "index.js",
"repository": {
@@ -9,24 +9,35 @@
},
"license": "BSD-3-Clause",
"dependencies": {
"bcrypt": "~0.8",
"body-parser": "~1.12.4",
"deepcopy": "^0.5.0",
"express": "~4.2.x",
"aws-sdk": "~2.2.33",
"bcrypt-nodejs": "0.0.3",
"body-parser": "^1.14.2",
"deepcopy": "^0.6.1",
"express": "^4.13.4",
"hat": "~0.0.3",
"mime": "^1.3.4",
"mongodb": "~2.0.33",
"multer": "~0.1.8",
"parse": "~1.6.12",
"mongodb": "~2.1.0",
"multer": "^1.1.0",
"parse": "^1.7.0",
"request": "^2.65.0"
},
"devDependencies": {
"jasmine": "^2.3.2"
"codecov": "^1.0.1",
"deep-diff": "^0.3.3",
"istanbul": "^0.4.2",
"jasmine": "^2.3.2",
"mongodb-runner": "^3.1.15"
},
"scripts": {
"test": "TESTING=1 jasmine"
"pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start",
"test": "TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
"posttest": "mongodb-runner stop",
"start": "./bin/parse-server"
},
"engines": {
"node": ">=4.1"
},
"bin": {
"parse-server": "./bin/parse-server"
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
// Tools for encrypting and decrypting passwords.
// Basically promise-friendly wrappers for bcrypt.
var bcrypt = require('bcrypt');
var bcrypt = require('bcrypt-nodejs');
// Returns a promise for a hashed password string.
function hash(password) {
return new Promise(function(fulfill, reject) {
bcrypt.hash(password, 8, function(err, hashedPassword) {
bcrypt.hash(password, null, null, function(err, hashedPassword) {
if (err) {
reject(err);
} else {
+69
View File
@@ -0,0 +1,69 @@
// schemas.js
var express = require('express'),
PromiseRouter = require('./PromiseRouter');
var router = new PromiseRouter();
function mongoFieldTypeToApiResponseType(type) {
if (type[0] === '*') {
return {
type: 'Pointer',
targetClass: type.slice(1),
};
}
if (type.startsWith('relation<')) {
return {
type: 'Relation',
targetClass: type.slice('relation<'.length, type.length - 1),
};
}
switch (type) {
case 'number': return {type: 'Number'};
case 'string': return {type: 'String'};
case 'boolean': return {type: 'Boolean'};
case 'date': return {type: 'Date'};
case 'object': return {type: 'Object'};
case 'array': return {type: 'Array'};
case 'geopoint': return {type: 'GeoPoint'};
case 'file': return {type: 'File'};
}
}
function mongoSchemaAPIResponseFields(schema) {
fieldNames = Object.keys(schema).filter(key => key !== '_id');
response = {};
fieldNames.forEach(fieldName => {
response[fieldName] = mongoFieldTypeToApiResponseType(schema[fieldName]);
});
response.ACL = {type: 'ACL'};
response.createdAt = {type: 'Date'};
response.updatedAt = {type: 'Date'};
response.objectId = {type: 'String'};
return response;
}
function mongoSchemaToSchemaAPIResponse(schema) {
return {
className: schema._id,
fields: mongoSchemaAPIResponseFields(schema),
};
}
function getAllSchemas(req) {
if (!req.auth.isMaster) {
return Promise.resolve({
status: 401,
response: {error: 'unauthorized'},
});
}
return req.config.database.collection('_SCHEMA')
.then(coll => coll.find({}).toArray())
.then(schemas => ({response: {
results: schemas.map(mongoSchemaToSchemaAPIResponse)
}}));
}
router.route('GET', '/schemas', getAllSchemas);
module.exports = router;
+29 -1
View File
@@ -153,12 +153,26 @@ describe('miscellaneous', function() {
});
it('basic beforeSave rejection', function(done) {
var obj = new Parse.Object('BeforeSaveFailure');
var obj = new Parse.Object('BeforeSaveFail');
obj.set('foo', 'bar');
obj.save().then(() => {
fail('Should not have been able to save BeforeSaveFailure class.');
done();
}, () => {
done();
})
});
it('basic beforeSave rejection via promise', function(done) {
var obj = new Parse.Object('BeforeSaveFailWithPromise');
obj.set('foo', 'bar');
obj.save().then(function() {
fail('Should not have been able to save BeforeSaveFailure class.');
done();
}, function(error) {
expect(error.code).toEqual(Parse.Error.SCRIPT_FAILED);
expect(error.message).toEqual('Nope');
done();
})
});
@@ -250,6 +264,20 @@ describe('miscellaneous', function() {
// We should have been able to fetch the object again
fail(error);
});
})
it('basic beforeDelete rejection via promise', function(done) {
var obj = new Parse.Object('BeforeDeleteFailWithPromise');
obj.set('foo', 'bar');
obj.save().then(function() {
fail('Should not have been able to save BeforeSaveFailure class.');
done();
}, function(error) {
expect(error.code).toEqual(Parse.Error.SCRIPT_FAILED);
expect(error.message).toEqual('Nope');
done();
})
});
it('test beforeDelete success', function(done) {
+43
View File
@@ -287,4 +287,47 @@ describe('Parse.GeoPoint testing', () => {
done();
});
});
it('supports a sub-object with a geo point', done => {
var point = new Parse.GeoPoint(44.0, -11.0);
var obj = new TestObject();
obj.set('subobject', { location: point });
obj.save(null, {
success: function() {
var query = new Parse.Query(TestObject);
query.find({
success: function(results) {
equal(results.length, 1);
var pointAgain = results[0].get('subobject')['location'];
ok(pointAgain);
equal(pointAgain.latitude, 44.0);
equal(pointAgain.longitude, -11.0);
done();
}
});
}
});
});
it('supports array of geo points', done => {
var point1 = new Parse.GeoPoint(44.0, -11.0);
var point2 = new Parse.GeoPoint(22.0, -55.0);
var obj = new TestObject();
obj.set('locations', [ point1, point2 ]);
obj.save(null, {
success: function() {
var query = new Parse.Query(TestObject);
query.find({
success: function(results) {
equal(results.length, 1);
var locations = results[0].get('locations');
expect(locations.length).toEqual(2);
expect(locations[0]).toEqual(point1);
expect(locations[1]).toEqual(point2);
done();
}
});
}
});
});
});
+77 -92
View File
@@ -6,7 +6,7 @@
// Tests that involve sending password reset emails.
var request = require('request');
var crypto = require('../crypto');
var passwordCrypto = require('../password');
describe('Parse.User testing', () => {
it("user sign up class method", (done) => {
@@ -78,7 +78,8 @@ describe('Parse.User testing', () => {
sessionToken = newUser.getSessionToken();
ok(sessionToken);
Parse.User.logOut();
return Parse.User.logOut();
}).then(() => {
ok(!Parse.User.current());
return Parse.User.become(sessionToken);
@@ -91,7 +92,8 @@ describe('Parse.User testing', () => {
equal(newUser.get("username"), "Jason");
equal(newUser.get("code"), "red");
Parse.User.logOut();
return Parse.User.logOut();
}).then(() => {
ok(!Parse.User.current());
return Parse.User.become("somegarbage");
@@ -236,22 +238,20 @@ describe('Parse.User testing', () => {
user.set("password", "asdf");
user.set("email", "asdf@example.com");
user.set("username", "zxcv");
user.signUp(null, {
success: function() {
var currentUser = Parse.User.current();
equal(user.id, currentUser.id);
ok(user.getSessionToken());
user.signUp().then(() => {
var currentUser = Parse.User.current();
equal(user.id, currentUser.id);
ok(user.getSessionToken());
var currentUserAgain = Parse.User.current();
// should be the same object
equal(currentUser, currentUserAgain);
var currentUserAgain = Parse.User.current();
// should be the same object
equal(currentUser, currentUserAgain);
// test logging out the current user
Parse.User.logOut();
equal(Parse.User.current(), null);
done();
}
// test logging out the current user
return Parse.User.logOut();
}).then(() => {
equal(Parse.User.current(), null);
done();
});
});
@@ -268,50 +268,39 @@ describe('Parse.User testing', () => {
user2.set("password", "password");
user3.set("password", "password");
user1.signUp(null, {
success: function () {
equal(user1.isCurrent(), true);
equal(user2.isCurrent(), false);
equal(user3.isCurrent(), false);
user2.signUp(null, {
success: function() {
equal(user1.isCurrent(), false);
equal(user2.isCurrent(), true);
equal(user3.isCurrent(), false);
user3.signUp(null, {
success: function() {
equal(user1.isCurrent(), false);
equal(user2.isCurrent(), false);
equal(user3.isCurrent(), true);
Parse.User.logIn("a", "password", {
success: function(user1) {
equal(user1.isCurrent(), true);
equal(user2.isCurrent(), false);
equal(user3.isCurrent(), false);
Parse.User.logIn("b", "password", {
success: function(user2) {
equal(user1.isCurrent(), false);
equal(user2.isCurrent(), true);
equal(user3.isCurrent(), false);
Parse.User.logIn("b", "password", {
success: function(user3) {
equal(user1.isCurrent(), false);
equal(user2.isCurrent(), true);
equal(user3.isCurrent(), true);
Parse.User.logOut();
equal(user3.isCurrent(), false);
done();
}
});
}
});
}
});
}
});
}
});
}
user1.signUp().then(() => {
equal(user1.isCurrent(), true);
equal(user2.isCurrent(), false);
equal(user3.isCurrent(), false);
return user2.signUp();
}).then(() => {
equal(user1.isCurrent(), false);
equal(user2.isCurrent(), true);
equal(user3.isCurrent(), false);
return user3.signUp();
}).then(() => {
equal(user1.isCurrent(), false);
equal(user2.isCurrent(), false);
equal(user3.isCurrent(), true);
return Parse.User.logIn("a", "password");
}).then(() => {
equal(user1.isCurrent(), true);
equal(user2.isCurrent(), false);
equal(user3.isCurrent(), false);
return Parse.User.logIn("b", "password");
}).then(() => {
equal(user1.isCurrent(), false);
equal(user2.isCurrent(), true);
equal(user3.isCurrent(), false);
return Parse.User.logIn("b", "password");
}).then(() => {
equal(user1.isCurrent(), false);
equal(user2.isCurrent(), true);
equal(user3.isCurrent(), false);
return Parse.User.logOut();
}).then(() => {
equal(user2.isCurrent(), false);
done();
});
});
@@ -589,28 +578,24 @@ describe('Parse.User testing', () => {
it("user loaded from localStorage from login", (done) => {
var id;
Parse.User.signUp("alice", "password").then((alice) => {
id = alice.id;
return Parse.User.logOut();
}).then(() => {
return Parse.User.logIn("alice", "password");
}).then((user) => {
// Force the current user to read from disk
delete Parse.User._currentUser;
delete Parse.User._currentUserMatchesDisk;
Parse.User.signUp("alice", "password", null, {
success: function(alice) {
var id = alice.id;
Parse.User.logOut();
Parse.User.logIn("alice", "password", {
success: function(user) {
// Force the current user to read from disk
delete Parse.User._currentUser;
delete Parse.User._currentUserMatchesDisk;
var userFromDisk = Parse.User.current();
equal(userFromDisk.get("password"), undefined,
"password should not be in attributes");
equal(userFromDisk.id, id, "id should be set");
ok(userFromDisk.getSessionToken(),
"currentUser should have a sessionToken");
done();
}
});
}
var userFromDisk = Parse.User.current();
equal(userFromDisk.get("password"), undefined,
"password should not be in attributes");
equal(userFromDisk.id, id, "id should be set");
ok(userFromDisk.getSessionToken(),
"currentUser should have a sessionToken");
done();
});
});
@@ -620,8 +605,8 @@ describe('Parse.User testing', () => {
Parse.User.signUp("alice", "password", null).then(function(alice) {
id = alice.id;
Parse.User.logOut();
return Parse.User.logOut();
}).then(() => {
return Parse.User.logIn("alice", "password");
}).then(function() {
// Simulate browser refresh by force-reloading user from localStorage
@@ -1306,13 +1291,13 @@ describe('Parse.User testing', () => {
});
});
it("querying for users doesn't get session tokens", (done) => {
notWorking("querying for users doesn't get session tokens", (done) => {
Parse.Promise.as().then(function() {
return Parse.User.signUp("finn", "human", { foo: "bar" });
}).then(function() {
Parse.User.logOut();
return Parse.User.logOut();
}).then(() => {
var user = new Parse.User();
user.set("username", "jake");
user.set("password", "dog");
@@ -1320,8 +1305,8 @@ describe('Parse.User testing', () => {
return user.signUp();
}).then(function() {
Parse.User.logOut();
return Parse.User.logOut();
}).then(() => {
var query = new Parse.Query(Parse.User);
return query.find();
@@ -1351,7 +1336,7 @@ describe('Parse.User testing', () => {
var b = JSON.parse(body);
expect(b.results.length).toEqual(1);
var user = b.results[0];
expect(Object.keys(user).length).toEqual(5);
expect(Object.keys(user).length).toEqual(6);
done();
});
});
@@ -1560,7 +1545,7 @@ describe('Parse.User testing', () => {
it('password format matches hosted parse', (done) => {
var hashed = '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie';
crypto.compare('test', hashed)
passwordCrypto.compare('test', hashed)
.then((pass) => {
expect(pass).toBe(true);
done();
@@ -1574,7 +1559,7 @@ describe('Parse.User testing', () => {
var sessionToken = null;
Parse.Promise.as().then(function() {
return Parse.User.signUp("fosco", "parse");
return Parse.User.signUp("fosco", "parse");
}).then(function(newUser) {
equal(Parse.User.current(), newUser);
sessionToken = newUser.getSessionToken();
+4 -5
View File
@@ -1,6 +1,6 @@
// Sets up a Parse API server for testing.
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
var cache = require('../cache');
var DatabaseAdapter = require('../DatabaseAdapter');
@@ -46,8 +46,7 @@ beforeEach(function(done) {
});
afterEach(function(done) {
Parse.User.logOut();
Parse.Promise.as().then(() => {
Parse.User.logOut().then(() => {
return clearData();
}).then(() => {
done();
@@ -153,7 +152,7 @@ function normalize(obj) {
return '[' + obj.map(normalize).join(', ') + ']';
}
var answer = '{';
for (key of Object.keys(obj).sort()) {
for (var key of Object.keys(obj).sort()) {
answer += key + ': ';
answer += normalize(obj[key]);
answer += ', ';
@@ -192,7 +191,7 @@ function mockFacebook() {
function clearData() {
var promises = [];
for (conn in DatabaseAdapter.dbConnections) {
for (var conn in DatabaseAdapter.dbConnections) {
promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything());
}
return Promise.all(promises);
+110
View File
@@ -0,0 +1,110 @@
var request = require('request');
var dd = require('deep-diff');
describe('schemas', () => {
it('requires the master key to get all schemas', (done) => {
request.get({
url: 'http://localhost:8378/1/schemas',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
},
}, (error, response, body) => {
expect(response.statusCode).toEqual(401);
expect(body.error).toEqual('unauthorized');
done();
});
});
it('responds with empty list when there are no schemas', done => {
request.get({
url: 'http://localhost:8378/1/schemas',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
}, (error, response, body) => {
expect(body.results).toEqual([]);
done();
});
});
it('responds with a list of schemas after creating objects', done => {
var obj1 = new Parse.Object('HasAllPOD');
obj1.set('aNumber', 5);
obj1.set('aString', 'string');
obj1.set('aBool', true);
obj1.set('aDate', new Date());
obj1.set('aObject', {k1: 'value', k2: true, k3: 5});
obj1.set('aArray', ['contents', true, 5]);
obj1.set('aGeoPoint', new Parse.GeoPoint({latitude: 0, longitude: 0}));
obj1.set('aFile', new Parse.File('f.txt', { base64: 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=' }));
var obj1ACL = new Parse.ACL();
obj1ACL.setPublicWriteAccess(false);
obj1.setACL(obj1ACL);
obj1.save().then(savedObj1 => {
var obj2 = new Parse.Object('HasPointersAndRelations');
obj2.set('aPointer', savedObj1);
var relation = obj2.relation('aRelation');
relation.add(obj1);
return obj2.save();
}).then(() => {
request.get({
url: 'http://localhost:8378/1/schemas',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
}, (error, response, body) => {
var expected = {
results: [
{
className: 'HasAllPOD',
fields: {
//Default fields
ACL: {type: 'ACL'},
createdAt: {type: 'Date'},
updatedAt: {type: 'Date'},
objectId: {type: 'String'},
//Custom fields
aNumber: {type: 'Number'},
aString: {type: 'String'},
aBool: {type: 'Boolean'},
aDate: {type: 'Date'},
aObject: {type: 'Object'},
aArray: {type: 'Array'},
aGeoPoint: {type: 'GeoPoint'},
aFile: {type: 'File'}
},
},
{
className: 'HasPointersAndRelations',
fields: {
//Default fields
ACL: {type: 'ACL'},
createdAt: {type: 'Date'},
updatedAt: {type: 'Date'},
objectId: {type: 'String'},
//Custom fields
aPointer: {
type: 'Pointer',
targetClass: 'HasAllPOD',
},
aRelation: {
type: 'Relation',
targetClass: 'HasAllPOD',
},
},
}
]
};
expect(body).toEqual(expected);
done();
})
});
});
});
+77 -16
View File
@@ -2,16 +2,18 @@
var transform = require('../transform');
var dummyConfig = {
schema: {
var dummySchema = {
data: {},
getExpectedType: function(className, key) {
if (key == 'userPointer') {
return '*_User';
} else if (key == 'picture') {
return 'file';
} else if (key == 'location') {
return 'geopoint';
}
return;
}
}
};
@@ -19,7 +21,7 @@ describe('transformCreate', () => {
it('a basic number', (done) => {
var input = {five: 5};
var output = transform.transformCreate(dummyConfig, null, input);
var output = transform.transformCreate(dummySchema, null, input);
jequal(input, output);
done();
});
@@ -29,7 +31,7 @@ describe('transformCreate', () => {
createdAt: "2015-10-06T21:24:50.332Z",
updatedAt: "2015-10-06T21:24:50.332Z"
};
var output = transform.transformCreate(dummyConfig, null, input);
var output = transform.transformCreate(dummySchema, null, input);
expect(output._created_at instanceof Date).toBe(true);
expect(output._updated_at instanceof Date).toBe(true);
done();
@@ -41,29 +43,52 @@ describe('transformCreate', () => {
objectId: 'myId',
className: 'Blah',
};
var out = transform.transformCreate(dummyConfig, null, {pointers: [pointer]});
var out = transform.transformCreate(dummySchema, null, {pointers: [pointer]});
jequal([pointer], out.pointers);
done();
});
it('a delete op', (done) => {
var input = {deleteMe: {__op: 'Delete'}};
var output = transform.transformCreate(dummyConfig, null, input);
var output = transform.transformCreate(dummySchema, null, input);
jequal(output, {});
done();
});
it('basic ACL', (done) => {
var input = {ACL: {'0123': {'read': true, 'write': true}}};
var output = transform.transformCreate(dummyConfig, null, input);
var output = transform.transformCreate(dummySchema, null, input);
// This just checks that it doesn't crash, but it should check format.
done();
});
describe('GeoPoints', () => {
it('plain', (done) => {
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
var out = transform.transformCreate(dummySchema, null, {location: geoPoint});
expect(out.location).toEqual([180, -180]);
done();
});
it('in array', (done) => {
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
var out = transform.transformCreate(dummySchema, null, {locations: [geoPoint, geoPoint]});
expect(out.locations).toEqual([geoPoint, geoPoint]);
done();
});
it('in sub-object', (done) => {
var geoPoint = {__type: 'GeoPoint', longitude: 180, latitude: -180};
var out = transform.transformCreate(dummySchema, null, { locations: { start: geoPoint }});
expect(out).toEqual({ locations: { start: geoPoint } });
done();
});
});
});
describe('transformWhere', () => {
it('objectId', (done) => {
var out = transform.transformWhere(dummyConfig, null, {objectId: 'foo'});
var out = transform.transformWhere(dummySchema, null, {objectId: 'foo'});
expect(out._id).toEqual('foo');
done();
});
@@ -72,7 +97,7 @@ describe('transformWhere', () => {
var input = {
objectId: {'$in': ['one', 'two', 'three']},
};
var output = transform.transformWhere(dummyConfig, null, input);
var output = transform.transformWhere(dummySchema, null, input);
jequal(input.objectId, output._id);
done();
});
@@ -81,17 +106,53 @@ describe('transformWhere', () => {
describe('untransformObject', () => {
it('built-in timestamps', (done) => {
var input = {createdAt: new Date(), updatedAt: new Date()};
var output = transform.untransformObject(dummyConfig, null, input);
var output = transform.untransformObject(dummySchema, null, input);
expect(typeof output.createdAt).toEqual('string');
expect(typeof output.updatedAt).toEqual('string');
done();
});
it('pointer', (done) => {
var input = {_p_userPointer: '_User$123'};
var output = transform.untransformObject(dummySchema, null, input);
expect(typeof output.userPointer).toEqual('object');
expect(output.userPointer).toEqual(
{__type: 'Pointer', className: '_User', objectId: '123'}
);
done();
});
it('null pointer', (done) => {
var input = {_p_userPointer: null};
var output = transform.untransformObject(dummySchema, null, input);
expect(output.userPointer).toBeUndefined();
done();
});
it('file', (done) => {
var input = {picture: 'pic.jpg'};
var output = transform.untransformObject(dummySchema, null, input);
expect(typeof output.picture).toEqual('object');
expect(output.picture).toEqual({__type: 'File', name: 'pic.jpg'});
done();
});
it('geopoint', (done) => {
var input = {location: [180, -180]};
var output = transform.untransformObject(dummySchema, null, input);
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'GeoPoint', longitude: 180, latitude: -180}
);
done();
});
});
describe('transformKey', () => {
it('throws out _password', (done) => {
try {
transform.transformKey(dummyConfig, '_User', '_password');
transform.transformKey(dummySchema, '_User', '_password');
fail('should have thrown');
} catch (e) {
done();
@@ -105,7 +166,7 @@ describe('transform schema key changes', () => {
var input = {
somePointer: {__type: 'Pointer', className: 'Micro', objectId: 'oft'}
};
var output = transform.transformCreate(dummyConfig, null, input);
var output = transform.transformCreate(dummySchema, null, input);
expect(typeof output._p_somePointer).toEqual('string');
expect(output._p_somePointer).toEqual('Micro$oft');
done();
@@ -115,7 +176,7 @@ describe('transform schema key changes', () => {
var input = {
userPointer: {__type: 'Pointer', className: '_User', objectId: 'qwerty'}
};
var output = transform.transformCreate(dummyConfig, null, input);
var output = transform.transformCreate(dummySchema, null, input);
expect(typeof output._p_userPointer).toEqual('string');
expect(output._p_userPointer).toEqual('_User$qwerty');
done();
@@ -128,7 +189,7 @@ describe('transform schema key changes', () => {
"Kevin": { "write": true }
}
};
var output = transform.transformCreate(dummyConfig, null, input);
var output = transform.transformCreate(dummySchema, null, input);
expect(typeof output._rperm).toEqual('object');
expect(typeof output._wperm).toEqual('object');
expect(output.ACL).toBeUndefined();
@@ -142,7 +203,7 @@ describe('transform schema key changes', () => {
_rperm: ["*"],
_wperm: ["Kevin"]
};
var output = transform.untransformObject(dummyConfig, null, input);
var output = transform.untransformObject(dummySchema, null, input);
expect(typeof output.ACL).toEqual('object');
expect(output._rperm).toBeUndefined();
expect(output._wperm).toBeUndefined();
+111 -29
View File
@@ -48,7 +48,7 @@ function transformKeyValue(schema, className, restKey, restValue, options) {
break;
case 'expiresAt':
case '_expiresAt':
key = '_expiresAt';
key = 'expiresAt';
timeField = true;
break;
case '_rperm':
@@ -335,6 +335,7 @@ function transformAtom(atom, force, options) {
return atom;
case 'undefined':
return atom;
case 'symbol':
case 'function':
throw new Parse.Error(Parse.Error.INVALID_JSON,
@@ -362,20 +363,17 @@ function transformAtom(atom, force, options) {
objectId: atom.objectId
};
}
if (atom.__type == 'Date') {
return new Date(atom.iso);
if (DateCoder.isValidJSON(atom)) {
return DateCoder.JSONToDatabase(atom);
}
if (atom.__type == 'GeoPoint') {
return [atom.longitude, atom.latitude];
if (BytesCoder.isValidJSON(atom)) {
return BytesCoder.JSONToDatabase(atom);
}
if (atom.__type == 'Bytes') {
return new mongodb.Binary(new Buffer(atom.base64, 'base64'));
if (GeoPointCoder.isValidJSON(atom)) {
return (inArray || inObject ? atom : GeoPointCoder.JSONToDatabase(atom));
}
if (atom.__type == 'File') {
if (!inArray && !inObject) {
return atom.name;
}
return atom;
if (FileCoder.isValidJSON(atom)) {
return (inArray || inObject ? atom : FileCoder.JSONToDatabase(atom));
}
if (force) {
@@ -616,11 +614,8 @@ function untransformObject(schema, className, mongoObject) {
return Parse._encode(mongoObject);
}
if (mongoObject instanceof mongodb.Binary) {
return {
__type: 'Bytes',
base64: mongoObject.buffer.toString('base64')
};
if (BytesCoder.isValidDatabaseObject(mongoObject)) {
return BytesCoder.databaseToJSON(mongoObject);
}
var restObject = untransformACL(mongoObject);
@@ -676,6 +671,9 @@ function untransformObject(schema, className, mongoObject) {
console.log('Found a pointer in a non-pointer column, dropping it.', className, key);
break;
}
if (mongoObject[key] === null) {
break;
}
var objData = mongoObject[key].split('$');
var newClass = (expected ? expected.substring(1) : objData[0]);
if (objData[0] !== newClass) {
@@ -689,21 +687,17 @@ function untransformObject(schema, className, mongoObject) {
break;
} else if (key[0] == '_' && key != '__type') {
throw ('bad key in untransform: ' + key);
//} else if (mongoObject[key] === null) {
//break;
} else {
var expected = schema.getExpectedType(className, key);
if (expected == 'file') {
restObject[key] = {
__type: 'File',
name: mongoObject[key]
};
var expectedType = schema.getExpectedType(className, key);
var value = mongoObject[key];
if (expectedType === 'file' && FileCoder.isValidDatabaseObject(value)) {
restObject[key] = FileCoder.databaseToJSON(value);
break;
}
if (expected == 'geopoint') {
restObject[key] = {
__type: 'GeoPoint',
latitude: mongoObject[key][1],
longitude: mongoObject[key][0]
};
if (expectedType === 'geopoint' && GeoPointCoder.isValidDatabaseObject(value)) {
restObject[key] = GeoPointCoder.databaseToJSON(value);
break;
}
}
@@ -717,6 +711,94 @@ function untransformObject(schema, className, mongoObject) {
}
}
var DateCoder = {
JSONToDatabase(json) {
return new Date(json.iso);
},
isValidJSON(value) {
return (typeof value === 'object' &&
value !== null &&
value.__type === 'Date'
);
}
};
var BytesCoder = {
databaseToJSON(object) {
return {
__type: 'Bytes',
base64: object.buffer.toString('base64')
};
},
isValidDatabaseObject(object) {
return (object instanceof mongodb.Binary);
},
JSONToDatabase(json) {
return new mongodb.Binary(new Buffer(json.base64, 'base64'));
},
isValidJSON(value) {
return (typeof value === 'object' &&
value !== null &&
value.__type === 'Bytes'
);
}
};
var GeoPointCoder = {
databaseToJSON(object) {
return {
__type: 'GeoPoint',
latitude: object[1],
longitude: object[0]
}
},
isValidDatabaseObject(object) {
return (object instanceof Array &&
object.length == 2
);
},
JSONToDatabase(json) {
return [ json.longitude, json.latitude ];
},
isValidJSON(value) {
return (typeof value === 'object' &&
value !== null &&
value.__type === 'GeoPoint'
);
}
};
var FileCoder = {
databaseToJSON(object) {
return {
__type: 'File',
name: object
}
},
isValidDatabaseObject(object) {
return (typeof object === 'string');
},
JSONToDatabase(json) {
return json.name;
},
isValidJSON(value) {
return (typeof value === 'object' &&
value !== null &&
value.__type === 'File'
);
}
};
module.exports = {
transformKey: transformKey,
transformCreate: transformCreate,
+2 -1
View File
@@ -57,7 +57,8 @@ var getResponseObject = function(request, resolve, reject) {
return resolve(response);
},
error: function(error) {
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, error);
var scriptError = new Parse.Error(Parse.Error.SCRIPT_FAILED, error);
return reject(scriptError);
}
}
};
+29 -5
View File
@@ -5,18 +5,21 @@ var Parse = require('parse/node').Parse;
var rack = require('hat').rack();
var Auth = require('./Auth');
var crypto = require('./crypto');
var passwordCrypto = require('./password');
var facebook = require('./facebook');
var PromiseRouter = require('./PromiseRouter');
var rest = require('./rest');
var RestWrite = require('./RestWrite');
var deepcopy = require('deepcopy');
var router = new PromiseRouter();
// Returns a promise for a {status, response, location} object.
function handleCreate(req) {
var data = deepcopy(req.body);
data.installationId = req.info.installationId;
return rest.create(req.config, req.auth,
'_User', req.body);
'_User', data);
}
// Returns a promise for a {response} object.
@@ -45,7 +48,7 @@ function handleLogIn(req) {
'Invalid username/password.');
}
user = results[0];
return crypto.compare(req.body.password, user.password);
return passwordCrypto.compare(req.body.password, user.password);
}).then((correct) => {
if (!correct) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND,
@@ -70,9 +73,13 @@ function handleLogIn(req) {
'authProvider': 'password'
},
restricted: false,
expiresAt: Parse._encode(expiresAt).iso,
installationId: req.info.installationId
expiresAt: Parse._encode(expiresAt)
};
if (req.info.installationId) {
sessionData.installationId = req.info.installationId
}
var create = new RestWrite(req.config, Auth.master(req.config),
'_Session', null, sessionData);
return create.execute();
@@ -157,6 +164,22 @@ function handleDelete(req) {
});
}
function handleLogOut(req) {
var success = {response: {}};
if (req.info && req.info.sessionToken) {
rest.find(req.config, Auth.master(req.config), '_Session',
{_session_token: req.info.sessionToken}
).then((records) => {
if (records.results && records.results.length) {
rest.del(req.config, Auth.master(req.config), '_Session',
records.results[0].id
);
}
});
}
return Promise.resolve(success);
}
function handleUpdate(req) {
return rest.update(req.config, req.auth, '_User',
req.params.objectId, req.body)
@@ -172,6 +195,7 @@ function notImplementedYet(req) {
router.route('POST', '/users', handleCreate);
router.route('GET', '/login', handleLogIn);
router.route('POST', '/logout', handleLogOut);
router.route('GET', '/users/me', handleMe);
router.route('GET', '/users/:objectId', handleGet);
router.route('PUT', '/users/:objectId', handleUpdate);