diff --git a/spec/index.spec.js b/spec/index.spec.js index 56f9bb6bff..01b90681a5 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -1,6 +1,8 @@ var request = require('request'); var parseServerPackage = require('../package.json'); var MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions'); +var ParseServer = require("../src/index"); +var express = require('express'); describe('server', () => { it('requires a master key and app id', done => { @@ -168,4 +170,64 @@ describe('server', () => { done(); }) }); + + it('can create a parse-server', done => { + var parseServer = new ParseServer.default({ + appId: "aTestApp", + masterKey: "aTestMasterKey", + serverURL: "http://localhost:12666/parse", + databaseURI: 'mongodb://localhost:27017/aTestApp' + }); + + expect(Parse.applicationId).toEqual("aTestApp"); + var app = express(); + app.use('/parse', parseServer.app); + + var server = app.listen(12666); + var obj = new Parse.Object("AnObject"); + var objId; + obj.save().then((obj) => { + objId = obj.id; + var q = new Parse.Query("AnObject"); + return q.first(); + }).then((obj) => { + expect(obj.id).toEqual(objId); + server.close(); + done(); + }).fail((err) => { + server.close(); + done(); + }) + }); + + it('can create a parse-server', done => { + var parseServer = ParseServer.ParseServer({ + appId: "anOtherTestApp", + masterKey: "anOtherTestMasterKey", + serverURL: "http://localhost:12667/parse", + databaseURI: 'mongodb://localhost:27017/anotherTstApp' + }); + + expect(Parse.applicationId).toEqual("anOtherTestApp"); + var app = express(); + app.use('/parse', parseServer); + + var server = app.listen(12667); + var obj = new Parse.Object("AnObject"); + var objId; + obj.save().then((obj) => { + objId = obj.id; + var q = new Parse.Query("AnObject"); + console.log("query") + return q.first(); + }).then((obj) => { + console.log("result"); + expect(obj.id).toEqual(objId); + server.close(); + done(); + }).fail((err) => { + server.close(); + done(); + }) + }); }); diff --git a/src/index.js b/src/index.js index 01a1d48ab4..cf99e0d7ac 100644 --- a/src/index.js +++ b/src/index.js @@ -78,187 +78,205 @@ addParseCloud(); // "javascriptKey": optional key from Parse dashboard // "push": optional key from configure push -function ParseServer({ - appId = requiredParameter('You must provide an appId!'), - masterKey = requiredParameter('You must provide a masterKey!'), - appName, - databaseAdapter, - filesAdapter, - push, - loggerAdapter, - databaseURI = DatabaseAdapter.defaultDatabaseURI, - databaseOptions, - cloud, - collectionPrefix = '', - clientKey, - javascriptKey, - dotNetKey, - restAPIKey, - fileKey = 'invalid-file-key', - facebookAppIds = [], - enableAnonymousUsers = true, - allowClientClassCreation = true, - oauth = {}, - serverURL = requiredParameter('You must provide a serverURL!'), - maxUploadSize = '20mb', - verifyUserEmails = false, - emailAdapter, - publicServerURL, - customPages = { - invalidLink: undefined, - verifyEmailSuccess: undefined, - choosePassword: undefined, - passwordResetSuccess: undefined - }, - liveQuery = {} -}) { - setFeature('serverVersion', parseServerPackage.version); - // Initialize the node client SDK automatically - Parse.initialize(appId, javascriptKey || 'unused', masterKey); - Parse.serverURL = serverURL; - - if (databaseAdapter) { - DatabaseAdapter.setAdapter(databaseAdapter); - } - - if (databaseURI) { - DatabaseAdapter.setAppDatabaseURI(appId, databaseURI); - } +export default class ParseServer { + + constructor({ + appId = requiredParameter('You must provide an appId!'), + masterKey = requiredParameter('You must provide a masterKey!'), + appName, + databaseAdapter, + filesAdapter, + push, + loggerAdapter, + databaseURI = DatabaseAdapter.defaultDatabaseURI, + databaseOptions, + cloud, + collectionPrefix = '', + clientKey, + javascriptKey, + dotNetKey, + restAPIKey, + fileKey = 'invalid-file-key', + facebookAppIds = [], + enableAnonymousUsers = true, + allowClientClassCreation = true, + oauth = {}, + serverURL = requiredParameter('You must provide a serverURL!'), + maxUploadSize = '20mb', + verifyUserEmails = false, + emailAdapter, + publicServerURL, + customPages = { + invalidLink: undefined, + verifyEmailSuccess: undefined, + choosePassword: undefined, + passwordResetSuccess: undefined + }, + liveQuery = {} + }) { + setFeature('serverVersion', parseServerPackage.version); + // Initialize the node client SDK automatically + Parse.initialize(appId, javascriptKey || 'unused', masterKey); + Parse.serverURL = serverURL; + + if (databaseAdapter) { + DatabaseAdapter.setAdapter(databaseAdapter); + } - if (databaseOptions) { - DatabaseAdapter.setAppDatabaseOptions(appId, databaseOptions); - } + if (databaseOptions) { + DatabaseAdapter.setAppDatabaseOptions(appId, databaseOptions); + } - if (cloud) { - addParseCloud(); - if (typeof cloud === 'function') { - cloud(Parse) - } else if (typeof cloud === 'string') { - require(cloud); - } else { - throw "argument 'cloud' must either be a string or a function"; + if (databaseURI) { + DatabaseAdapter.setAppDatabaseURI(appId, databaseURI); } - } - const filesControllerAdapter = loadAdapter(filesAdapter, () => { - return new GridStoreAdapter(databaseURI); - }); - // Pass the push options too as it works with the default - const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push); - const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter); - const emailControllerAdapter = loadAdapter(emailAdapter); - // We pass the options and the base class for the adatper, - // Note that passing an instance would work too - const filesController = new FilesController(filesControllerAdapter, appId); - const pushController = new PushController(pushControllerAdapter, appId); - const loggerController = new LoggerController(loggerControllerAdapter, appId); - const hooksController = new HooksController(appId, collectionPrefix); - const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails }); - const liveQueryController = new LiveQueryController(liveQuery); - - - cache.apps.set(appId, { - masterKey: masterKey, - serverURL: serverURL, - collectionPrefix: collectionPrefix, - clientKey: clientKey, - javascriptKey: javascriptKey, - dotNetKey: dotNetKey, - restAPIKey: restAPIKey, - fileKey: fileKey, - facebookAppIds: facebookAppIds, - filesController: filesController, - pushController: pushController, - loggerController: loggerController, - hooksController: hooksController, - userController: userController, - verifyUserEmails: verifyUserEmails, - allowClientClassCreation: allowClientClassCreation, - authDataManager: authDataManager(oauth, enableAnonymousUsers), - appName: appName, - publicServerURL: publicServerURL, - customPages: customPages, - liveQueryController: liveQueryController - }); - - // To maintain compatibility. TODO: Remove in some version that breaks backwards compatability - if (process.env.FACEBOOK_APP_ID) { - cache.apps.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID); - } + if (cloud) { + addParseCloud(); + if (typeof cloud === 'function') { + cloud(Parse) + } else if (typeof cloud === 'string') { + require(cloud); + } else { + throw "argument 'cloud' must either be a string or a function"; + } + } - Config.validate(cache.apps.get(appId)); - // This app serves the Parse API directly. - // It's the equivalent of https://api.parse.com/1 in the hosted Parse API. - var api = express(); - //api.use("/apps", express.static(__dirname + "/public")); - // File handling needs to be before default middlewares are applied - api.use('/', middlewares.allowCrossDomain, new FilesRouter().getExpressRouter({ - maxUploadSize: maxUploadSize - })); + const filesControllerAdapter = loadAdapter(filesAdapter, () => { + return new GridStoreAdapter(databaseURI); + }); + // Pass the push options too as it works with the default + const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push); + const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter); + const emailControllerAdapter = loadAdapter(emailAdapter); + // We pass the options and the base class for the adatper, + // Note that passing an instance would work too + const filesController = new FilesController(filesControllerAdapter, appId); + const pushController = new PushController(pushControllerAdapter, appId); + const loggerController = new LoggerController(loggerControllerAdapter, appId); + const hooksController = new HooksController(appId, collectionPrefix); + const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails }); + const liveQueryController = new LiveQueryController(liveQuery); + + cache.apps.set(appId, { + masterKey: masterKey, + serverURL: serverURL, + collectionPrefix: collectionPrefix, + clientKey: clientKey, + javascriptKey: javascriptKey, + dotNetKey: dotNetKey, + restAPIKey: restAPIKey, + fileKey: fileKey, + facebookAppIds: facebookAppIds, + filesController: filesController, + pushController: pushController, + loggerController: loggerController, + hooksController: hooksController, + userController: userController, + verifyUserEmails: verifyUserEmails, + allowClientClassCreation: allowClientClassCreation, + authDataManager: authDataManager(oauth, enableAnonymousUsers), + appName: appName, + publicServerURL: publicServerURL, + customPages: customPages, + maxUploadSize: maxUploadSize, + liveQueryController: liveQueryController + }); - api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressApp()); + // To maintain compatibility. TODO: Remove in some version that breaks backwards compatability + if (process.env.FACEBOOK_APP_ID) { + cache.apps.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID); + } - // TODO: separate this from the regular ParseServer object - if (process.env.TESTING == 1) { - api.use('/', require('./testing-routes').router); + Config.validate(cache.apps.get(appId)); + this.config = cache.apps.get(appId); + hooksController.load(); } - api.use(bodyParser.json({ 'type': '*/*' , limit: maxUploadSize })); - api.use(middlewares.allowCrossDomain); - api.use(middlewares.allowMethodOverride); - api.use(middlewares.handleParseHeaders); - - let routers = [ - new ClassesRouter(), - new UsersRouter(), - new SessionsRouter(), - new RolesRouter(), - new AnalyticsRouter(), - new InstallationsRouter(), - new FunctionsRouter(), - new SchemasRouter(), - new PushRouter(), - new LogsRouter(), - new IAPValidationRouter(), - new FeaturesRouter(), - ]; - - if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) { - routers.push(new GlobalConfigRouter()); + get app() { + return ParseServer.app(this.config); } - if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) { - routers.push(new HooksRouter()); - } + static app({maxUploadSize = '20mb'}) { + // This app serves the Parse API directly. + // It's the equivalent of https://api.parse.com/1 in the hosted Parse API. + var api = express(); + //api.use("/apps", express.static(__dirname + "/public")); + // File handling needs to be before default middlewares are applied + api.use('/', middlewares.allowCrossDomain, new FilesRouter().getExpressRouter({ + maxUploadSize: maxUploadSize + })); + + api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressApp()); + + // TODO: separate this from the regular ParseServer object + if (process.env.TESTING == 1) { + api.use('/', require('./testing-routes').router); + } - let routes = routers.reduce((memo, router) => { - return memo.concat(router.routes); - }, []); + api.use(bodyParser.json({ 'type': '*/*' , limit: maxUploadSize })); + api.use(middlewares.allowCrossDomain); + api.use(middlewares.allowMethodOverride); + api.use(middlewares.handleParseHeaders); + + let routers = [ + new ClassesRouter(), + new UsersRouter(), + new SessionsRouter(), + new RolesRouter(), + new AnalyticsRouter(), + new InstallationsRouter(), + new FunctionsRouter(), + new SchemasRouter(), + new PushRouter(), + new LogsRouter(), + new IAPValidationRouter(), + new FeaturesRouter(), + ]; + + if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) { + routers.push(new GlobalConfigRouter()); + } - let appRouter = new PromiseRouter(routes); + if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) { + routers.push(new HooksRouter()); + } - batch.mountOnto(appRouter); + let routes = routers.reduce((memo, router) => { + return memo.concat(router.routes); + }, []); - api.use(appRouter.expressApp()); + let appRouter = new PromiseRouter(routes); - api.use(middlewares.handleParseErrors); + batch.mountOnto(appRouter); - //This causes tests to spew some useless warnings, so disable in test - if (!process.env.TESTING) { - process.on('uncaughtException', (err) => { - if( err.code === "EADDRINUSE" ) { // user-friendly message for this common error - console.log(`Unable to listen on port ${err.port}. The port is already in use.`); - process.exit(0); - } - else { - throw err; - } - }); + api.use(appRouter.expressApp()); + + api.use(middlewares.handleParseErrors); + + //This causes tests to spew some useless warnings, so disable in test + if (!process.env.TESTING) { + process.on('uncaughtException', (err) => { + if ( err.code === "EADDRINUSE" ) { // user-friendly message for this common error + console.log(`Unable to listen on port ${err.port}. The port is already in use.`); + process.exit(0); + } else { + throw err; + } + }); + } + return api; } - hooksController.load(); - return api; + static ParseServer(options) { + let server = new ParseServer(options); + return server.app; + } + + static createLiveQueryServer(httpServer, config) { + return new ParseLiveQueryServer(httpServer, config); + } } function addParseCloud() { @@ -267,12 +285,9 @@ function addParseCloud() { global.Parse = Parse; } -ParseServer.createLiveQueryServer = function(httpServer, config) { - return new ParseLiveQueryServer(httpServer, config); +let runServer = function(options) { + return ParseServer.ParseServer(options); } -module.exports = { - ParseServer: ParseServer, - S3Adapter: S3Adapter, - GCSAdapter: GCSAdapter -}; +export { S3Adapter, GCSAdapter }; +export { runServer as ParseServer };