From 3b4515ac439cde1a552d33a664e7e08eb9112e77 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Sun, 7 Feb 2016 00:13:42 +0100 Subject: [PATCH 01/18] Add file for handling GET/POST to /config --- global_config.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 global_config.js diff --git a/global_config.js b/global_config.js new file mode 100644 index 0000000000..56eafc3c67 --- /dev/null +++ b/global_config.js @@ -0,0 +1,35 @@ +// global_config.js + +var Parse = require('parse/node').Parse, + PromiseRouter = require('./PromiseRouter'), + rest = require('./rest'); + +var router = new PromiseRouter(); + +// Returns a promise for a {response} object. +function handleUpdateGlobalConfig(req) { + return rest.update(req.config, req.auth, + '_GlobalConfig', 1, req.body) + .then((response) => { + return {response: response}; + }); +} + +// Returns a promise for a {response} object. +function handleGetGlobalConfig(req) { + return rest.find(req.config, req.auth, '_GlobalConfig', 1) + .then((response) => { + if (!response.results || response.results.length == 0) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, + 'Object not found.'); + } else { + // only return 'params' attribute of response + return {response: { params: response.results[0].params }}; + } + }); +} + +router.route('GET','/config', handleGetGlobalConfig); +router.route('POST','/config', handleUpdateGlobalConfig); + +module.exports = router; \ No newline at end of file From 0dfb4ac665981b93bd7b6118b9d308ef5130c7f3 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Sun, 7 Feb 2016 00:13:56 +0100 Subject: [PATCH 02/18] Add route for handling /config --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 37a88b893a..95b390e30e 100644 --- a/index.js +++ b/index.js @@ -115,6 +115,7 @@ function ParseServer(args) { router.merge(require('./installations')); router.merge(require('./functions')); router.merge(require('./schemas')); + router.merge(require('./global_config')); batch.mountOnto(router); From ba1104f6edfb4f391802378172603e09b96840da Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Sun, 7 Feb 2016 00:14:23 +0100 Subject: [PATCH 03/18] Open _GlobalConfig for querying --- Schema.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Schema.js b/Schema.js index 25e301cdd9..2fe7a2de90 100644 --- a/Schema.js +++ b/Schema.js @@ -75,6 +75,7 @@ function classNameIsValid(className) { className === '_Session' || className === '_SCHEMA' || //TODO: remove this, as _SCHEMA is not a valid class name for storing Parse Objects. className === '_Role' || + className === '_GlobalConfig' || joinClassRegex.test(className) || //Class names have the same constraints as field names, but also allow the previous additional names. fieldNameIsValid(className) From c8792b4ec2556b61b592828d828efcde7a94e348 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Sun, 7 Feb 2016 01:19:44 +0100 Subject: [PATCH 04/18] Add default column for _GlobalConfig --- Schema.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Schema.js b/Schema.js index 2fe7a2de90..dfba4713b9 100644 --- a/Schema.js +++ b/Schema.js @@ -60,6 +60,9 @@ defaultColumns = { "expiresAt": {type:'Date'}, "createdWith": {type:'Object'}, }, + _GlobalConfig: { + "params": {type:'Object'} + }, } // Valid classes must: From 6fe050b397dc310c955a2fdffe0313047e06db01 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Sun, 7 Feb 2016 01:20:15 +0100 Subject: [PATCH 05/18] Add read/write test for _GlobalConfig --- spec/ParseGlobalConfig.spec.js | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 spec/ParseGlobalConfig.spec.js diff --git a/spec/ParseGlobalConfig.spec.js b/spec/ParseGlobalConfig.spec.js new file mode 100644 index 0000000000..dd04a99765 --- /dev/null +++ b/spec/ParseGlobalConfig.spec.js @@ -0,0 +1,49 @@ +// run test when changing related file using +// $ TESTING=1 node_modules/jasmine/bin/jasmine.js spec/ParseGlobalConfig.spec.js + +var auth = require('../Auth'); +var cache = require('../cache'); +var Config = require('../Config'); +var DatabaseAdapter = require('../DatabaseAdapter'); +var Parse = require('parse/node').Parse; +var rest = require('../rest'); + +var config = new Config('test'); +var database = DatabaseAdapter.getDatabaseConnection('test'); + +describe('GlobalConfig', () => { + beforeEach(function() { + database.create('_GlobalConfig', { objectId: 1, params: { mostValuableCompany: 'Apple' } }, {}); + }); + + it('find existing values', (done) => { + rest.find(config, auth.nobody(config), '_GlobalConfig', 1) + .then(() => { + return database.mongoFind('_GlobalConfig', {}, {}); + }).then((results) => { + expect(results.length).toEqual(1); + var obj = results[0]; + expect(obj.params.mostValuableCompany).toEqual('Apple'); + done(); + }).catch((error) => { console.log(error); }); + }); + + it('update with a new value', (done) => { + var input = { + params: { + mostValuableCompany: 'Alphabet' + } + }; + rest.update(config, auth.nobody(config), '_GlobalConfig', 1, input) + .then(() => { + return database.mongoFind('_GlobalConfig', {}, {}); + }).then((results) => { + expect(results.length).toEqual(1); + var obj = results[0]; + expect(obj.params.mostValuableCompany).toEqual('Alphabet'); + done(); + }).catch((error) => { console.log(error); }); + }); + + +}); From 3b0ab809653dba2b0f47370c563b5cdd8bd0fc5c Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Sun, 7 Feb 2016 13:51:32 +0100 Subject: [PATCH 06/18] Require masterKey when performing config updates --- global_config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/global_config.js b/global_config.js index 56eafc3c67..cba5c790e0 100644 --- a/global_config.js +++ b/global_config.js @@ -8,6 +8,10 @@ var router = new PromiseRouter(); // Returns a promise for a {response} object. function handleUpdateGlobalConfig(req) { + if (!req.auth.isMaster) { + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Config updates requires valid masterKey.'); + } + return rest.update(req.config, req.auth, '_GlobalConfig', 1, req.body) .then((response) => { From 7733ab96254f936e1988e2309b4df31c2db06ee6 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Mon, 8 Feb 2016 09:01:21 +0100 Subject: [PATCH 07/18] Remove unnecessary comment about testing --- spec/ParseGlobalConfig.spec.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/ParseGlobalConfig.spec.js b/spec/ParseGlobalConfig.spec.js index dd04a99765..c5c52d9084 100644 --- a/spec/ParseGlobalConfig.spec.js +++ b/spec/ParseGlobalConfig.spec.js @@ -1,5 +1,3 @@ -// run test when changing related file using -// $ TESTING=1 node_modules/jasmine/bin/jasmine.js spec/ParseGlobalConfig.spec.js var auth = require('../Auth'); var cache = require('../cache'); From 8b3f8751f4e61964549a33776e2458e6fe44e478 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Tue, 9 Feb 2016 00:36:45 +0100 Subject: [PATCH 08/18] Uses rawCollection() for direct db access Updated tests accordingly to changed access --- ExportAdapter.js | 4 ++ Schema.js | 1 - global_config.js | 57 ++++++++++++++----------- spec/ParseGlobalConfig.spec.js | 78 ++++++++++++++++++++-------------- spec/helper.js | 2 +- 5 files changed, 83 insertions(+), 59 deletions(-) diff --git a/ExportAdapter.js b/ExportAdapter.js index df417ac83a..f8619d5e4d 100644 --- a/ExportAdapter.js +++ b/ExportAdapter.js @@ -64,6 +64,10 @@ ExportAdapter.prototype.collection = function(className) { throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className); } + return this.rawCollection(className); +}; + +ExportAdapter.prototype.rawCollection = function(className) { return this.connect().then(() => { return this.db.collection(this.collectionPrefix + className); }); diff --git a/Schema.js b/Schema.js index dfba4713b9..2715f46a20 100644 --- a/Schema.js +++ b/Schema.js @@ -78,7 +78,6 @@ function classNameIsValid(className) { className === '_Session' || className === '_SCHEMA' || //TODO: remove this, as _SCHEMA is not a valid class name for storing Parse Objects. className === '_Role' || - className === '_GlobalConfig' || joinClassRegex.test(className) || //Class names have the same constraints as field names, but also allow the previous additional names. fieldNameIsValid(className) diff --git a/global_config.js b/global_config.js index cba5c790e0..773b2597e9 100644 --- a/global_config.js +++ b/global_config.js @@ -1,39 +1,46 @@ // global_config.js var Parse = require('parse/node').Parse, - PromiseRouter = require('./PromiseRouter'), - rest = require('./rest'); + PromiseRouter = require('./PromiseRouter'); var router = new PromiseRouter(); -// Returns a promise for a {response} object. -function handleUpdateGlobalConfig(req) { +function updateGlobalConfig(req) { if (!req.auth.isMaster) { - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Config updates requires valid masterKey.'); + return Promise.resolve({ + status: 401, + response: {error: 'unauthorized'}, + }); } - return rest.update(req.config, req.auth, - '_GlobalConfig', 1, req.body) - .then((response) => { - return {response: response}; - }); + return req.config.database.rawCollection('_GlobalConfig') + .then(coll => coll.findOneAndUpdate({ _id: 1 }, { $set: req.body }, { returnOriginal: false })) + .then(response => { + return { response: { params: response.value.params } } + }) + .catch(() => ({ + status: 404, + response: { + code: 103, + error: 'config cannot be updated', + } + })); } -// Returns a promise for a {response} object. -function handleGetGlobalConfig(req) { - return rest.find(req.config, req.auth, '_GlobalConfig', 1) - .then((response) => { - if (!response.results || response.results.length == 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, - 'Object not found.'); - } else { - // only return 'params' attribute of response - return {response: { params: response.results[0].params }}; - } - }); +function getGlobalConfig(req) { + return req.config.database.rawCollection('_GlobalConfig') + .then(coll => coll.findOne({'_id': 1})) + .then(globalConfig => ({response: { params: globalConfig.params }})) + .catch(() => ({ + status: 404, + response: { + code: 103, + error: 'config does not exist', + } + })); } -router.route('GET','/config', handleGetGlobalConfig); -router.route('POST','/config', handleUpdateGlobalConfig); +router.route('GET', '/config', getGlobalConfig); +router.route('POST', '/config', updateGlobalConfig); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/spec/ParseGlobalConfig.spec.js b/spec/ParseGlobalConfig.spec.js index c5c52d9084..1258ae99df 100644 --- a/spec/ParseGlobalConfig.spec.js +++ b/spec/ParseGlobalConfig.spec.js @@ -1,47 +1,61 @@ -var auth = require('../Auth'); -var cache = require('../cache'); -var Config = require('../Config'); +var request = require('request'); var DatabaseAdapter = require('../DatabaseAdapter'); -var Parse = require('parse/node').Parse; -var rest = require('../rest'); -var config = new Config('test'); var database = DatabaseAdapter.getDatabaseConnection('test'); -describe('GlobalConfig', () => { - beforeEach(function() { - database.create('_GlobalConfig', { objectId: 1, params: { mostValuableCompany: 'Apple' } }, {}); +describe('a GlobalConfig', () => { + beforeEach(function(done) { + database.rawCollection('_GlobalConfig') + .then(coll => coll.updateOne({ '_id': 1}, { $set: { params: { companies: ['US', 'DK'] } } }, { upsert: true })) + .then(done()); }); - it('find existing values', (done) => { - rest.find(config, auth.nobody(config), '_GlobalConfig', 1) - .then(() => { - return database.mongoFind('_GlobalConfig', {}, {}); - }).then((results) => { - expect(results.length).toEqual(1); - var obj = results[0]; - expect(obj.params.mostValuableCompany).toEqual('Apple'); + it('can be retrieved', (done) => { + request.get({ + url: 'http://localhost:8378/1/config', + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', + }, + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + expect(body.params.companies).toEqual(['US', 'DK']); done(); - }).catch((error) => { console.log(error); }); + }); }); - it('update with a new value', (done) => { - var input = { - params: { - mostValuableCompany: 'Alphabet' - } - }; - rest.update(config, auth.nobody(config), '_GlobalConfig', 1, input) - .then(() => { - return database.mongoFind('_GlobalConfig', {}, {}); - }).then((results) => { - expect(results.length).toEqual(1); - var obj = results[0]; - expect(obj.params.mostValuableCompany).toEqual('Alphabet'); + it('can be updated when a master key exists', (done) => { + request.post({ + url: 'http://localhost:8378/1/config', + json: true, + body: { params: { companies: ['US', 'DK', 'SE'] } }, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test' + }, + }, (error, response, body) => { + expect(response.statusCode).toEqual(200); + expect(body.params.companies).toEqual(['US', 'DK', 'SE']); done(); - }).catch((error) => { console.log(error); }); + }); }); + it('fail to update if master key is missing', (done) => { + request.post({ + url: 'http://localhost:8378/1/config', + json: true, + body: { params: { companies: [] } }, + 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(); + }); + }); }); diff --git a/spec/helper.js b/spec/helper.js index cca4d1a570..5727c6b54b 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -192,7 +192,7 @@ function mockFacebook() { function clearData() { var promises = []; for (var conn in DatabaseAdapter.dbConnections) { - promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything()); + // promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything()); } return Promise.all(promises); } From 307a5d8157aa3023fe12a4d293137ea238e96d49 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Tue, 9 Feb 2016 00:39:31 +0100 Subject: [PATCH 09/18] Revert commented out line --- spec/helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helper.js b/spec/helper.js index 5727c6b54b..cca4d1a570 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -192,7 +192,7 @@ function mockFacebook() { function clearData() { var promises = []; for (var conn in DatabaseAdapter.dbConnections) { - // promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything()); + promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything()); } return Promise.all(promises); } From 7fa4b3bc073e2c6dd38fb6624873afd879a69a20 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Tue, 9 Feb 2016 10:23:55 +0100 Subject: [PATCH 10/18] Merged with master --- .babelrc | 5 + .gitignore | 5 +- .travis.yml | 3 + CONTRIBUTING.md | 2 +- bin/parse-server | 2 +- package.json | 17 +- spec/APNS.spec.js | 2 +- spec/ExportAdapter.spec.js | 2 +- spec/GCM.spec.js | 2 +- spec/ParseACL.spec.js | 3 + spec/ParseAPI.spec.js | 2 +- spec/ParseInstallation.spec.js | 10 +- spec/ParseQuery.spec.js | 18 +- spec/ParseUser.spec.js | 2 +- spec/RestCreate.spec.js | 10 +- spec/RestQuery.spec.js | 8 +- spec/Schema.spec.js | 8 +- spec/helper.js | 8 +- spec/push.spec.js | 2 +- spec/schemas.spec.js | 216 +++++++++++++++--- spec/support/jasmine.json | 2 +- spec/transform.spec.js | 2 +- APNS.js => src/APNS.js | 2 +- Auth.js => src/Auth.js | 0 Config.js => src/Config.js | 0 DatabaseAdapter.js => src/DatabaseAdapter.js | 0 ExportAdapter.js => src/ExportAdapter.js | 15 +- FilesAdapter.js => src/FilesAdapter.js | 0 GCM.js => src/GCM.js | 0 .../GridStoreAdapter.js | 0 PromiseRouter.js => src/PromiseRouter.js | 0 RestQuery.js => src/RestQuery.js | 0 RestWrite.js => src/RestWrite.js | 0 S3Adapter.js => src/S3Adapter.js | 0 Schema.js => src/Schema.js | 30 ++- analytics.js => src/analytics.js | 0 batch.js => src/batch.js | 0 cache.js => src/cache.js | 0 classes.js => src/classes.js | 2 +- {cloud => src/cloud}/main.js | 0 facebook.js => src/facebook.js | 0 files.js => src/files.js | 0 functions.js => src/functions.js | 0 httpRequest.js => src/httpRequest.js | 0 index.js => src/index.js | 0 installations.js => src/installations.js | 0 middlewares.js => src/middlewares.js | 0 password.js => src/password.js | 0 push.js => src/push.js | 0 rest.js => src/rest.js | 0 roles.js => src/roles.js | 0 schemas.js => src/schemas.js | 50 +++- sessions.js => src/sessions.js | 0 testing-routes.js => src/testing-routes.js | 0 transform.js => src/transform.js | 4 +- triggers.js => src/triggers.js | 0 users.js => src/users.js | 0 57 files changed, 325 insertions(+), 109 deletions(-) create mode 100644 .babelrc rename APNS.js => src/APNS.js (99%) rename Auth.js => src/Auth.js (100%) rename Config.js => src/Config.js (100%) rename DatabaseAdapter.js => src/DatabaseAdapter.js (100%) rename ExportAdapter.js => src/ExportAdapter.js (96%) rename FilesAdapter.js => src/FilesAdapter.js (100%) rename GCM.js => src/GCM.js (100%) rename GridStoreAdapter.js => src/GridStoreAdapter.js (100%) rename PromiseRouter.js => src/PromiseRouter.js (100%) rename RestQuery.js => src/RestQuery.js (100%) rename RestWrite.js => src/RestWrite.js (100%) rename S3Adapter.js => src/S3Adapter.js (100%) rename Schema.js => src/Schema.js (97%) rename analytics.js => src/analytics.js (100%) rename batch.js => src/batch.js (100%) rename cache.js => src/cache.js (100%) rename classes.js => src/classes.js (98%) rename {cloud => src/cloud}/main.js (100%) rename facebook.js => src/facebook.js (100%) rename files.js => src/files.js (100%) rename functions.js => src/functions.js (100%) rename httpRequest.js => src/httpRequest.js (100%) rename index.js => src/index.js (100%) rename installations.js => src/installations.js (100%) rename middlewares.js => src/middlewares.js (100%) rename password.js => src/password.js (100%) rename push.js => src/push.js (100%) rename rest.js => src/rest.js (100%) rename roles.js => src/roles.js (100%) rename schemas.js => src/schemas.js (59%) rename sessions.js => src/sessions.js (100%) rename testing-routes.js => src/testing-routes.js (100%) rename transform.js => src/transform.js (99%) rename triggers.js => src/triggers.js (100%) rename users.js => src/users.js (100%) diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..3c078e9f99 --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "es2015" + ] +} diff --git a/.gitignore b/.gitignore index 2d9748d648..318fed2034 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 e34b4a44c1..dc081ced7a 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 397621264d..6a1923cfcd 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 c2606f4b67..902e43b249 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 3d145ee40b..689110c01b 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 c50bb5c952..72490e9791 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 95fbdd2190..a4f3f9b6ec 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 d7484b0ea9..4bad883ee4 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 fead537e02..370550c0a5 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 24edf38f54..8670bdd2be 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 6d8e61625f..91bb9a23b4 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 88c1f53a89..f5b6dc1a2b 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 b364adf188..c9f25bd84d 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 59de11ead9..244555075a 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 08d0176654..b93a07d588 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 ccc83525b6..636311a640 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 cca4d1a570..3e6c6d9853 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 ba5b533bbe..a2ea41b5e6 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 8c7434da49..68ac31c9a0 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 b1aae6661f..e0347ebfe7 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 528c46bfa1..c7780ffbd2 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 5fc73ab0f2..85c97401a5 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 f8619d5e4d..139096c93c 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 2715f46a20..3656507a7c 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 98e948714d..f14009144c 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 875967cde7..837224abce 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 051bc75ae7..802bf0754a 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 From 11082b15b479868c44b310e29345ff25f3dda1c9 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Tue, 9 Feb 2016 11:06:12 +0100 Subject: [PATCH 11/18] Move global_config to new src/ location --- global_config.js => src/global_config.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename global_config.js => src/global_config.js (100%) diff --git a/global_config.js b/src/global_config.js similarity index 100% rename from global_config.js rename to src/global_config.js From bd4546f444232e2fdf014b4809753142f964c4c5 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Tue, 9 Feb 2016 11:10:40 +0100 Subject: [PATCH 12/18] Update error message --- src/global_config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/global_config.js b/src/global_config.js index 773b2597e9..9d032d7d31 100644 --- a/src/global_config.js +++ b/src/global_config.js @@ -21,7 +21,7 @@ function updateGlobalConfig(req) { .catch(() => ({ status: 404, response: { - code: 103, + code: Parse.Error.INVALID_KEY_NAME, error: 'config cannot be updated', } })); @@ -34,7 +34,7 @@ function getGlobalConfig(req) { .catch(() => ({ status: 404, response: { - code: 103, + code: Parse.Error.INVALID_KEY_NAME, error: 'config does not exist', } })); From 84b35eab466acce8d3f40061d9936bd5b31ad242 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Tue, 9 Feb 2016 11:28:54 +0100 Subject: [PATCH 13/18] Fixed reference after source move --- spec/ParseGlobalConfig.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ParseGlobalConfig.spec.js b/spec/ParseGlobalConfig.spec.js index 1258ae99df..ff1286e2ee 100644 --- a/spec/ParseGlobalConfig.spec.js +++ b/spec/ParseGlobalConfig.spec.js @@ -1,6 +1,6 @@ var request = require('request'); -var DatabaseAdapter = require('../DatabaseAdapter'); +var DatabaseAdapter = require('../src/DatabaseAdapter'); var database = DatabaseAdapter.getDatabaseConnection('test'); From f039b70cf5880ca38edd34210b1879595bf9befd Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Tue, 9 Feb 2016 12:32:00 +0100 Subject: [PATCH 14/18] Add test for uncovered cases --- spec/ParseGlobalConfig.spec.js | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/spec/ParseGlobalConfig.spec.js b/spec/ParseGlobalConfig.spec.js index ff1286e2ee..78364c6afd 100644 --- a/spec/ParseGlobalConfig.spec.js +++ b/spec/ParseGlobalConfig.spec.js @@ -1,5 +1,6 @@ var request = require('request'); +var Parse = require('parse/node').Parse; var DatabaseAdapter = require('../src/DatabaseAdapter'); var database = DatabaseAdapter.getDatabaseConnection('test'); @@ -58,4 +59,43 @@ describe('a GlobalConfig', () => { }); }); + it('failed getting config when it is missing', (done) => { + database.rawCollection('_GlobalConfig') + .then(coll => coll.deleteOne({ '_id': 1}, {}, {})) + .then(_ => { + request.get({ + url: 'http://localhost:8378/1/config', + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', + }, + }, (error, response, body) => { + expect(response.statusCode).toEqual(404); + expect(body.code).toEqual(Parse.Error.INVALID_KEY_NAME); + done(); + }); + }); + }); + + it('failed updating config when it is missing', (done) => { + database.rawCollection('_GlobalConfig') + .then(coll => coll.deleteOne({ '_id': 1}, {}, {})) + .then(_ => { + request.post({ + url: 'http://localhost:8378/1/config', + json: true, + body: { params: { companies: ['US', 'DK', 'SE'] } }, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test' + }, + }, (error, response, body) => { + expect(response.statusCode).toEqual(404); + expect(body.code).toEqual(Parse.Error.INVALID_KEY_NAME); + done(); + }); + }); + }); + }); From fb6af2cfa0e7d149dd745de4d7a46821b93a0135 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Thu, 11 Feb 2016 01:06:25 +0100 Subject: [PATCH 15/18] Hide /config behind PARSE_EXPERIMENTAL_CONFIG_ENABLED flag --- src/global_config.js | 2 +- src/index.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/global_config.js b/src/global_config.js index 9d032d7d31..c7c9266a20 100644 --- a/src/global_config.js +++ b/src/global_config.js @@ -41,6 +41,6 @@ function getGlobalConfig(req) { } router.route('GET', '/config', getGlobalConfig); -router.route('POST', '/config', updateGlobalConfig); +router.route('PUT', '/config', updateGlobalConfig); module.exports = router; diff --git a/src/index.js b/src/index.js index 25e136412e..ade8527b5f 100644 --- a/src/index.js +++ b/src/index.js @@ -121,7 +121,9 @@ function ParseServer(args) { router.merge(require('./installations')); router.merge(require('./functions')); router.merge(require('./schemas')); - router.merge(require('./global_config')); + if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED) { + router.merge(require('./global_config')); + } batch.mountOnto(router); From 19777699c9b77392736f80b2b0d2331139cbdf61 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Thu, 11 Feb 2016 01:07:49 +0100 Subject: [PATCH 16/18] Rearrange methods to follow route setup --- src/global_config.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/global_config.js b/src/global_config.js index c7c9266a20..b1b8f90e47 100644 --- a/src/global_config.js +++ b/src/global_config.js @@ -5,6 +5,19 @@ var Parse = require('parse/node').Parse, var router = new PromiseRouter(); +function getGlobalConfig(req) { + return req.config.database.rawCollection('_GlobalConfig') + .then(coll => coll.findOne({'_id': 1})) + .then(globalConfig => ({response: { params: globalConfig.params }})) + .catch(() => ({ + status: 404, + response: { + code: Parse.Error.INVALID_KEY_NAME, + error: 'config does not exist', + } + })); +} + function updateGlobalConfig(req) { if (!req.auth.isMaster) { return Promise.resolve({ @@ -27,19 +40,6 @@ function updateGlobalConfig(req) { })); } -function getGlobalConfig(req) { - return req.config.database.rawCollection('_GlobalConfig') - .then(coll => coll.findOne({'_id': 1})) - .then(globalConfig => ({response: { params: globalConfig.params }})) - .catch(() => ({ - status: 404, - response: { - code: Parse.Error.INVALID_KEY_NAME, - error: 'config does not exist', - } - })); -} - router.route('GET', '/config', getGlobalConfig); router.route('PUT', '/config', updateGlobalConfig); From 930573bb4768f0cdf48c166c4acffa3ed487c5a5 Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Thu, 11 Feb 2016 01:10:09 +0100 Subject: [PATCH 17/18] Update PUT response to align with current dashboard --- src/global_config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/global_config.js b/src/global_config.js index b1b8f90e47..30314f7ad6 100644 --- a/src/global_config.js +++ b/src/global_config.js @@ -29,7 +29,7 @@ function updateGlobalConfig(req) { return req.config.database.rawCollection('_GlobalConfig') .then(coll => coll.findOneAndUpdate({ _id: 1 }, { $set: req.body }, { returnOriginal: false })) .then(response => { - return { response: { params: response.value.params } } + return { response: { result: true } } }) .catch(() => ({ status: 404, From 1d576bcc9fd2f7b0cba9fa86715f99c5a68d14de Mon Sep 17 00:00:00 2001 From: Peter Theill Date: Thu, 11 Feb 2016 01:32:38 +0100 Subject: [PATCH 18/18] Update tests and ensure tests are run regardless of exp flag --- spec/ParseGlobalConfig.spec.js | 26 +++----------------------- src/global_config.js | 2 +- src/index.js | 2 +- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/spec/ParseGlobalConfig.spec.js b/spec/ParseGlobalConfig.spec.js index 78364c6afd..0257fe9cbb 100644 --- a/spec/ParseGlobalConfig.spec.js +++ b/spec/ParseGlobalConfig.spec.js @@ -28,7 +28,7 @@ describe('a GlobalConfig', () => { }); it('can be updated when a master key exists', (done) => { - request.post({ + request.put({ url: 'http://localhost:8378/1/config', json: true, body: { params: { companies: ['US', 'DK', 'SE'] } }, @@ -38,13 +38,13 @@ describe('a GlobalConfig', () => { }, }, (error, response, body) => { expect(response.statusCode).toEqual(200); - expect(body.params.companies).toEqual(['US', 'DK', 'SE']); + expect(body.result).toEqual(true); done(); }); }); it('fail to update if master key is missing', (done) => { - request.post({ + request.put({ url: 'http://localhost:8378/1/config', json: true, body: { params: { companies: [] } }, @@ -78,24 +78,4 @@ describe('a GlobalConfig', () => { }); }); - it('failed updating config when it is missing', (done) => { - database.rawCollection('_GlobalConfig') - .then(coll => coll.deleteOne({ '_id': 1}, {}, {})) - .then(_ => { - request.post({ - url: 'http://localhost:8378/1/config', - json: true, - body: { params: { companies: ['US', 'DK', 'SE'] } }, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test' - }, - }, (error, response, body) => { - expect(response.statusCode).toEqual(404); - expect(body.code).toEqual(Parse.Error.INVALID_KEY_NAME); - done(); - }); - }); - }); - }); diff --git a/src/global_config.js b/src/global_config.js index 30314f7ad6..3dd1b5432e 100644 --- a/src/global_config.js +++ b/src/global_config.js @@ -27,7 +27,7 @@ function updateGlobalConfig(req) { } return req.config.database.rawCollection('_GlobalConfig') - .then(coll => coll.findOneAndUpdate({ _id: 1 }, { $set: req.body }, { returnOriginal: false })) + .then(coll => coll.findOneAndUpdate({ _id: 1 }, { $set: req.body })) .then(response => { return { response: { result: true } } }) diff --git a/src/index.js b/src/index.js index ade8527b5f..ef29ec7f39 100644 --- a/src/index.js +++ b/src/index.js @@ -121,7 +121,7 @@ function ParseServer(args) { router.merge(require('./installations')); router.merge(require('./functions')); router.merge(require('./schemas')); - if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED) { + if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) { router.merge(require('./global_config')); }