diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..3c078e9f --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "es2015" + ] +} diff --git a/.gitignore b/.gitignore index 2d9748d6..318fed20 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,7 @@ node_modules *~ # WebStorm/IntelliJ -.idea \ No newline at end of file +.idea + +# Babel.js +lib/ diff --git a/.travis.yml b/.travis.yml index e34b4a44..dc081ced 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,7 @@ node_js: env: - MONGODB_VERSION=2.6.11 - MONGODB_VERSION=3.0.8 +cache: + directories: + - $HOME/.mongodb/versions/downloads after_success: ./node_modules/.bin/codecov diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39762126..6a1923cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ We really want Parse to be yours, to see it grow and thrive in the open source c ##### Please Do's * Take testing seriously! Aim to increase the test coverage with every pull request. -* Run the tests for the file you are working on with `TESTING=1 (repo-root)/node_modules/jasmine/bin/jasmine.js spec/MyFile.spec.js` +* Run the tests for the file you are working on with `npm test spec/MyFile.spec.js` * Run the tests for the whole project and look at the coverage report to make sure your tests are exhaustive by running `npm test` and looking at (project-root)/lcov-report/parse-server/FileUnderTest.js.html ##### Code of Conduct diff --git a/bin/parse-server b/bin/parse-server index c2606f4b..902e43b2 100755 --- a/bin/parse-server +++ b/bin/parse-server @@ -1,6 +1,6 @@ #!/usr/bin/env node var express = require('express'); -var ParseServer = require("../index").ParseServer; +var ParseServer = require("../lib/index").ParseServer; var app = express(); diff --git a/package.json b/package.json index 3d145ee4..689110c0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "parse-server", "version": "2.0.7", "description": "An express module providing a Parse-compatible API server", - "main": "index.js", + "main": "lib/index.js", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-server" @@ -11,6 +11,7 @@ "dependencies": { "apn": "^1.7.5", "aws-sdk": "~2.2.33", + "babel-runtime": "^6.5.0", "bcrypt-nodejs": "0.0.3", "body-parser": "^1.14.2", "deepcopy": "^0.6.1", @@ -19,23 +20,29 @@ "mime": "^1.3.4", "mongodb": "~2.1.0", "multer": "^1.1.0", + "node-gcm": "^0.14.0", "parse": "^1.7.0", "randomstring": "^1.1.3", - "node-gcm": "^0.14.0", "request": "^2.65.0" }, "devDependencies": { + "babel-cli": "^6.5.1", + "babel-core": "^6.5.1", + "babel-istanbul": "^0.6.0", + "babel-preset-es2015": "^6.5.0", + "babel-register": "^6.5.1", "codecov": "^1.0.1", "deep-diff": "^0.3.3", - "istanbul": "^0.4.2", "jasmine": "^2.3.2", "mongodb-runner": "^3.1.15" }, "scripts": { + "build": "./node_modules/.bin/babel src/ -d lib/", "pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start", - "test": "NODE_ENV=test TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine", + "test": "NODE_ENV=test TESTING=1 ./node_modules/.bin/babel-node ./node_modules/.bin/babel-istanbul cover -x **/spec/** ./node_modules/.bin/jasmine", "posttest": "mongodb-runner stop", - "start": "./bin/parse-server" + "start": "./bin/parse-server", + "prepublish": "npm run build" }, "engines": { "node": ">=4.1" diff --git a/spec/APNS.spec.js b/spec/APNS.spec.js index c50bb5c9..72490e97 100644 --- a/spec/APNS.spec.js +++ b/spec/APNS.spec.js @@ -1,4 +1,4 @@ -var APNS = require('../APNS'); +var APNS = require('../src/APNS'); describe('APNS', () => { it('can generate APNS notification', (done) => { diff --git a/spec/ExportAdapter.spec.js b/spec/ExportAdapter.spec.js index 95fbdd21..a4f3f9b6 100644 --- a/spec/ExportAdapter.spec.js +++ b/spec/ExportAdapter.spec.js @@ -1,4 +1,4 @@ -var ExportAdapter = require('../ExportAdapter'); +var ExportAdapter = require('../src/ExportAdapter'); describe('ExportAdapter', () => { it('can be constructed', (done) => { diff --git a/spec/GCM.spec.js b/spec/GCM.spec.js index d7484b0e..4bad883e 100644 --- a/spec/GCM.spec.js +++ b/spec/GCM.spec.js @@ -1,4 +1,4 @@ -var GCM = require('../GCM'); +var GCM = require('../src/GCM'); describe('GCM', () => { it('can generate GCM Payload without expiration time', (done) => { diff --git a/spec/ParseACL.spec.js b/spec/ParseACL.spec.js index fead537e..370550c0 100644 --- a/spec/ParseACL.spec.js +++ b/spec/ParseACL.spec.js @@ -251,6 +251,9 @@ describe('Parse.ACL', () => { equal(results.length, 1); var result = results[0]; ok(result); + if (!result) { + return fail(); + } equal(result.id, object.id); equal(result.getACL().getReadAccess(user), true); equal(result.getACL().getWriteAccess(user), true); diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 24edf38f..8670bdd2 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -1,7 +1,7 @@ // A bunch of different tests are in here - it isn't very thematic. // It would probably be better to refactor them into different files. -var DatabaseAdapter = require('../DatabaseAdapter'); +var DatabaseAdapter = require('../src/DatabaseAdapter'); var request = require('request'); describe('miscellaneous', function() { diff --git a/spec/ParseInstallation.spec.js b/spec/ParseInstallation.spec.js index 6d8e6162..91bb9a23 100644 --- a/spec/ParseInstallation.spec.js +++ b/spec/ParseInstallation.spec.js @@ -1,12 +1,12 @@ // These tests check the Installations functionality of the REST API. // Ported from installation_collection_test.go -var auth = require('../Auth'); -var cache = require('../cache'); -var Config = require('../Config'); -var DatabaseAdapter = require('../DatabaseAdapter'); +var auth = require('../src/Auth'); +var cache = require('../src/cache'); +var Config = require('../src/Config'); +var DatabaseAdapter = require('../src/DatabaseAdapter'); var Parse = require('parse/node').Parse; -var rest = require('../rest'); +var rest = require('../src/rest'); var config = new Config('test'); var database = DatabaseAdapter.getDatabaseConnection('test'); diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 88c1f53a..f5b6dc1a 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -2056,7 +2056,7 @@ describe('Parse.Query testing', () => { }); }); - it('query match on array value', (done) => { + it('query match on array with single object', (done) => { var target = {__type: 'Pointer', className: 'TestObject', objectId: 'abc123'}; var obj = new Parse.Object('TestObject'); obj.set('someObjs', [target]); @@ -2072,4 +2072,20 @@ describe('Parse.Query testing', () => { }); }); + it('query match on array with multiple objects', (done) => { + var target1 = {__type: 'Pointer', className: 'TestObject', objectId: 'abc'}; + var target2 = {__type: 'Pointer', className: 'TestObject', objectId: '123'}; + var obj= new Parse.Object('TestObject'); + obj.set('someObjs', [target1, target2]); + obj.save().then(() => { + var query = new Parse.Query('TestObject'); + query.equalTo('someObjs', target1); + return query.find(); + }).then((results) => { + expect(results.length).toEqual(1); + done(); + }, (error) => { + console.log(error); + }); + }); }); diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index b364adf1..c9f25bd8 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -6,7 +6,7 @@ // Tests that involve sending password reset emails. var request = require('request'); -var passwordCrypto = require('../password'); +var passwordCrypto = require('../src/password'); describe('Parse.User testing', () => { it("user sign up class method", (done) => { diff --git a/spec/RestCreate.spec.js b/spec/RestCreate.spec.js index 59de11ea..24455507 100644 --- a/spec/RestCreate.spec.js +++ b/spec/RestCreate.spec.js @@ -1,10 +1,10 @@ // These tests check the "create" functionality of the REST API. -var auth = require('../Auth'); -var cache = require('../cache'); -var Config = require('../Config'); -var DatabaseAdapter = require('../DatabaseAdapter'); +var auth = require('../src/Auth'); +var cache = require('../src/cache'); +var Config = require('../src/Config'); +var DatabaseAdapter = require('../src/DatabaseAdapter'); var Parse = require('parse/node').Parse; -var rest = require('../rest'); +var rest = require('../src/rest'); var request = require('request'); var config = new Config('test'); diff --git a/spec/RestQuery.spec.js b/spec/RestQuery.spec.js index 08d01766..b93a07d5 100644 --- a/spec/RestQuery.spec.js +++ b/spec/RestQuery.spec.js @@ -1,8 +1,8 @@ // These tests check the "find" functionality of the REST API. -var auth = require('../Auth'); -var cache = require('../cache'); -var Config = require('../Config'); -var rest = require('../rest'); +var auth = require('../src/Auth'); +var cache = require('../src/cache'); +var Config = require('../src/Config'); +var rest = require('../src/rest'); var config = new Config('test'); var nobody = auth.nobody(config); diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index ccc83525..636311a6 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -1,6 +1,6 @@ // These tests check that the Schema operates correctly. -var Config = require('../Config'); -var Schema = require('../Schema'); +var Config = require('../src/Config'); +var Schema = require('../src/Schema'); var dd = require('deep-diff'); var config = new Config('test'); @@ -252,7 +252,7 @@ describe('Schema', () => { it('refuses to add fields with invalid pointer types', done => { config.database.loadSchema() .then(schema => schema.addClassIfNotExists('NewClass', { - foo: {type: 'Pointer'}, + foo: {type: 'Pointer'} })) .catch(error => { expect(error.code).toEqual(135); @@ -398,7 +398,7 @@ describe('Schema', () => { config.database.loadSchema() .then(schema => schema.addClassIfNotExists('NewClass', { geo1: {type: 'GeoPoint'}, - geo2: {type: 'GeoPoint'}, + geo2: {type: 'GeoPoint'} })) .catch(error => { expect(error.code).toEqual(Parse.Error.INCORRECT_TYPE); diff --git a/spec/helper.js b/spec/helper.js index cca4d1a5..3e6c6d98 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -2,11 +2,11 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000; -var cache = require('../cache'); -var DatabaseAdapter = require('../DatabaseAdapter'); +var cache = require('../src/cache'); +var DatabaseAdapter = require('../src/DatabaseAdapter'); var express = require('express'); -var facebook = require('../facebook'); -var ParseServer = require('../index').ParseServer; +var facebook = require('../src/facebook'); +var ParseServer = require('../src/index').ParseServer; var databaseURI = process.env.DATABASE_URI; var cloudMain = process.env.CLOUD_CODE_MAIN || './cloud/main.js'; diff --git a/spec/push.spec.js b/spec/push.spec.js index ba5b533b..a2ea41b5 100644 --- a/spec/push.spec.js +++ b/spec/push.spec.js @@ -1,4 +1,4 @@ -var push = require('../push'); +var push = require('../src/push'); describe('push', () => { it('can check valid master key of request', (done) => { diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 8c7434da..68ac31c9 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -1,5 +1,7 @@ +var Parse = require('parse/node').Parse; var request = require('request'); var dd = require('deep-diff'); + var hasAllPODobject = () => { var obj = new Parse.Object('HasAllPOD'); obj.set('aNumber', 5); @@ -14,9 +16,9 @@ var hasAllPODobject = () => { objACL.setPublicWriteAccess(false); obj.setACL(objACL); return obj; -} +}; -var expectedResponseForHasAllPOD = { +var plainOldDataSchema = { className: 'HasAllPOD', fields: { //Default fields @@ -33,10 +35,10 @@ var expectedResponseForHasAllPOD = { aArray: {type: 'Array'}, aGeoPoint: {type: 'GeoPoint'}, aFile: {type: 'File'} - }, + } }; -var expectedResponseforHasPointersAndRelations = { +var pointersAndRelationsSchema = { className: 'HasPointersAndRelations', fields: { //Default fields @@ -56,17 +58,30 @@ var expectedResponseforHasPointersAndRelations = { }, } +var noAuthHeaders = { + 'X-Parse-Application-Id': 'test', +}; + +var restKeyHeaders = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', +}; + +var masterKeyHeaders = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', +}; + 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', - }, + headers: noAuthHeaders, }, (error, response, body) => { - expect(response.statusCode).toEqual(401); + //api.parse.com uses status code 401, but due to the lack of keys + //being necessary in parse-server, 403 makes more sense + expect(response.statusCode).toEqual(403); expect(body.error).toEqual('unauthorized'); done(); }); @@ -76,10 +91,7 @@ describe('schemas', () => { request.get({ url: 'http://localhost:8378/1/schemas/SomeSchema', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'rest', - }, + headers: restKeyHeaders, }, (error, response, body) => { expect(response.statusCode).toEqual(401); expect(body.error).toEqual('unauthorized'); @@ -87,14 +99,23 @@ describe('schemas', () => { }); }); + it('asks for the master key if you use the rest key', (done) => { + request.get({ + url: 'http://localhost:8378/1/schemas', + json: true, + headers: restKeyHeaders, + }, (error, response, body) => { + expect(response.statusCode).toEqual(401); + expect(body.error).toEqual('master key not specified'); + 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', - }, + headers: masterKeyHeaders, }, (error, response, body) => { expect(body.results).toEqual([]); done(); @@ -113,13 +134,10 @@ describe('schemas', () => { request.get({ url: 'http://localhost:8378/1/schemas', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, + headers: masterKeyHeaders, }, (error, response, body) => { var expected = { - results: [expectedResponseForHasAllPOD,expectedResponseforHasPointersAndRelations] + results: [plainOldDataSchema,pointersAndRelationsSchema] }; expect(body).toEqual(expected); done(); @@ -133,12 +151,9 @@ describe('schemas', () => { request.get({ url: 'http://localhost:8378/1/schemas/HasAllPOD', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, + headers: masterKeyHeaders, }, (error, response, body) => { - expect(body).toEqual(expectedResponseForHasAllPOD); + expect(body).toEqual(plainOldDataSchema); done(); }); }); @@ -150,10 +165,7 @@ describe('schemas', () => { request.get({ url: 'http://localhost:8378/1/schemas/HASALLPOD', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, + headers: masterKeyHeaders, }, (error, response, body) => { expect(response.statusCode).toEqual(400); expect(body).toEqual({ @@ -164,4 +176,146 @@ describe('schemas', () => { }); }); }); + + it('requires the master key to create a schema', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + json: true, + headers: noAuthHeaders, + body: { + className: 'MyClass', + } + }, (error, response, body) => { + expect(response.statusCode).toEqual(403); + expect(body.error).toEqual('unauthorized'); + done(); + }); + }); + + it('asks for the master key if you use the rest key', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + json: true, + headers: restKeyHeaders, + body: { + className: 'MyClass', + }, + }, (error, response, body) => { + expect(response.statusCode).toEqual(401); + expect(body.error).toEqual('master key not specified'); + done(); + }); + }); + + it('sends an error if you use mismatching class names', done => { + request.post({ + url: 'http://localhost:8378/1/schemas/A', + headers: masterKeyHeaders, + json: true, + body: { + className: 'B', + } + }, (error, response, body) => { + expect(response.statusCode).toEqual(400); + expect(body).toEqual({ + code: Parse.Error.INVALID_CLASS_NAME, + error: 'class name mismatch between B and A', + }); + done(); + }); + }); + + it('sends an error if you use no class name', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + headers: masterKeyHeaders, + json: true, + body: {}, + }, (error, response, body) => { + expect(response.statusCode).toEqual(400); + expect(body).toEqual({ + code: 135, + error: 'POST /schemas needs class name', + }); + done(); + }) + }); + + it('sends an error if you try to create the same class twice', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + headers: masterKeyHeaders, + json: true, + body: { + className: 'A', + }, + }, (error, response, body) => { + expect(error).toEqual(null); + request.post({ + url: 'http://localhost:8378/1/schemas', + headers: masterKeyHeaders, + json: true, + body: { + className: 'A', + } + }, (error, response, body) => { + expect(response.statusCode).toEqual(400); + expect(body).toEqual({ + code: Parse.Error.INVALID_CLASS_NAME, + error: 'class A already exists', + }); + done(); + }); + }); + }); + + it('responds with all fields when you create a class', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + headers: masterKeyHeaders, + json: true, + body: { + className: "NewClass", + fields: { + foo: {type: 'Number'}, + ptr: {type: 'Pointer', targetClass: 'SomeClass'} + } + } + }, (error, response, body) => { + expect(body).toEqual({ + className: 'NewClass', + fields: { + ACL: {type: 'ACL'}, + createdAt: {type: 'Date'}, + updatedAt: {type: 'Date'}, + objectId: {type: 'String'}, + foo: {type: 'Number'}, + ptr: {type: 'Pointer', targetClass: 'SomeClass'}, + } + }); + done(); + }); + }); + + it('lets you specify class name in both places', done => { + request.post({ + url: 'http://localhost:8378/1/schemas/NewClass', + headers: masterKeyHeaders, + json: true, + body: { + className: "NewClass", + } + }, (error, response, body) => { + expect(body).toEqual({ + className: 'NewClass', + fields: { + ACL: {type: 'ACL'}, + createdAt: {type: 'Date'}, + updatedAt: {type: 'Date'}, + objectId: {type: 'String'}, + } + }); + done(); + }); + }); }); diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json index b1aae666..e0347ebf 100644 --- a/spec/support/jasmine.json +++ b/spec/support/jasmine.json @@ -4,7 +4,7 @@ "*spec.js" ], "helpers": [ + "../node_modules/babel-core/register.js", "helper.js" ] } - diff --git a/spec/transform.spec.js b/spec/transform.spec.js index 528c46bf..c7780ffb 100644 --- a/spec/transform.spec.js +++ b/spec/transform.spec.js @@ -1,6 +1,6 @@ // These tests are unit tests designed to only test transform.js. -var transform = require('../transform'); +var transform = require('../src/transform'); var dummySchema = { data: {}, diff --git a/APNS.js b/src/APNS.js similarity index 99% rename from APNS.js rename to src/APNS.js index 5fc73ab0..85c97401 100644 --- a/APNS.js +++ b/src/APNS.js @@ -60,7 +60,7 @@ APNS.prototype.send = function(data, deviceTokens) { var generateNotification = function(coreData, expirationTime) { var notification = new apn.notification(); var payload = {}; - for (key in coreData) { + for (var key in coreData) { switch (key) { case 'alert': notification.setAlertText(coreData.alert); diff --git a/Auth.js b/src/Auth.js similarity index 100% rename from Auth.js rename to src/Auth.js diff --git a/Config.js b/src/Config.js similarity index 100% rename from Config.js rename to src/Config.js diff --git a/DatabaseAdapter.js b/src/DatabaseAdapter.js similarity index 100% rename from DatabaseAdapter.js rename to src/DatabaseAdapter.js diff --git a/ExportAdapter.js b/src/ExportAdapter.js similarity index 96% rename from ExportAdapter.js rename to src/ExportAdapter.js index f8619d5e..139096c9 100644 --- a/ExportAdapter.js +++ b/src/ExportAdapter.js @@ -34,21 +34,8 @@ 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(encodedMongoURI, {uri_decode_auth:true}); + return MongoClient.connect(this.mongoURI); }).then((db) => { this.db = db; }); diff --git a/FilesAdapter.js b/src/FilesAdapter.js similarity index 100% rename from FilesAdapter.js rename to src/FilesAdapter.js diff --git a/GCM.js b/src/GCM.js similarity index 100% rename from GCM.js rename to src/GCM.js diff --git a/GridStoreAdapter.js b/src/GridStoreAdapter.js similarity index 100% rename from GridStoreAdapter.js rename to src/GridStoreAdapter.js diff --git a/PromiseRouter.js b/src/PromiseRouter.js similarity index 100% rename from PromiseRouter.js rename to src/PromiseRouter.js diff --git a/RestQuery.js b/src/RestQuery.js similarity index 100% rename from RestQuery.js rename to src/RestQuery.js diff --git a/RestWrite.js b/src/RestWrite.js similarity index 100% rename from RestWrite.js rename to src/RestWrite.js diff --git a/S3Adapter.js b/src/S3Adapter.js similarity index 100% rename from S3Adapter.js rename to src/S3Adapter.js diff --git a/Schema.js b/src/Schema.js similarity index 97% rename from Schema.js rename to src/Schema.js index 2715f46a..3656507a 100644 --- a/Schema.js +++ b/src/Schema.js @@ -17,7 +17,7 @@ var Parse = require('parse/node').Parse; var transform = require('./transform'); -defaultColumns = { +var defaultColumns = { // Contain the default columns for every parse object type (except _Join collection) _Default: { "objectId": {type:'String'}, @@ -43,13 +43,13 @@ defaultColumns = { "GCMSenderId": {type:'String'}, "timeZone": {type:'String'}, "localeIdentifier": {type:'String'}, - "badge": {type:'Number'}, + "badge": {type:'Number'} }, // The additional default columns for the _User collection (in addition to DefaultCols) _Role: { "name": {type:'String'}, "users": {type:'Relation',className:'_User'}, - "roles": {type:'Relation',className:'_Role'}, + "roles": {type:'Relation',className:'_Role'} }, // The additional default columns for the _User collection (in addition to DefaultCols) _Session: { @@ -58,12 +58,9 @@ defaultColumns = { "installationId": {type:'String'}, "sessionToken": {type:'String'}, "expiresAt": {type:'Date'}, - "createdWith": {type:'Object'}, - }, - _GlobalConfig: { - "params": {type:'Object'} - }, -} + "createdWith": {type:'Object'} + } +}; // Valid classes must: // Be one of _User, _Installation, _Role, _Session OR @@ -224,7 +221,7 @@ Schema.prototype.addClassIfNotExists = function(className, fields) { error: invalidClassNameMessage(className), }); } - for (fieldName in fields) { + for (var fieldName in fields) { if (!fieldNameIsValid(fieldName)) { return Promise.reject({ code: Parse.Error.INVALID_KEY_NAME, @@ -243,18 +240,18 @@ Schema.prototype.addClassIfNotExists = function(className, fields) { _id: className, objectId: 'string', updatedAt: 'string', - createdAt: 'string', + createdAt: 'string' }; - for (fieldName in defaultColumns[className]) { - validatedField = schemaAPITypeToMongoFieldType(defaultColumns[className][fieldName]); + for (var fieldName in defaultColumns[className]) { + var validatedField = schemaAPITypeToMongoFieldType(defaultColumns[className][fieldName]); if (validatedField.code) { return Promise.reject(validatedField); } mongoObject[fieldName] = validatedField.result; } - for (fieldName in fields) { - validatedField = schemaAPITypeToMongoFieldType(fields[fieldName]); + for (var fieldName in fields) { + var validatedField = schemaAPITypeToMongoFieldType(fields[fieldName]); if (validatedField.code) { return Promise.reject(validatedField); } @@ -262,7 +259,6 @@ Schema.prototype.addClassIfNotExists = function(className, fields) { } var geoPoints = Object.keys(mongoObject).filter(key => mongoObject[key] === 'geopoint'); - if (geoPoints.length > 1) { return Promise.reject({ code: Parse.Error.INCORRECT_TYPE, @@ -281,7 +277,7 @@ Schema.prototype.addClassIfNotExists = function(className, fields) { } return Promise.reject(error); }); -} +}; // Returns a promise that resolves successfully to the new schema // object or fails with a reason. diff --git a/analytics.js b/src/analytics.js similarity index 100% rename from analytics.js rename to src/analytics.js diff --git a/batch.js b/src/batch.js similarity index 100% rename from batch.js rename to src/batch.js diff --git a/cache.js b/src/cache.js similarity index 100% rename from cache.js rename to src/cache.js diff --git a/classes.js b/src/classes.js similarity index 98% rename from classes.js rename to src/classes.js index 98e94871..f1400914 100644 --- a/classes.js +++ b/src/classes.js @@ -42,7 +42,7 @@ function handleFind(req) { req.params.className, body.where, options) .then((response) => { if (response && response.results) { - for (result of response.results) { + for (var result of response.results) { if (result.sessionToken) { result.sessionToken = req.info.sessionToken || result.sessionToken; } diff --git a/cloud/main.js b/src/cloud/main.js similarity index 100% rename from cloud/main.js rename to src/cloud/main.js diff --git a/facebook.js b/src/facebook.js similarity index 100% rename from facebook.js rename to src/facebook.js diff --git a/files.js b/src/files.js similarity index 100% rename from files.js rename to src/files.js diff --git a/functions.js b/src/functions.js similarity index 100% rename from functions.js rename to src/functions.js diff --git a/httpRequest.js b/src/httpRequest.js similarity index 100% rename from httpRequest.js rename to src/httpRequest.js diff --git a/index.js b/src/index.js similarity index 100% rename from index.js rename to src/index.js diff --git a/installations.js b/src/installations.js similarity index 100% rename from installations.js rename to src/installations.js diff --git a/middlewares.js b/src/middlewares.js similarity index 100% rename from middlewares.js rename to src/middlewares.js diff --git a/password.js b/src/password.js similarity index 100% rename from password.js rename to src/password.js diff --git a/push.js b/src/push.js similarity index 100% rename from push.js rename to src/push.js diff --git a/rest.js b/src/rest.js similarity index 100% rename from rest.js rename to src/rest.js diff --git a/roles.js b/src/roles.js similarity index 100% rename from roles.js rename to src/roles.js diff --git a/schemas.js b/src/schemas.js similarity index 59% rename from schemas.js rename to src/schemas.js index 875967cd..837224ab 100644 --- a/schemas.js +++ b/src/schemas.js @@ -1,7 +1,9 @@ // schemas.js var express = require('express'), - PromiseRouter = require('./PromiseRouter'); + Parse = require('parse/node').Parse, + PromiseRouter = require('./PromiseRouter'), + Schema = require('./Schema'); var router = new PromiseRouter(); @@ -23,6 +25,7 @@ function mongoFieldTypeToSchemaAPIType(type) { case 'string': return {type: 'String'}; case 'boolean': return {type: 'Boolean'}; case 'date': return {type: 'Date'}; + case 'map': case 'object': return {type: 'Object'}; case 'array': return {type: 'Array'}; case 'geopoint': return {type: 'GeoPoint'}; @@ -31,8 +34,8 @@ function mongoFieldTypeToSchemaAPIType(type) { } function mongoSchemaAPIResponseFields(schema) { - fieldNames = Object.keys(schema).filter(key => key !== '_id'); - response = fieldNames.reduce((obj, fieldName) => { + var fieldNames = Object.keys(schema).filter(key => key !== '_id' && key !== '_metadata'); + var response = fieldNames.reduce((obj, fieldName) => { obj[fieldName] = mongoFieldTypeToSchemaAPIType(schema[fieldName]) return obj; }, {}); @@ -54,7 +57,7 @@ function getAllSchemas(req) { if (!req.auth.isMaster) { return Promise.resolve({ status: 401, - response: {error: 'unauthorized'}, + response: {error: 'master key not specified'}, }); } return req.config.database.collection('_SCHEMA') @@ -83,7 +86,46 @@ function getOneSchema(req) { })); } +function createSchema(req) { + if (!req.auth.isMaster) { + return Promise.resolve({ + status: 401, + response: {error: 'master key not specified'}, + }); + } + if (req.params.className && req.body.className) { + if (req.params.className != req.body.className) { + return Promise.resolve({ + status: 400, + response: { + code: Parse.Error.INVALID_CLASS_NAME, + error: 'class name mismatch between ' + req.body.className + ' and ' + req.params.className, + }, + }); + } + } + var className = req.params.className || req.body.className; + if (!className) { + return Promise.resolve({ + status: 400, + response: { + code: 135, + error: 'POST ' + req.path + ' needs class name', + }, + }); + } + return req.config.database.loadSchema() + .then(schema => schema.addClassIfNotExists(className, req.body.fields)) + .then(result => ({ response: mongoSchemaToSchemaAPIResponse(result) })) + .catch(error => ({ + status: 400, + response: error, + })); +} + router.route('GET', '/schemas', getAllSchemas); router.route('GET', '/schemas/:className', getOneSchema); +router.route('POST', '/schemas', createSchema); +router.route('POST', '/schemas/:className', createSchema); module.exports = router; diff --git a/sessions.js b/src/sessions.js similarity index 100% rename from sessions.js rename to src/sessions.js diff --git a/testing-routes.js b/src/testing-routes.js similarity index 100% rename from testing-routes.js rename to src/testing-routes.js diff --git a/transform.js b/src/transform.js similarity index 99% rename from transform.js rename to src/transform.js index 051bc75a..802bf075 100644 --- a/transform.js +++ b/src/transform.js @@ -21,7 +21,7 @@ var Parse = require('parse/node').Parse; // validate: true indicates that key names are to be validated. // // Returns an object with {key: key, value: value}. -function transformKeyValue(schema, className, restKey, restValue, options) { +export function transformKeyValue(schema, className, restKey, restValue, options) { options = options || {}; // Check if the schema is known since it's a built-in field. @@ -126,7 +126,7 @@ function transformKeyValue(schema, className, restKey, restValue, options) { if (inArray && options.query && !(restValue instanceof Array)) { return { - key: key, value: [restValue] + key: key, value: { '$all' : [restValue] } }; } diff --git a/triggers.js b/src/triggers.js similarity index 100% rename from triggers.js rename to src/triggers.js diff --git a/users.js b/src/users.js similarity index 100% rename from users.js rename to src/users.js