mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6a67e213c | ||
|
|
d9b547a5e4 | ||
|
|
36675a9598 | ||
|
|
c106ac6499 | ||
|
|
585ba8dcf8 | ||
|
|
72fa1f28a8 | ||
|
|
27560e14c7 | ||
|
|
4b1132ad40 | ||
|
|
21aef055b0 | ||
|
|
ee5e06cff9 | ||
|
|
437e7726ed | ||
|
|
3cd7f7dd73 | ||
|
|
0acd8c6c0f | ||
|
|
808d2ccbec | ||
|
|
098d671004 | ||
|
|
b2a5e67978 | ||
|
|
39fb0d02dd | ||
|
|
33a881764d | ||
|
|
3d989501b2 | ||
|
|
0547592952 | ||
|
|
c866afd016 | ||
|
|
bf1b886c85 | ||
|
|
6946d8670a | ||
|
|
e44106f3ed | ||
|
|
5a37c8307c | ||
|
|
33f8cdcd39 | ||
|
|
fec751b5e6 | ||
|
|
ba9d1f9eef | ||
|
|
c37db7b6ff | ||
|
|
f035307984 | ||
|
|
26a60f2341 | ||
|
|
456392df4a | ||
|
|
51dfc62ba8 | ||
|
|
d8b143379c | ||
|
|
a65f2d13ab | ||
|
|
4d4d2a2ce0 | ||
|
|
0a1237326d | ||
|
|
e5911d3cb9 | ||
|
|
c73d9f1e27 | ||
|
|
4bc921480f | ||
|
|
ca2ac2195d | ||
|
|
e5140deaed | ||
|
|
190dc5eb75 |
+11
@@ -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
|
||||
+2
-2
@@ -34,8 +34,8 @@ ExportAdapter.prototype.connect = function() {
|
||||
return this.connectionPromise;
|
||||
}
|
||||
|
||||
//http://regexr.com/3cn6m
|
||||
if (!this.mongoURI.match(/^mongodb:\/\/((.+):(.+)@)?([^:@]+):([^:]+)\/(.+?)$/gm)) {
|
||||
//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;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
## parse-server
|
||||
|
||||
[](https://travis-ci.org/ParsePlatform/parse-server)
|
||||
[](https://codecov.io/github/ParsePlatform/parse-server?branch=master)
|
||||
[](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
|
||||
|
||||
@@ -212,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++;
|
||||
|
||||
Executable
+43
@@ -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);
|
||||
});
|
||||
+19
-1
@@ -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();
|
||||
});
|
||||
|
||||
+3
-2
@@ -13,7 +13,8 @@ function handleCloudFunction(req) {
|
||||
var response = createResponseObject(resolve, reject);
|
||||
var request = {
|
||||
params: req.body || {},
|
||||
user: req.auth && req.auth.user || {}
|
||||
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)
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -11,7 +11,7 @@ var batch = require('./batch'),
|
||||
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();
|
||||
@@ -82,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.
|
||||
@@ -111,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);
|
||||
|
||||
@@ -148,36 +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;
|
||||
}
|
||||
if (typeof options.body === 'object') {
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+18
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.7",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -11,24 +11,33 @@
|
||||
"dependencies": {
|
||||
"aws-sdk": "~2.2.33",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"body-parser": "~1.12.4",
|
||||
"deepcopy": "^0.5.0",
|
||||
"express": "~4.2.x",
|
||||
"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": {
|
||||
"codecov": "^1.0.1",
|
||||
"deep-diff": "^0.3.3",
|
||||
"istanbul": "^0.4.2",
|
||||
"jasmine": "^2.3.2"
|
||||
"jasmine": "^2.3.2",
|
||||
"mongodb-runner": "^3.1.15"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources ./node_modules/.bin/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"
|
||||
}
|
||||
}
|
||||
|
||||
+69
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1291,7 +1291,7 @@ 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" });
|
||||
|
||||
@@ -1336,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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -61,6 +61,29 @@ describe('transformCreate', () => {
|
||||
// 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', () => {
|
||||
|
||||
+105
-28
@@ -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);
|
||||
@@ -695,20 +690,14 @@ function untransformObject(schema, className, mongoObject) {
|
||||
//} else if (mongoObject[key] === null) {
|
||||
//break;
|
||||
} else {
|
||||
var expected = schema.getExpectedType(className, key);
|
||||
if (expected == 'file' && mongoObject[key]) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -722,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
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user