From 502e92a65dd2dde77f5e194c669fff006cbdd430 Mon Sep 17 00:00:00 2001 From: Alexander Mays Date: Fri, 5 Feb 2016 15:46:53 -0500 Subject: [PATCH 1/2] Deprecated the FilesAdapter and DatabaseAdapter in favor of FilesProvider and DatabaseProvider. Updated the implementation and tests Signed-off-by: Alexander Mays --- Config.js | 10 ++++-- DatabaseAdapter.js | 56 --------------------------------- FilesAdapter.js | 29 ----------------- classes/BaseProvider.js | 18 +++++++++++ classes/DatabaseProvider.js | 57 ++++++++++++++++++++++++++++++++++ classes/FilesProvider.js | 20 ++++++++++++ files.js | 16 +++++++--- index.js | 23 ++++++++------ interfaces/ServiceProvider.js | 12 +++++++ spec/ParseAPI.spec.js | 5 +-- spec/ParseInstallation.spec.js | 4 +-- spec/RestCreate.spec.js | 4 +-- spec/helper.js | 8 +++-- 13 files changed, 152 insertions(+), 110 deletions(-) delete mode 100644 DatabaseAdapter.js delete mode 100644 FilesAdapter.js create mode 100644 classes/BaseProvider.js create mode 100644 classes/DatabaseProvider.js create mode 100644 classes/FilesProvider.js create mode 100644 interfaces/ServiceProvider.js diff --git a/Config.js b/Config.js index df44f8b170..9d6d4fad1c 100644 --- a/Config.js +++ b/Config.js @@ -1,19 +1,23 @@ // A Config object provides information about how a specific app is // configured. // mount is the URL for the root of the API; includes http, domain, etc. + +// TODO: Cache should get it's providers from the server context, rather than requiring them +var DatabaseProvider = require('./classes/DatabaseProvider'); + function Config(applicationId, mount) { var cache = require('./cache'); - var DatabaseAdapter = require('./DatabaseAdapter'); + var dbProvider = new DatabaseProvider(); var cacheInfo = cache.apps[applicationId]; this.valid = !!cacheInfo; if (!this.valid) { return; } - + this.applicationId = applicationId; this.collectionPrefix = cacheInfo.collectionPrefix || ''; - this.database = DatabaseAdapter.getDatabaseConnection(applicationId); + this.database = dbProvider.getDatabaseConnection(applicationId); this.masterKey = cacheInfo.masterKey; this.clientKey = cacheInfo.clientKey; this.javascriptKey = cacheInfo.javascriptKey; diff --git a/DatabaseAdapter.js b/DatabaseAdapter.js deleted file mode 100644 index 4967d5665d..0000000000 --- a/DatabaseAdapter.js +++ /dev/null @@ -1,56 +0,0 @@ -// Database Adapter -// -// Allows you to change the underlying database. -// -// Adapter classes must implement the following methods: -// * a constructor with signature (connectionString, optionsObject) -// * connect() -// * loadSchema() -// * create(className, object) -// * find(className, query, options) -// * update(className, query, update, options) -// * destroy(className, query, options) -// * This list is incomplete and the database process is not fully modularized. -// -// Default is ExportAdapter, which uses mongo. - -var ExportAdapter = require('./ExportAdapter'); - -var adapter = ExportAdapter; -var cache = require('./cache'); -var dbConnections = {}; -var databaseURI = 'mongodb://localhost:27017/parse'; -var appDatabaseURIs = {}; - -function setAdapter(databaseAdapter) { - adapter = databaseAdapter; -} - -function setDatabaseURI(uri) { - databaseURI = uri; -} - -function setAppDatabaseURI(appId, uri) { - appDatabaseURIs[appId] = uri; -} - -function getDatabaseConnection(appId) { - if (dbConnections[appId]) { - return dbConnections[appId]; - } - - var dbURI = (appDatabaseURIs[appId] ? appDatabaseURIs[appId] : databaseURI); - dbConnections[appId] = new adapter(dbURI, { - collectionPrefix: cache.apps[appId]['collectionPrefix'] - }); - dbConnections[appId].connect(); - return dbConnections[appId]; -} - -module.exports = { - dbConnections: dbConnections, - getDatabaseConnection: getDatabaseConnection, - setAdapter: setAdapter, - setDatabaseURI: setDatabaseURI, - setAppDatabaseURI: setAppDatabaseURI -}; diff --git a/FilesAdapter.js b/FilesAdapter.js deleted file mode 100644 index 427e20d9bb..0000000000 --- a/FilesAdapter.js +++ /dev/null @@ -1,29 +0,0 @@ -// Files Adapter -// -// Allows you to change the file storage mechanism. -// -// Adapter classes must implement the following functions: -// * create(config, filename, data) -// * get(config, filename) -// * location(config, req, filename) -// -// Default is GridStoreAdapter, which requires mongo -// and for the API server to be using the ExportAdapter -// database adapter. - -var GridStoreAdapter = require('./GridStoreAdapter'); - -var adapter = GridStoreAdapter; - -function setAdapter(filesAdapter) { - adapter = filesAdapter; -} - -function getAdapter() { - return adapter; -} - -module.exports = { - getAdapter: getAdapter, - setAdapter: setAdapter -}; diff --git a/classes/BaseProvider.js b/classes/BaseProvider.js new file mode 100644 index 0000000000..c1f8d3afa3 --- /dev/null +++ b/classes/BaseProvider.js @@ -0,0 +1,18 @@ +var ServiceProviderInterface = require('../interfaces/ServiceProvider'); +var util = require('util'); + +function BaseProvider(adapter) { + this.adapter = adapter; +}; + +util.inherits(BaseProvider, ServiceProviderInterface); + +BaseProvider.prototype.getAdapter = function getAdapter() { + return this.adapter; +} + +BaseProvider.prototype.setAdapter = function setAdapter(adapter) { + this.adapter = adapter; +} + +module.exports = BaseProvider; \ No newline at end of file diff --git a/classes/DatabaseProvider.js b/classes/DatabaseProvider.js new file mode 100644 index 0000000000..8c594a4335 --- /dev/null +++ b/classes/DatabaseProvider.js @@ -0,0 +1,57 @@ +var BaseProvider = require('./BaseProvider'); +var util = require('util'); +var cache = require('../cache'); + +// TODO: Make these instance variables? +var dbConnections = {}; +var databaseURI = 'mongodb://localhost:27017/parse'; +var appDatabaseURIs = {}; + +// Singleton for the entire server. TODO: Refactor away from singleton paradigm +var instance = null; + +// Adapter is actually the adapter constructor. +// TODO: Refactor so that the provider doesn't have to implement these methods +// TODO: Instantiate the adapter if it's a constructor. +function DatabaseProvider(adapter) { + if (instance) { + return instance; + } + + instance = this; + + this.adapter = adapter; +}; + +util.inherits(DatabaseProvider, BaseProvider); + +DatabaseProvider.prototype.setDatabaseURI = function setDatabaseURI(uri) { + databaseURI = uri; +}; + +DatabaseProvider.prototype.setAppDatabaseURI = function setAppDatabaseURI(appId, uri) { + appDatabaseURIs[appId] = uri; +} + +DatabaseProvider.prototype.getDatabaseConnections = function getDatabaseConnections() { + return dbConnections; +} + +DatabaseProvider.prototype.getDatabaseConnection = function getDatabaseConnection(appId) { + if (dbConnections[appId]) { + return dbConnections[appId]; + } + + var adapterClass = this.getAdapter(); + + var dbURI = (appDatabaseURIs[appId] ? appDatabaseURIs[appId] : databaseURI); + var adapter = new adapterClass(dbURI, { + collectionPrefix: cache.apps[appId]['collectionPrefix'] + }); + dbConnections[appId] = adapter; + dbConnections[appId].connect(); + return dbConnections[appId]; +} + + +module.exports = DatabaseProvider; \ No newline at end of file diff --git a/classes/FilesProvider.js b/classes/FilesProvider.js new file mode 100644 index 0000000000..ff7feb3814 --- /dev/null +++ b/classes/FilesProvider.js @@ -0,0 +1,20 @@ +var BaseProvider = require('./BaseProvider'); +var util = require('util'); + +// Singleton for the entire server. TODO: Refactor away from singleton paradigm +var instance = null; + +// TODO: Instantiate the adapter if it's a constructor. +function FilesProvider(adapter) { + if (instance) { + return instance; + } + + instance = this; + + this.adapter = adapter; +}; + +util.inherits(FilesProvider, BaseProvider); + +module.exports = FilesProvider; \ No newline at end of file diff --git a/files.js b/files.js index a840e098de..739f9b814a 100644 --- a/files.js +++ b/files.js @@ -3,7 +3,7 @@ var bodyParser = require('body-parser'), Config = require('./Config'), express = require('express'), - FilesAdapter = require('./FilesAdapter'), + FilesProvider = require('./classes/FilesProvider'), middlewares = require('./middlewares.js'), mime = require('mime'), Parse = require('parse/node').Parse, @@ -12,6 +12,12 @@ var bodyParser = require('body-parser'), var router = express.Router(); var processCreate = function(req, res, next) { + var FilesAdapter = (new FilesProvider()).getAdapter(); + + if (!FilesAdapter) { + throw new Error('Unable to get an instance of the FilesAdapter'); + } + if (!req.body || !req.body.length) { next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.')); @@ -40,10 +46,10 @@ var processCreate = function(req, res, next) { } var filename = rack() + '_' + req.params.filename + extension; - FilesAdapter.getAdapter().create(req.config, filename, req.body) + FilesAdapter.create(req.config, filename, req.body) .then(() => { res.status(201); - var location = FilesAdapter.getAdapter().location(req.config, req, filename); + var location = FilesAdapter.location(req.config, req, filename); res.set('Location', location); res.json({ url: location, name: filename }); }).catch((error) => { @@ -53,8 +59,10 @@ var processCreate = function(req, res, next) { }; var processGet = function(req, res) { + var FilesAdapter = (new FilesProvider()).getAdapter(); var config = new Config(req.params.appId); - FilesAdapter.getAdapter().get(config, req.params.filename) + + FilesAdapter.get(config, req.params.filename) .then((data) => { res.status(200); var contentType = mime.lookup(req.params.filename); diff --git a/index.js b/index.js index 9f8a5a4702..9f5df0aaa4 100644 --- a/index.js +++ b/index.js @@ -3,14 +3,15 @@ var batch = require('./batch'), bodyParser = require('body-parser'), cache = require('./cache'), - DatabaseAdapter = require('./DatabaseAdapter'), express = require('express'), - FilesAdapter = require('./FilesAdapter'), S3Adapter = require('./S3Adapter'), middlewares = require('./middlewares'), multer = require('multer'), Parse = require('parse/node').Parse, PromiseRouter = require('./PromiseRouter'), + BaseProvider = require('./classes/BaseProvider'), + DatabaseProvider = require('./classes/DatabaseProvider'), + FilesProvider = require('./classes/FilesProvider'), httpRequest = require('./httpRequest'); // Mutate the Parse object to add the Cloud Code handlers @@ -38,20 +39,24 @@ addParseCloud(); // "dotNetKey": optional key from Parse dashboard // "restAPIKey": optional key from Parse dashboard // "javascriptKey": optional key from Parse dashboard +// + +var DefaultDatabaseAdapter = require('./ExportAdapter'); +var DefaultFilesAdapter = require('./GridStoreAdapter'); + function ParseServer(args) { if (!args.appId || !args.masterKey) { throw 'You must provide an appId and masterKey!'; } - if (args.databaseAdapter) { - DatabaseAdapter.setAdapter(args.databaseAdapter); - } - if (args.filesAdapter) { - FilesAdapter.setAdapter(args.filesAdapter); - } + this.databaseProvider = new DatabaseProvider(args.databaseAdapter || DefaultDatabaseAdapter); + this.filesProvider = new FilesProvider(args.filesAdapter || DefaultFilesAdapter); + + // TODO: Move this into the instantiation if (args.databaseURI) { - DatabaseAdapter.setAppDatabaseURI(args.appId, args.databaseURI); + this.databaseProvider.setAppDatabaseURI(args.appId, args.databaseURI); } + if (args.cloud) { addParseCloud(); if (typeof args.cloud === 'function') { diff --git a/interfaces/ServiceProvider.js b/interfaces/ServiceProvider.js new file mode 100644 index 0000000000..c73c5f450c --- /dev/null +++ b/interfaces/ServiceProvider.js @@ -0,0 +1,12 @@ +function ServiceProviderInterface() { +}; + +ServiceProviderInterface.prototype.getAdapter = function() { + throw new Error('A service provider must implement getAdapter!'); +} + +ServiceProviderInterface.prototype.setAdapter = function() { + throw new Error('A service provider must implement setAdapter!'); +} + +module.exports = ServiceProviderInterface; \ No newline at end of file diff --git a/spec/ParseAPI.spec.js b/spec/ParseAPI.spec.js index 348b14181a..8de9484070 100644 --- a/spec/ParseAPI.spec.js +++ b/spec/ParseAPI.spec.js @@ -1,8 +1,9 @@ // 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 DatabaseProvider = require('../classes/DatabaseProvider'); var request = require('request'); +var dbProvider = new DatabaseProvider(); describe('miscellaneous', function() { it('create a GameScore object', function(done) { @@ -358,7 +359,7 @@ describe('miscellaneous', function() { obj.set('foo', 'bar'); return obj.save(); }).then(() => { - var db = DatabaseAdapter.getDatabaseConnection(appId); + var db = dbProvider.getDatabaseConnection(appId); return db.mongoFind('TestObject', {}, {}); }).then((results) => { expect(results.length).toEqual(1); diff --git a/spec/ParseInstallation.spec.js b/spec/ParseInstallation.spec.js index 6d8e61625f..c5ae2eb5ea 100644 --- a/spec/ParseInstallation.spec.js +++ b/spec/ParseInstallation.spec.js @@ -4,12 +4,12 @@ var auth = require('../Auth'); var cache = require('../cache'); var Config = require('../Config'); -var DatabaseAdapter = require('../DatabaseAdapter'); +var DatabaseProvider = require('../classes/DatabaseProvider'); var Parse = require('parse/node').Parse; var rest = require('../rest'); var config = new Config('test'); -var database = DatabaseAdapter.getDatabaseConnection('test'); +var database = (new DatabaseProvider()).getDatabaseConnection('test'); describe('Installations', () => { diff --git a/spec/RestCreate.spec.js b/spec/RestCreate.spec.js index 59de11ead9..739ef1d5d4 100644 --- a/spec/RestCreate.spec.js +++ b/spec/RestCreate.spec.js @@ -2,13 +2,13 @@ var auth = require('../Auth'); var cache = require('../cache'); var Config = require('../Config'); -var DatabaseAdapter = require('../DatabaseAdapter'); +var DatabaseProvider = require('../classes/DatabaseProvider'); var Parse = require('parse/node').Parse; var rest = require('../rest'); var request = require('request'); var config = new Config('test'); -var database = DatabaseAdapter.getDatabaseConnection('test'); +var database = (new DatabaseProvider()).getDatabaseConnection('test'); describe('rest create', () => { it('handles _id', (done) => { diff --git a/spec/helper.js b/spec/helper.js index cca4d1a570..7c56851d4c 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -3,7 +3,6 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000; var cache = require('../cache'); -var DatabaseAdapter = require('../DatabaseAdapter'); var express = require('express'); var facebook = require('../facebook'); var ParseServer = require('../index').ParseServer; @@ -38,6 +37,8 @@ Parse.serverURL = 'http://localhost:' + port + '/1'; // TODO: update tests to work in an A+ way Parse.Promise.disableAPlusCompliant(); +var DatabaseProvider = require('../classes/DatabaseProvider'); + beforeEach(function(done) { Parse.initialize('test', 'test', 'test'); mockFacebook(); @@ -191,8 +192,9 @@ function mockFacebook() { function clearData() { var promises = []; - for (var conn in DatabaseAdapter.dbConnections) { - promises.push(DatabaseAdapter.dbConnections[conn].deleteEverything()); + var connections = (new DatabaseProvider()).getDatabaseConnections(); + for (var conn in connections) { + promises.push(connections[conn].deleteEverything()); } return Promise.all(promises); } From 11412d5e6fe7897541668fb56d873d498cd9373c Mon Sep 17 00:00:00 2001 From: Alexander Mays Date: Fri, 5 Feb 2016 18:15:17 -0500 Subject: [PATCH 2/2] Readability updates. Signed-off-by: Alexander Mays --- classes/BaseProvider.js | 7 +++++-- classes/DatabaseProvider.js | 12 ++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/classes/BaseProvider.js b/classes/BaseProvider.js index c1f8d3afa3..856646af06 100644 --- a/classes/BaseProvider.js +++ b/classes/BaseProvider.js @@ -7,12 +7,15 @@ function BaseProvider(adapter) { util.inherits(BaseProvider, ServiceProviderInterface); -BaseProvider.prototype.getAdapter = function getAdapter() { +function getAdapter() { return this.adapter; } -BaseProvider.prototype.setAdapter = function setAdapter(adapter) { +function setAdapter(adapter) { this.adapter = adapter; } +BaseProvider.prototype.getAdapter = getAdapter; +BaseProvider.prototype.setAdapter = setAdapter; + module.exports = BaseProvider; \ No newline at end of file diff --git a/classes/DatabaseProvider.js b/classes/DatabaseProvider.js index 8c594a4335..11214bb70c 100644 --- a/classes/DatabaseProvider.js +++ b/classes/DatabaseProvider.js @@ -25,19 +25,19 @@ function DatabaseProvider(adapter) { util.inherits(DatabaseProvider, BaseProvider); -DatabaseProvider.prototype.setDatabaseURI = function setDatabaseURI(uri) { +function setDatabaseURI(uri) { databaseURI = uri; }; -DatabaseProvider.prototype.setAppDatabaseURI = function setAppDatabaseURI(appId, uri) { +function setAppDatabaseURI(appId, uri) { appDatabaseURIs[appId] = uri; } -DatabaseProvider.prototype.getDatabaseConnections = function getDatabaseConnections() { +function getDatabaseConnections() { return dbConnections; } -DatabaseProvider.prototype.getDatabaseConnection = function getDatabaseConnection(appId) { +function getDatabaseConnection(appId) { if (dbConnections[appId]) { return dbConnections[appId]; } @@ -53,5 +53,9 @@ DatabaseProvider.prototype.getDatabaseConnection = function getDatabaseConnectio return dbConnections[appId]; } +DatabaseProvider.prototype.setDatabaseURI = setDatabaseURI; +DatabaseProvider.prototype.setAppDatabaseURI = setAppDatabaseURI; +DatabaseProvider.prototype.getDatabaseConnections = getDatabaseConnections; +DatabaseProvider.prototype.getDatabaseConnection = getDatabaseConnection; module.exports = DatabaseProvider; \ No newline at end of file