Refactors configuration management (#4271)

* Adds flow types / Configuration interfaces

* Lets call it options

* Use a single interface to generate the configurations

* Translates options to definitions only if comments are set

* improves logic

* Moves objects around

* Fixes issue affecting logging of circular objects

* fixes undefined env

* Moves all defaults to defaults

* Adds back CLI defaults

* Restored defaults in commander.js

* Merge provided defaults and platform defaults

* Addresses visual nits

* Improves Config.js code

* Adds ability to pass the default value in trailing comments

* Load platform defaults from the definitions file

* proper default values on various options

* Adds ParseServer.start and server.start(options) as quick startup methods

* Moves creating liveQueryServer http into ParseServer.js

* removes dead code

* Adds tests to guarantee we can start a LQ Server from main module

* Fixes incorrect code regading liveQuery init port

* Start a http server for LQ if port is specified

* ensure we dont fail if config.port is not set

* Specify port

* ignore other path skipped in tests

* Adds test for custom middleware setting

* Refactors new Config into Config.get

- Hides AppCache from ParseServer.js, use Config.put which validates

* Extracts controller creation into Controllers/index.js

- This makes the ParseServer init way simpler

* Move serverURL inference into ParseServer

* review nits
This commit is contained in:
Florent Vilmart
2017-10-23 08:43:05 -04:00
committed by GitHub
parent d29a4483d0
commit 9de4b8b2a7
55 changed files with 1462 additions and 874 deletions

View File

@@ -0,0 +1,230 @@
/**
* Parse Server Configuration Builder
*
* This module builds the definitions file (src/Options/Definitions.js)
* from the src/Options/index.js options interfaces.
* The Definitions.js module is responsible for the default values as well
* as the mappings for the CLI.
*
* To rebuild the definitions file, run
* `$ node resources/buildConfigDefinitions.js`
*/
const parsers = require('../src/Options/parsers');
function last(array) {
return array[array.length - 1];
}
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
function toENV(key) {
let str = '';
let previousIsUpper = false;
for(let i = 0; i < key.length; i++) {
const char = key[i];
if (letters.indexOf(char) >= 0) {
if (!previousIsUpper) {
str += '_';
previousIsUpper = true;
}
} else {
previousIsUpper = false;
}
str += char;
}
return str.toUpperCase();
}
function getCommentValue(comment) {
if (!comment) { return }
return comment.value.trim();
}
function getENVPrefix(iface) {
if (iface.id.name === 'ParseServerOptions') {
return 'PARSE_SERVER_';
}
if (iface.id.name === 'CustomPagesOptions') {
return 'PARSE_SERVER_CUSTOM_PAGES_';
}
if (iface.id.name === 'LiveQueryServerOptions') {
return 'PARSE_SERVER_LIVE_QUERY_';
}
}
function processProperty(property, iface) {
const firstComment = getCommentValue(last(property.leadingComments || []));
const lastComment = getCommentValue((property.trailingComments || [])[0]);
const name = property.key.name;
const prefix = getENVPrefix(iface);
if (!firstComment) {
return;
}
const components = firstComment.split(':ENV:').map((elt) => {
return elt.trim();
});
let defaultValue;
if (lastComment && lastComment.indexOf('=') >= 0) {
const slice = lastComment.slice(lastComment.indexOf('=') + 1, lastComment.length).trim();
defaultValue = slice;
}
const help = components[0];
const env = components[1] || (prefix + toENV(name));
let type = property.value.type;
let isRequired = true;
if (type == 'NullableTypeAnnotation') {
isRequired = false;
type = property.value.typeAnnotation.type;
}
return {
name,
env,
help,
type,
defaultValue,
types: property.value.types,
typeAnnotation: property.value.typeAnnotation,
required: isRequired
};
}
function doInterface(iface) {
return iface.body.properties
.map((prop) => processProperty(prop, iface))
.filter((e) => e !== undefined);
}
function mapperFor(elt, t) {
const p = t.identifier('parsers');
const wrap = (identifier) => t.memberExpression(p, identifier);
if (t.isNumberTypeAnnotation(elt)) {
return t.callExpression(wrap(t.identifier('numberParser')), [t.stringLiteral(elt.name)]);
} else if (t.isArrayTypeAnnotation(elt)) {
return wrap(t.identifier('arrayParser'));
} else if (t.isAnyTypeAnnotation(elt)) {
return wrap(t.identifier('objectParser'));
} else if (t.isBooleanTypeAnnotation(elt)) {
return wrap(t.identifier('booleanParser'));
} else if (t.isGenericTypeAnnotation(elt)) {
const type = elt.typeAnnotation.id.name;
if (type == 'Adapter') {
return wrap(t.identifier('moduleOrObjectParser'));
}
if (type == 'NumberOrBoolean') {
return wrap(t.identifier('numberOrBooleanParser'));
}
return wrap(t.identifier('objectParser'));
}
}
function parseDefaultValue(elt, value, t) {
let litteralValue;
if (t.isStringTypeAnnotation(elt)) {
if (value == '""' || value == "''") {
litteralValue = t.stringLiteral('');
} else {
litteralValue = t.stringLiteral(value);
}
} else if (t.isNumberTypeAnnotation(elt)) {
litteralValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
} else if (t.isArrayTypeAnnotation(elt)) {
const array = parsers.objectParser(value);
litteralValue = t.arrayExpression(array.map((value) => {
if (typeof value == 'string') {
return t.stringLiteral(value);
} else {
throw new Error('Unable to parse array');
}
}));
} else if (t.isAnyTypeAnnotation(elt)) {
litteralValue = t.arrayExpression([]);
} else if (t.isBooleanTypeAnnotation(elt)) {
litteralValue = t.booleanLiteral(parsers.booleanParser(value));
} else if (t.isGenericTypeAnnotation(elt)) {
const type = elt.typeAnnotation.id.name;
if (type == 'NumberOrBoolean') {
litteralValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
}
if (type == 'CustomPagesOptions') {
const object = parsers.objectParser(value);
const props = Object.keys(object).map((key) => {
return t.objectProperty(key, object[value]);
});
litteralValue = t.objectExpression(props);
}
}
return litteralValue;
}
function inject(t, list) {
return list.map((elt) => {
if (!elt.name) {
return;
}
const props = ['env', 'help'].map((key) => {
if (elt[key]) {
return t.objectProperty(t.stringLiteral(key), t.stringLiteral(elt[key]));
}
}).filter((e) => e !== undefined);
if (elt.required) {
props.push(t.objectProperty(t.stringLiteral('required'), t.booleanLiteral(true)))
}
const action = mapperFor(elt, t);
if (action) {
props.push(t.objectProperty(t.stringLiteral('action'), action))
}
if (elt.defaultValue) {
const parsedValue = parseDefaultValue(elt, elt.defaultValue, t);
if (parsedValue) {
props.push(t.objectProperty(t.stringLiteral('default'), parsedValue));
} else {
throw new Error(`Unable to parse value for ${elt.name} `);
}
}
const obj = t.objectExpression(props);
return t.objectProperty(t.stringLiteral(elt.name), obj);
}).filter((elt) => {
return elt != undefined;
});
}
const makeRequire = function(variableName, module, t) {
const decl = t.variableDeclarator(t.identifier(variableName), t.callExpression(t.identifier('require'), [t.stringLiteral(module)]));
return t.variableDeclaration('var', [decl])
}
const plugin = function (babel) {
const t = babel.types;
const moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports'));
return {
visitor: {
Program: function(path) {
// Inject the parser's loader
path.unshiftContainer('body', makeRequire('parsers', './parsers', t));
},
ExportDeclaration: function(path) {
// Export declaration on an interface
if (path.node && path.node.declaration && path.node.declaration.type == 'InterfaceDeclaration') {
const l = inject(t, doInterface(path.node.declaration));
const id = path.node.declaration.id.name;
const exports = t.memberExpression(moduleExports, t.identifier(id));
path.replaceWith(
t.assignmentExpression('=', exports, t.objectExpression(l))
)
}
}
}
}
};
const auxiliaryCommentBefore = `
**** GENERATED CODE ****
This code has been generated by resources/buildConfigDefinitions.js
Do not edit manually, but update Options/index.js
`
const babel = require("babel-core");
const res = babel.transformFileSync('./src/Options/index.js', { plugins: [ plugin ], auxiliaryCommentBefore });
require('fs').writeFileSync('./src/Options/Definitions.js', res.code + '\n');

View File

@@ -71,7 +71,7 @@ describe("Account Lockout Policy: ", () => {
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
new Config('test');
Config.get('test');
fail('set duration to an invalid number test failed');
done();
})
@@ -95,7 +95,7 @@ describe("Account Lockout Policy: ", () => {
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
new Config('test');
Config.get('test');
fail('set threshold to an invalid number test failed');
done();
})
@@ -119,7 +119,7 @@ describe("Account Lockout Policy: ", () => {
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
new Config('test');
Config.get('test');
fail('threshold value < 1 is invalid test failed');
done();
})
@@ -143,7 +143,7 @@ describe("Account Lockout Policy: ", () => {
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
new Config('test');
Config.get('test');
fail('threshold value > 999 is invalid test failed');
done();
})
@@ -167,7 +167,7 @@ describe("Account Lockout Policy: ", () => {
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
new Config('test');
Config.get('test');
fail('duration value < 1 is invalid test failed');
done();
})
@@ -191,7 +191,7 @@ describe("Account Lockout Policy: ", () => {
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
new Config('test');
Config.get('test');
fail('duration value > 99999 is invalid test failed');
done();
})

View File

@@ -135,7 +135,7 @@ describe("AdapterLoader", ()=>{
reconfigureServer({
push: pushAdapterOptions,
}).then(() => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const pushAdapter = config.pushWorker.adapter;
expect(pushAdapter.getValidPushTypes()).toEqual(['ios']);
expect(pushAdapter.options).toEqual(pushAdapterOptions);

View File

@@ -5,7 +5,7 @@ var AudiencesRouter = require('../src/Routers/AudiencesRouter').AudiencesRouter;
describe('AudiencesRouter', () => {
it('uses find condition from request.body', (done) => {
var config = new Config('test');
var config = Config.get('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
@@ -46,7 +46,7 @@ describe('AudiencesRouter', () => {
});
it('uses find condition from request.query', (done) => {
var config = new Config('test');
var config = Config.get('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
@@ -87,7 +87,7 @@ describe('AudiencesRouter', () => {
});
it('query installations with limit = 0', (done) => {
var config = new Config('test');
var config = Config.get('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
@@ -106,7 +106,7 @@ describe('AudiencesRouter', () => {
info: {}
};
new Config('test');
Config.get('test');
var router = new AudiencesRouter();
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
.then(() => {
@@ -127,7 +127,7 @@ describe('AudiencesRouter', () => {
});
it('query installations with count = 1', done => {
var config = new Config('test');
var config = Config.get('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
@@ -163,7 +163,7 @@ describe('AudiencesRouter', () => {
});
it('query installations with limit = 0 and count = 1', (done) => {
var config = new Config('test');
var config = Config.get('test');
var androidAudienceRequest = {
'name': 'Android Users',
'query': '{ "test": "android" }'
@@ -286,7 +286,7 @@ describe('AudiencesRouter', () => {
});
it_exclude_dbs(['postgres'])('should support legacy parse.com audience fields', (done) => {
const database = (new Config(Parse.applicationId)).database.adapter.database;
const database = (Config.get(Parse.applicationId)).database.adapter.database;
const now = new Date();
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
.then((audience) => {

View File

@@ -186,7 +186,7 @@ describe('AuthenticationProviders', function() {
ok(!provider.synchronizedExpiration,
"Expiration should be cleared");
// make sure the auth data is properly deleted
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
config.database.adapter.find('_User', {
fields: Object.assign({}, defaultColumns._Default, defaultColumns._Installation),
}, { objectId: model.id }, {})

View File

@@ -242,7 +242,7 @@ describe("Email Verification Token Expiration: ", () => {
return user.signUp();
})
.then(() => {
const config = new Config('test');
const config = Config.get('test');
return config.database.find('_User', {username: 'sets_email_verify_token_expires_at'});
})
.then(results => {
@@ -289,7 +289,7 @@ describe("Email Verification Token Expiration: ", () => {
followRedirect: false,
}, (error, response) => {
expect(response.statusCode).toEqual(302);
const config = new Config('test');
const config = Config.get('test');
return config.database.find('_User', {username: 'unsets_email_verify_token_expires_at'}).then((results) => {
expect(results.length).toBe(1);
return results[0];
@@ -448,7 +448,7 @@ describe("Email Verification Token Expiration: ", () => {
return user.signUp();
})
.then(() => {
const config = new Config('test');
const config = Config.get('test');
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
return results[0];
});
@@ -465,7 +465,7 @@ describe("Email Verification Token Expiration: ", () => {
});
})
.then(() => {
const config = new Config('test');
const config = Config.get('test');
return config.database.find('_User', {username: 'newEmailVerifyTokenOnEmailReset'}).then((results) => {
return results[0];
});

View File

@@ -45,5 +45,5 @@ describe('Enable single schema cache', () => {
});
const fakeRequestForConfig = function() {
return new Config('test');
return Config.get('test');
};

View File

@@ -17,7 +17,7 @@ const mockAdapter = {
describe("FilesController",() =>{
it("should properly expand objects", (done) => {
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse');
var filesController = new FilesController(gridStoreAdapter)
var result = filesController.expandFilesInObject(config, function(){});

View File

@@ -11,7 +11,7 @@ describe_only_db('mongo')("GridStoreAdapter",() =>{
it("should properly instanciate the GridStore when deleting a file", (done) => {
var databaseURI = 'mongodb://localhost:27017/parse';
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var gridStoreAdapter = new GridStoreAdapter(databaseURI);
var filesController = new FilesController(gridStoreAdapter);

View File

@@ -5,7 +5,7 @@ var InstallationsRouter = require('../src/Routers/InstallationsRouter').Installa
describe('InstallationsRouter', () => {
it('uses find condition from request.body', (done) => {
var config = new Config('test');
var config = Config.get('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
@@ -43,7 +43,7 @@ describe('InstallationsRouter', () => {
});
it('uses find condition from request.query', (done) => {
var config = new Config('test');
var config = Config.get('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
@@ -81,7 +81,7 @@ describe('InstallationsRouter', () => {
});
it('query installations with limit = 0', (done) => {
var config = new Config('test');
var config = Config.get('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
@@ -100,7 +100,7 @@ describe('InstallationsRouter', () => {
info: {}
};
new Config('test');
Config.get('test');
var router = new InstallationsRouter();
rest.create(config, auth.nobody(config), '_Installation', androidDeviceRequest)
.then(() => {
@@ -118,7 +118,7 @@ describe('InstallationsRouter', () => {
});
it('query installations with count = 1', done => {
var config = new Config('test');
var config = Config.get('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'
@@ -154,7 +154,7 @@ describe('InstallationsRouter', () => {
});
it('query installations with limit = 0 and count = 1', (done) => {
var config = new Config('test');
var config = Config.get('test');
var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc',
'deviceType': 'android'

View File

@@ -1234,7 +1234,7 @@ describe('Parse.ACL', () => {
});
it('regression test #701', done => {
const config = new Config('test');
const config = Config.get('test');
var anonUser = {
authData: {
anonymous: {

View File

@@ -23,7 +23,7 @@ describe_only_db('mongo')('miscellaneous', () => {
obj.set('foo', 'bar');
return obj.save();
}).then(() => {
const config = new Config(appId);
const config = Config.get(appId);
return config.database.adapter.find('TestObject', { fields: {} }, {}, {});
}).then((results) => {
expect(results.length).toEqual(1);
@@ -151,7 +151,7 @@ describe('miscellaneous', function() {
});
it('ensure that if people already have duplicate users, they can still sign up new users', done => {
const config = new Config('test');
const config = Config.get('test');
// Remove existing data to clear out unique index
TestUtils.destroyAllDataPermanently()
.then(() => config.database.adapter.createClass('_User', userSchema))
@@ -183,7 +183,7 @@ describe('miscellaneous', function() {
});
it('ensure that if people already have duplicate emails, they can still sign up new users', done => {
const config = new Config('test');
const config = Config.get('test');
// Remove existing data to clear out unique index
TestUtils.destroyAllDataPermanently()
.then(() => config.database.adapter.createClass('_User', userSchema))
@@ -211,7 +211,7 @@ describe('miscellaneous', function() {
});
it('ensure that if you try to sign up a user with a unique username and email, but duplicates in some other field that has a uniqueness constraint, you get a regular duplicate value error', done => {
const config = new Config('test');
const config = Config.get('test');
config.database.adapter.addFieldIfNotExists('_User', 'randomField', { type: 'String' })
.then(() => config.database.adapter.ensureUniqueness('_User', userSchema, ['randomField']))
.then(() => {
@@ -1546,7 +1546,7 @@ describe_only_db('mongo')('legacy _acl', () => {
},
json: true
}).then(() => {
const config = new Config('test');
const config = Config.get('test');
const adapter = config.database.adapter;
return adapter._adaptiveCollection("Report")
.then(collection => collection.find({}))

View File

@@ -5,7 +5,7 @@ const Config = require('../src/Config');
describe('a GlobalConfig', () => {
beforeEach(done => {
const config = new Config('test');
const config = Config.get('test');
const query = on_db('mongo', () => {
// Legacy is with an int...
return { objectId: 1 };
@@ -147,7 +147,7 @@ describe('a GlobalConfig', () => {
});
it('failed getting config when it is missing', (done) => {
const config = new Config('test');
const config = Config.get('test');
config.database.adapter.deleteObjectsByQuery(
'_GlobalConfig',
{ fields: { params: { __type: 'String' } } },

View File

@@ -21,7 +21,7 @@ const installationSchema = { fields: Object.assign({}, defaultColumns._Default,
describe('Installations', () => {
beforeEach(() => {
config = new Config('test');
config = Config.get('test');
database = config.database;
});

View File

@@ -1,5 +1,6 @@
var Parse = require('parse/node');
var ParseLiveQueryServer = require('../src/LiveQuery/ParseLiveQueryServer').ParseLiveQueryServer;
var ParseServer = require('../src/ParseServer').default;
// Global mock info
var queryHashValue = 'hash';
@@ -86,6 +87,66 @@ describe('ParseLiveQueryServer', function() {
expect(parseLiveQueryServer.subscriptions.size).toBe(0);
});
it('can be initialized from ParseServer', function() {
var httpServer = {};
var parseLiveQueryServer = ParseServer.createLiveQueryServer(httpServer, {});
expect(parseLiveQueryServer.clientId).toBeUndefined();
expect(parseLiveQueryServer.clients.size).toBe(0);
expect(parseLiveQueryServer.subscriptions.size).toBe(0);
});
it('can be initialized from ParseServer without httpServer', function(done) {
var parseLiveQueryServer = ParseServer.createLiveQueryServer(undefined, {
port: 22345
});
expect(parseLiveQueryServer.clientId).toBeUndefined();
expect(parseLiveQueryServer.clients.size).toBe(0);
expect(parseLiveQueryServer.subscriptions.size).toBe(0);
parseLiveQueryServer.server.close(done);
});
it('can be initialized through ParseServer without liveQueryServerOptions', function(done) {
var parseServer = ParseServer.start({
appId: 'hello',
masterKey: 'world',
port: 22345,
mountPath: '/1',
serverURL: 'http://localhost:12345/1',
liveQuery: {
classNames: ['Yolo']
},
startLiveQueryServer: true
});
expect(parseServer.liveQueryServer).not.toBeUndefined();
expect(parseServer.liveQueryServer.server).toBe(parseServer.server);
parseServer.server.close(done);
});
it('can be initialized through ParseServer with liveQueryServerOptions', function(done) {
var parseServer = ParseServer.start({
appId: 'hello',
masterKey: 'world',
port: 22346,
mountPath: '/1',
serverURL: 'http://localhost:12345/1',
liveQuery: {
classNames: ['Yolo']
},
liveQueryServerOptions: {
port: 22347,
}
});
expect(parseServer.liveQueryServer).not.toBeUndefined();
expect(parseServer.liveQueryServer.server).not.toBe(parseServer.server);
parseServer.liveQueryServer.server.close();
parseServer.server.close(done);
});
it('can handle connect command', function() {
var parseLiveQueryServer = new ParseLiveQueryServer(10, 10, {});
var parseWebSocket = {

View File

@@ -116,7 +116,7 @@ describe('Parse Role testing', () => {
return Parse.Object.saveAll(roles, { useMasterKey: true });
}).then(() => {
auth = new Auth({config: new Config("test"), isMaster: true, user: user});
auth = new Auth({config: Config.get("test"), isMaster: true, user: user});
getAllRolesSpy = spyOn(auth, "_getAllRolesNamesForRoleIds").and.callThrough();
return auth._loadRoles();
@@ -159,7 +159,7 @@ describe('Parse Role testing', () => {
return createRole(rolesNames[2], anotherRole, null);
}).then((lastRole) => {
roleIds[lastRole.get("name")] = lastRole.id;
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
var auth = new Auth({ config: Config.get("test"), isMaster: true, user: user });
return auth._loadRoles();
})
}).then((roles) => {
@@ -222,7 +222,7 @@ describe('Parse Role testing', () => {
superContentManager.getRoles().add(superModerator);
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
}).then(() => {
var auth = new Auth({ config: new Config("test"), isMaster: true });
var auth = new Auth({ config: Config.get("test"), isMaster: true });
// For each role, fetch their sibling, what they inherit
// return with result and roleId for later comparison
const promises = [admin, moderator, contentManager, superModerator].map((role) => {

View File

@@ -2523,7 +2523,7 @@ describe('Parse.User testing', () => {
});
it_exclude_dbs(['postgres'])('should cleanup null authData keys (regression test for #935)', (done) => {
const database = new Config(Parse.applicationId).database;
const database = Config.get(Parse.applicationId).database;
database.create('_User', {
username: 'user',
_hashed_password: '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie',
@@ -2557,7 +2557,7 @@ describe('Parse.User testing', () => {
});
it_exclude_dbs(['postgres'])('should not serve null authData keys', (done) => {
const database = new Config(Parse.applicationId).database;
const database = Config.get(Parse.applicationId).database;
database.create('_User', {
username: 'user',
_hashed_password: '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie',
@@ -2842,7 +2842,7 @@ describe('Parse.User testing', () => {
});
it('should not create extraneous session tokens', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
config.database.loadSchema().then((s) => {
// Lock down the _User class for creation
return s.addClassIfNotExists('_User', {}, {create: {}})

View File

@@ -4,11 +4,11 @@ var Config = require('../src/Config');
describe('Pointer Permissions', () => {
beforeEach(() => {
new Config(Parse.applicationId).database.schemaCache.clear();
Config.get(Parse.applicationId).database.schemaCache.clear();
});
it('should work with find', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -47,7 +47,7 @@ describe('Pointer Permissions', () => {
it('should work with write', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -112,7 +112,7 @@ describe('Pointer Permissions', () => {
});
it('should let a proper user find', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -172,7 +172,7 @@ describe('Pointer Permissions', () => {
});
it('should query on pointer permission enabled column', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -213,7 +213,7 @@ describe('Pointer Permissions', () => {
});
it('should not allow creating objects', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
user.set({
username: 'user1',
@@ -239,7 +239,7 @@ describe('Pointer Permissions', () => {
});
it('should handle multiple writeUserFields', done => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -278,7 +278,7 @@ describe('Pointer Permissions', () => {
});
it('should prevent creating pointer permission on missing field', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
config.database.loadSchema().then((schema) => {
return schema.addClassIfNotExists('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
}).then(() => {
@@ -291,7 +291,7 @@ describe('Pointer Permissions', () => {
});
it('should prevent creating pointer permission on bad field', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
config.database.loadSchema().then((schema) => {
return schema.addClassIfNotExists('AnObject', {owner: {type: 'String'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
}).then(() => {
@@ -304,7 +304,7 @@ describe('Pointer Permissions', () => {
});
it('should prevent creating pointer permission on bad field', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('owner', 'value');
object.save().then(() => {
@@ -329,7 +329,7 @@ describe('Pointer Permissions', () => {
The owner is another user than the ACL
*/
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -374,7 +374,7 @@ describe('Pointer Permissions', () => {
PointerPerm: "owner"
ACL: logged in user has access
*/
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -419,7 +419,7 @@ describe('Pointer Permissions', () => {
PointerPerm: "owner"
ACL: logged in user has access
*/
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -466,7 +466,7 @@ describe('Pointer Permissions', () => {
The owner is another user than the ACL
*/
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -511,7 +511,7 @@ describe('Pointer Permissions', () => {
PointerPerm: "owner" : read
ACL: logged in user has access
*/
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -558,7 +558,7 @@ describe('Pointer Permissions', () => {
PointerPerm: "owner" : read // proper owner
ACL: logged in user has not access
*/
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = new Parse.User();
const user2 = new Parse.User();
user.set({
@@ -597,7 +597,7 @@ describe('Pointer Permissions', () => {
});
it('should let master key find objects', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
@@ -626,7 +626,7 @@ describe('Pointer Permissions', () => {
});
it('should let master key get objects', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
@@ -657,7 +657,7 @@ describe('Pointer Permissions', () => {
it('should let master key update objects', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
@@ -684,7 +684,7 @@ describe('Pointer Permissions', () => {
});
it('should let master key delete objects', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const object = new Parse.Object('AnObject');
object.set('hello', 'world');
return object.save().then(() => {
@@ -710,7 +710,7 @@ describe('Pointer Permissions', () => {
});
it('should fail with invalid pointer perms', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: 'owner'});
@@ -721,7 +721,7 @@ describe('Pointer Permissions', () => {
});
it('should fail with invalid pointer perms', (done) => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
config.database.loadSchema().then((schema) => {
// Lock the update, and let only owner write
return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner', 'invalid']});

View File

@@ -205,7 +205,7 @@ describe('PushController', () => {
installation.set("deviceType", "android");
installations.push(installation);
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -276,7 +276,7 @@ describe('PushController', () => {
installations.push(installation);
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -350,7 +350,7 @@ describe('PushController', () => {
}
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -425,7 +425,7 @@ describe('PushController', () => {
}
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -529,7 +529,7 @@ describe('PushController', () => {
}
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -579,7 +579,7 @@ describe('PushController', () => {
alert: "Hello World!",
badge: 1,
}}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -616,7 +616,7 @@ describe('PushController', () => {
return ["ios"];
}
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -677,7 +677,7 @@ describe('PushController', () => {
}
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -762,7 +762,7 @@ describe('PushController', () => {
});
it('should not schedule push when not configured', (done) => {
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -862,7 +862,7 @@ describe('PushController', () => {
push: { adapter: pushAdapter },
scheduledPush: true
}).then(() => {
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
return Parse.Object.saveAll(installations).then(() => {
return pushController.sendPush(payload, {}, config, auth);
}).then(() => new Promise(resolve => setTimeout(resolve, 300)));
@@ -931,7 +931,7 @@ describe('PushController', () => {
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
return Parse.Object.saveAll(installations).then(() => {
return pushController.sendPush(payload, {}, config, auth);
}).then(() => new Promise(resolve => setTimeout(resolve, 100)));
@@ -996,7 +996,7 @@ describe('PushController', () => {
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
return Parse.Object.saveAll(installations).then(() => {
return pushController.sendPush(payload, {}, config, auth)
.then(() => { done.fail('should not success') })
@@ -1034,7 +1034,7 @@ describe('PushController', () => {
}
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -1094,7 +1094,7 @@ describe('PushController', () => {
}
}
var config = new Config(Parse.applicationId);
var config = Config.get(Parse.applicationId);
var auth = {
isMaster: true
}
@@ -1230,7 +1230,7 @@ describe('PushController', () => {
scheduledPush: true
})
.then(() => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
return new Promise((resolve, reject) => {
const pushController = new PushController();
pushController.sendPush({

View File

@@ -21,7 +21,7 @@ describe('PushQueue', () => {
}
})
.then(() => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
expect(config.pushWorker.channel).toEqual('my-specific-channel', 'pushWorker.channel');
expect(config.pushControllerQueue.channel).toEqual('my-specific-channel', 'pushWorker.channel');
})
@@ -47,7 +47,7 @@ describe('PushQueue', () => {
}
})
.then(() => {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
expect(PushQueue.defaultPushChannel()).toEqual('test-parse-server-push');
expect(config.pushWorker.channel).toEqual('test-parse-server-push');
expect(config.pushControllerQueue.channel).toEqual('test-parse-server-push');

View File

@@ -16,7 +16,7 @@ describe('PushWorker', () => {
}
}
}).then(() => {
expect(new Config('test').pushWorker).toBeUndefined();
expect(Config.get('test').pushWorker).toBeUndefined();
new PushWorker({
send: (body, installations) => {
expect(installations.length <= batchSize).toBe(true);
@@ -161,7 +161,7 @@ describe('PushWorker', () => {
describe('pushStatus', () => {
it('should remove invalid installations', (done) => {
const config = new Config('test');
const config = Config.get('test');
const handler = pushStatusHandler(config);
const spy = spyOn(config.database, "update").and.callFake(() => {
return Promise.resolve();
@@ -244,7 +244,7 @@ describe('PushWorker', () => {
});
it('tracks push status per UTC offsets', (done) => {
const config = new Config('test');
const config = Config.get('test');
const handler = pushStatusHandler(config);
const spy = spyOn(rest, "update").and.callThrough();
const UTCOffset = 1;
@@ -320,7 +320,7 @@ describe('PushWorker', () => {
});
it('tracks push status per UTC offsets with negative offsets', (done) => {
const config = new Config('test');
const config = Config.get('test');
const handler = pushStatusHandler(config);
const spy = spyOn(rest, "update").and.callThrough();
const UTCOffset = -6;

View File

@@ -7,7 +7,7 @@ const Config = require("../src/Config");
describe_only_db('mongo')('Read preference option', () => {
it('should find in primary by default', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -39,7 +39,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference in the beforeFind trigger', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -75,7 +75,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference in the beforeFind trigger even changing query', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -112,7 +112,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference in the beforeFind trigger even returning query', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -152,7 +152,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference in the beforeFind trigger even returning promise', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -192,7 +192,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference to PRIMARY_PREFERRED', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -228,7 +228,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference to SECONDARY_PREFERRED', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -264,7 +264,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference to NEAREST', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -300,7 +300,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference for GET', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -334,7 +334,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference for GET using API', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -374,7 +374,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change read preference for count', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject');
obj0.set('boolKey', false);
@@ -407,7 +407,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should find includes in primary by default', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
@@ -462,7 +462,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change includes read preference', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
@@ -519,7 +519,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should find subqueries in primary by default', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
@@ -575,7 +575,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change subqueries read preference when using matchesQuery', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
@@ -632,7 +632,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change subqueries read preference when using doesNotMatchQuery', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);
@@ -689,7 +689,7 @@ describe_only_db('mongo')('Read preference option', () => {
});
it('should change subqueries read preference when using matchesKeyInQuery and doesNotMatchKeyInQuery', (done) => {
const databaseAdapter = (new Config(Parse.applicationId)).database.adapter;
const databaseAdapter = (Config.get(Parse.applicationId)).database.adapter;
const obj0 = new Parse.Object('MyObject0');
obj0.set('boolKey', false);

View File

@@ -14,7 +14,7 @@ var nobody = auth.nobody(config);
describe('rest query', () => {
beforeEach(() => {
config = new Config('test');
config = Config.get('test');
database = config.database;
});

View File

@@ -4,7 +4,7 @@ const rp = require('request-promise');
const Parse = require('parse/node');
function createUser() {
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
const user = {
objectId: '1234567890',
username: 'hello',
@@ -29,7 +29,7 @@ describe_only_db('mongo')('revocable sessions', () => {
});
user._upgradeToRevocableSession().then((res) => {
expect(res.getSessionToken().indexOf('r:')).toBe(0);
const config = new Config(Parse.applicationId);
const config = Config.get(Parse.applicationId);
// use direct access to the DB to make sure we're not
// getting the session token stripped
return config.database.loadSchema().then(schemaController => {

View File

@@ -21,7 +21,7 @@ var hasAllPODobject = () => {
describe('SchemaController', () => {
beforeEach(() => {
config = new Config('test');
config = Config.get('test');
});
it('can validate one object', (done) => {
@@ -1029,7 +1029,7 @@ describe('SchemaController', () => {
describe('Class Level Permissions for requiredAuth', () => {
beforeEach(() => {
config = new Config('test');
config = Config.get('test');
});
function createUser() {

View File

@@ -9,7 +9,7 @@ describe('Uniqueness', function() {
obj.set('unique', 'value');
obj.save().then(() => {
expect(obj.id).not.toBeUndefined();
const config = new Config('test');
const config = Config.get('test');
return config.database.adapter.ensureUniqueness('UniqueField', { fields: { unique: { __type: 'String' } } }, ['unique'])
})
.then(() => {
@@ -30,7 +30,7 @@ describe('Uniqueness', function() {
obj.save({ string: 'who cares' })
.then(() => obj.save({ ptr: obj }))
.then(() => {
const config = new Config('test');
const config = Config.get('test');
return config.database.adapter.ensureUniqueness('UniquePointer', { fields: {
string: { __type: 'String' },
ptr: { __type: 'Pointer', targetClass: 'UniquePointer' }
@@ -58,7 +58,7 @@ describe('Uniqueness', function() {
o2.set('key', 'val');
Parse.Object.saveAll([o1, o2])
.then(() => {
const config = new Config('test');
const config = Config.get('test');
return config.database.adapter.ensureUniqueness('UniqueFail', { fields: { key: { __type: 'String' } } }, ['key']);
})
.catch(error => {
@@ -68,7 +68,7 @@ describe('Uniqueness', function() {
});
it_exclude_dbs(['postgres'])('can do compound uniqueness', done => {
const config = new Config('test');
const config = Config.get('test');
config.database.adapter.ensureUniqueness('CompoundUnique', { fields: { k1: { __type: 'String' }, k2: { __type: 'String' } } }, ['k1', 'k2'])
.then(() => {
const o1 = new Parse.Object('CompoundUnique');

View File

@@ -18,7 +18,7 @@ describe("Custom Pages, Email Verification, Password Reset", () => {
publicServerURL: "https://my.public.server.com/1"
})
.then(() => {
var config = new Config("test");
var config = Config.get("test");
expect(config.invalidLinkURL).toEqual("myInvalidLink");
expect(config.verifyEmailSuccessURL).toEqual("myVerifyEmailSuccess");
expect(config.choosePasswordURL).toEqual("myChoosePassword");
@@ -849,7 +849,7 @@ describe("Custom Pages, Email Verification, Password Reset", () => {
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/password_reset_success.html?username=zxcv');
Parse.User.logIn("zxcv", "hello").then(function(){
const config = new Config('test');
const config = Config.get('test');
config.database.adapter.find('_User', { fields: {} }, { 'username': 'zxcv' }, { limit: 1 })
.then(results => {
// _perishable_token should be unset after reset password

View File

@@ -24,7 +24,6 @@ if (global._babelPolyfill) {
}
var cache = require('../src/cache').default;
var express = require('express');
var ParseServer = require('../src/index').ParseServer;
var path = require('path');
var TestUtils = require('../src/TestUtils');
@@ -116,8 +115,6 @@ if (process.env.PARSE_SERVER_TEST_CACHE === 'redis') {
const openConnections = {};
// Set up a default API server for testing with default configuration.
var app;
var api;
var server;
// Allows testing specific configurations of Parse Server
@@ -131,17 +128,18 @@ const reconfigureServer = changedConfiguration => {
}
try {
const newConfiguration = Object.assign({}, defaultConfiguration, changedConfiguration, {
__indexBuildCompletionCallbackForTests: indexBuildPromise => indexBuildPromise.then(resolve, reject)
__indexBuildCompletionCallbackForTests: indexBuildPromise => indexBuildPromise.then(resolve, reject),
mountPath: '/1',
port,
});
cache.clear();
app = express();
api = new ParseServer(newConfiguration);
api.use(require('./testing-routes').router);
app.use('/1', api);
app.use('/1', () => {
const parseServer = ParseServer.start(newConfiguration);
parseServer.app.use(require('./testing-routes').router);
parseServer.expressApp.use('/1', (err) => {
console.error(err);
fail('should not call next');
});
server = app.listen(port);
server = parseServer.server;
server.on('connection', connection => {
const key = `${connection.remoteAddress}:${connection.remotePort}`;
openConnections[key] = connection;

View File

@@ -166,7 +166,7 @@ describe('server', () => {
it('can properly sets the push support', done => {
// default config passes push options
const config = new Config('test');
const config = Config.get('test');
expect(config.hasPushSupport).toEqual(true);
expect(config.hasPushScheduledSupport).toEqual(false);
request.get({
@@ -187,7 +187,7 @@ describe('server', () => {
reconfigureServer({
push: undefined // force no config
}).then(() => {
const config = new Config('test');
const config = Config.get('test');
expect(config.hasPushSupport).toEqual(false);
expect(config.hasPushScheduledSupport).toEqual(false);
request.get({
@@ -214,7 +214,7 @@ describe('server', () => {
}
}
}).then(() => {
const config = new Config('test');
const config = Config.get('test');
expect(config.hasPushSupport).toEqual(true);
expect(config.hasPushScheduledSupport).toEqual(false);
request.get({
@@ -242,7 +242,7 @@ describe('server', () => {
},
scheduledPush: true,
}).then(() => {
const config = new Config('test');
const config = Config.get('test');
expect(config.hasPushSupport).toEqual(true);
expect(config.hasPushScheduledSupport).toEqual(true);
request.get({
@@ -360,7 +360,7 @@ describe('server', () => {
it('properly gives publicServerURL when set', done => {
reconfigureServer({ publicServerURL: 'https://myserver.com/1' })
.then(() => {
var config = new Config('test', 'http://localhost:8378/1');
var config = Config.get('test', 'http://localhost:8378/1');
expect(config.mount).toEqual('https://myserver.com/1');
done();
});
@@ -369,7 +369,7 @@ describe('server', () => {
it('properly removes trailing slash in mount', done => {
reconfigureServer({})
.then(() => {
var config = new Config('test', 'http://localhost:8378/1/');
var config = Config.get('test', 'http://localhost:8378/1/');
expect(config.mount).toEqual('http://localhost:8378/1');
done();
});
@@ -385,6 +385,7 @@ describe('server', () => {
it('fails if the session length is not a number', done => {
reconfigureServer({ sessionLength: 'test' })
.then(done.fail)
.catch(error => {
expect(error).toEqual('Session length must be a valid number.');
done();
@@ -393,6 +394,7 @@ describe('server', () => {
it('fails if the session length is less than or equal to 0', done => {
reconfigureServer({ sessionLength: '-33' })
.then(done.fail)
.catch(error => {
expect(error).toEqual('Session length must be a value greater than 0.');
return reconfigureServer({ sessionLength: '0' })
@@ -441,4 +443,33 @@ describe('server', () => {
.then(done)
});
it('should load a middleware', (done) => {
const obj = {
middleware: function(req, res, next) {
next();
}
}
const spy = spyOn(obj, 'middleware').and.callThrough();
reconfigureServer({
middleware: obj.middleware
}).then(() => {
const query = new Parse.Query('AnObject');
return query.find();
}).then(() => {
expect(spy).toHaveBeenCalled();
done();
}).catch(done.fail);
});
it('should load a middleware from string', (done) => {
reconfigureServer({
middleware: 'spec/support/CustomMiddleware'
}).then(() => {
return request.get('http://localhost:8378/1', (err, res) => {
// Just check that the middleware set the header
expect(res.headers['x-yolo']).toBe('1');
done();
});
}).catch(done.fail);
});
});

View File

@@ -6,7 +6,7 @@ import {
arrayParser,
moduleOrObjectParser,
nullParser,
} from '../src/cli/utils/parsers';
} from '../src/Options/parsers';
describe('parsers', () => {
it('parses correctly with numberParser', () => {

View File

@@ -13,7 +13,7 @@ let database;
describe('rest create', () => {
beforeEach(() => {
config = new Config('test');
config = Config.get('test');
database = config.database;
});

View File

@@ -132,7 +132,7 @@ var masterKeyHeaders = {
describe('schemas', () => {
beforeEach(() => {
config = new Config('test');
config = Config.get('test');
});
afterEach(() => {

View File

@@ -0,0 +1,4 @@
module.exports = function(req, res, next) {
res.set('X-Yolo', '1');
next();
}

View File

@@ -18,65 +18,34 @@ function removeTrailingSlash(str) {
}
export class Config {
constructor(applicationId: string, mount: string) {
static get(applicationId: string, mount: string) {
const cacheInfo = AppCache.get(applicationId);
if (!cacheInfo) {
return;
}
const config = new Config();
config.applicationId = applicationId;
Object.keys(cacheInfo).forEach((key) => {
if (key == 'databaseController') {
const schemaCache = new SchemaCache(cacheInfo.cacheController,
cacheInfo.schemaCacheTTL,
cacheInfo.enableSingleSchemaCache);
config.database = new DatabaseController(cacheInfo.databaseController.adapter, schemaCache);
} else {
config[key] = cacheInfo[key];
}
});
config.mount = removeTrailingSlash(mount);
config.generateSessionExpiresAt = config.generateSessionExpiresAt.bind(config);
config.generateEmailVerifyTokenExpiresAt = config.generateEmailVerifyTokenExpiresAt.bind(config);
return config;
}
this.applicationId = applicationId;
this.jsonLogs = cacheInfo.jsonLogs;
this.masterKey = cacheInfo.masterKey;
this.masterKeyIps = cacheInfo.masterKeyIps;
this.clientKey = cacheInfo.clientKey;
this.javascriptKey = cacheInfo.javascriptKey;
this.dotNetKey = cacheInfo.dotNetKey;
this.restAPIKey = cacheInfo.restAPIKey;
this.webhookKey = cacheInfo.webhookKey;
this.fileKey = cacheInfo.fileKey;
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
this.userSensitiveFields = cacheInfo.userSensitiveFields;
// Create a new DatabaseController per request
if (cacheInfo.databaseController) {
const schemaCache = new SchemaCache(cacheInfo.cacheController, cacheInfo.schemaCacheTTL, cacheInfo.enableSingleSchemaCache);
this.database = new DatabaseController(cacheInfo.databaseController.adapter, schemaCache);
}
this.schemaCacheTTL = cacheInfo.schemaCacheTTL;
this.enableSingleSchemaCache = cacheInfo.enableSingleSchemaCache;
this.serverURL = cacheInfo.serverURL;
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);
this.verifyUserEmails = cacheInfo.verifyUserEmails;
this.preventLoginWithUnverifiedEmail = cacheInfo.preventLoginWithUnverifiedEmail;
this.emailVerifyTokenValidityDuration = cacheInfo.emailVerifyTokenValidityDuration;
this.accountLockout = cacheInfo.accountLockout;
this.passwordPolicy = cacheInfo.passwordPolicy;
this.appName = cacheInfo.appName;
this.analyticsController = cacheInfo.analyticsController;
this.cacheController = cacheInfo.cacheController;
this.hooksController = cacheInfo.hooksController;
this.filesController = cacheInfo.filesController;
this.pushController = cacheInfo.pushController;
this.pushControllerQueue = cacheInfo.pushControllerQueue;
this.pushWorker = cacheInfo.pushWorker;
this.hasPushSupport = cacheInfo.hasPushSupport;
this.hasPushScheduledSupport = cacheInfo.hasPushScheduledSupport;
this.loggerController = cacheInfo.loggerController;
this.userController = cacheInfo.userController;
this.authDataManager = cacheInfo.authDataManager;
this.customPages = cacheInfo.customPages || {};
this.mount = removeTrailingSlash(mount);
this.liveQueryController = cacheInfo.liveQueryController;
this.sessionLength = cacheInfo.sessionLength;
this.maxLimit = cacheInfo.maxLimit;
this.expireInactiveSessions = cacheInfo.expireInactiveSessions;
this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this);
this.generateEmailVerifyTokenExpiresAt = this.generateEmailVerifyTokenExpiresAt.bind(this);
this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset;
this.objectIdSize = cacheInfo.objectIdSize;
static put(serverConfiguration) {
Config.validate(serverConfiguration);
AppCache.put(serverConfiguration.appId, serverConfiguration);
Config.setupPasswordValidator(serverConfiguration.passwordPolicy);
return serverConfiguration;
}
static validate({

View File

@@ -30,7 +30,7 @@ export class AdaptableController {
}
get config() {
return new Config(this.appId);
return Config.get(this.appId);
}
expectedAdapterType() {

View File

@@ -1,10 +1,10 @@
import { ParseCloudCodePublisher } from '../LiveQuery/ParseCloudCodePublisher';
import { LiveQueryOptions } from '../Options';
export class LiveQueryController {
classNames: any;
liveQueryPublisher: any;
constructor(config: any) {
constructor(config: ?LiveQueryOptions) {
// If config is empty, we just assume no classs needs to be registered as LiveQuery
if (!config || !config.classNames) {
this.classNames = new Set();

231
src/Controllers/index.js Normal file
View File

@@ -0,0 +1,231 @@
import authDataManager from '../Adapters/Auth';
import { ParseServerOptions } from '../Options';
import { loadAdapter } from '../Adapters/AdapterLoader';
import defaults from '../defaults';
import url from 'url';
// Controllers
import { LoggerController } from './LoggerController';
import { FilesController } from './FilesController';
import { HooksController } from './HooksController';
import { UserController } from './UserController';
import { CacheController } from './CacheController';
import { LiveQueryController } from './LiveQueryController';
import { AnalyticsController } from './AnalyticsController';
import { PushController } from './PushController';
import { PushQueue } from '../Push/PushQueue';
import { PushWorker } from '../Push/PushWorker';
import DatabaseController from './DatabaseController';
import SchemaCache from './SchemaCache';
// Adapters
import { GridStoreAdapter } from '../Adapters/Files/GridStoreAdapter';
import { WinstonLoggerAdapter } from '../Adapters/Logger/WinstonLoggerAdapter';
import { InMemoryCacheAdapter } from '../Adapters/Cache/InMemoryCacheAdapter';
import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter';
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter';
import ParsePushAdapter from 'parse-server-push-adapter';
export function getControllers(options: ParseServerOptions) {
const loggerController = getLoggerController(options);
const filesController = getFilesController(options);
const userController = getUserController(options);
const {
pushController,
hasPushScheduledSupport,
hasPushSupport,
pushControllerQueue,
pushWorker
} = getPushController(options);
const cacheController = getCacheController(options);
const analyticsController = getAnalyticsController(options);
const liveQueryController = getLiveQueryController(options);
const databaseController = getDatabaseController(options, cacheController);
const hooksController = getHooksController(options, databaseController);
const authDataManager = getAuthDataManager(options);
return {
loggerController,
filesController,
userController,
pushController,
hasPushScheduledSupport,
hasPushSupport,
pushWorker,
pushControllerQueue,
analyticsController,
cacheController,
liveQueryController,
databaseController,
hooksController,
authDataManager,
};
}
export function getLoggerController(options: ParseServerOptions): LoggerController {
const {
appId,
jsonLogs,
logsFolder,
verbose,
logLevel,
silent,
loggerAdapter,
} = options;
const loggerOptions = { jsonLogs, logsFolder, verbose, logLevel, silent };
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, loggerOptions);
return new LoggerController(loggerControllerAdapter, appId, loggerOptions);
}
export function getFilesController(options: ParseServerOptions): FilesController {
const {
appId,
databaseURI,
filesAdapter,
databaseAdapter,
} = options;
if (!filesAdapter && databaseAdapter) {
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
}
const filesControllerAdapter = loadAdapter(filesAdapter, () => {
return new GridStoreAdapter(databaseURI);
});
return new FilesController(filesControllerAdapter, appId);
}
export function getUserController(options: ParseServerOptions): UserController {
const {
appId,
emailAdapter,
verifyUserEmails,
} = options;
const emailControllerAdapter = loadAdapter(emailAdapter);
return new UserController(emailControllerAdapter, appId, { verifyUserEmails });
}
export function getCacheController(options: ParseServerOptions): CacheController {
const {
appId,
cacheAdapter,
cacheTTL,
cacheMaxSize,
} = options;
const cacheControllerAdapter = loadAdapter(cacheAdapter, InMemoryCacheAdapter, {appId: appId, ttl: cacheTTL, maxSize: cacheMaxSize });
return new CacheController(cacheControllerAdapter, appId);
}
export function getAnalyticsController(options: ParseServerOptions): AnalyticsController {
const {
analyticsAdapter,
} = options;
const analyticsControllerAdapter = loadAdapter(analyticsAdapter, AnalyticsAdapter);
return new AnalyticsController(analyticsControllerAdapter);
}
export function getLiveQueryController(options: ParseServerOptions): LiveQueryController {
return new LiveQueryController(options.liveQuery);
}
export function getDatabaseController(options: ParseServerOptions, cacheController: CacheController): DatabaseController {
const {
databaseURI,
databaseOptions,
collectionPrefix,
schemaCacheTTL,
enableSingleSchemaCache,
} = options;
let {
databaseAdapter
} = options;
if ((databaseOptions || (databaseURI && databaseURI !== defaults.databaseURI) || collectionPrefix !== defaults.collectionPrefix) && databaseAdapter) {
throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/collectionPrefix.';
} else if (!databaseAdapter) {
databaseAdapter = getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions)
} else {
databaseAdapter = loadAdapter(databaseAdapter)
}
return new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache));
}
export function getHooksController(options: ParseServerOptions, databaseController: DatabaseController): HooksController {
const {
appId,
webhookKey
} = options;
return new HooksController(appId, databaseController, webhookKey);
}
interface PushControlling {
pushController: PushController,
hasPushScheduledSupport: boolean,
pushControllerQueue: PushQueue,
pushWorker: PushWorker
}
export function getPushController(options: ParseServerOptions): PushControlling {
const {
scheduledPush,
push,
} = options;
const pushOptions = Object.assign({}, push);
const pushQueueOptions = pushOptions.queueOptions || {};
if (pushOptions.queueOptions) {
delete pushOptions.queueOptions;
}
// Pass the push options too as it works with the default
const pushAdapter = loadAdapter(pushOptions && pushOptions.adapter, ParsePushAdapter, pushOptions);
// We pass the options and the base class for the adatper,
// Note that passing an instance would work too
const pushController = new PushController();
const hasPushSupport = !!(pushAdapter && push);
const hasPushScheduledSupport = hasPushSupport && (scheduledPush === true);
const {
disablePushWorker
} = pushQueueOptions;
const pushControllerQueue = new PushQueue(pushQueueOptions);
let pushWorker;
if (!disablePushWorker) {
pushWorker = new PushWorker(pushAdapter, pushQueueOptions);
}
return {
pushController,
hasPushSupport,
hasPushScheduledSupport,
pushControllerQueue,
pushWorker
}
}
export function getAuthDataManager(options: ParseServerOptions) {
const {
auth,
enableAnonymousUsers
} = options;
return authDataManager(auth, enableAnonymousUsers)
}
export function getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions) {
let protocol;
try {
const parsedURI = url.parse(databaseURI);
protocol = parsedURI.protocol ? parsedURI.protocol.toLowerCase() : null;
} catch(e) { /* */ }
switch (protocol) {
case 'postgres:':
return new PostgresStorageAdapter({
uri: databaseURI,
collectionPrefix,
databaseOptions
});
default:
return new MongoStorageAdapter({
uri: databaseURI,
collectionPrefix,
mongoOptions: databaseOptions,
});
}
}

View File

@@ -22,6 +22,7 @@ class ParseLiveQueryServer {
subscriber: Object;
constructor(server: any, config: any) {
this.server = server;
this.clients = new Map();
this.subscriptions = new Map();

385
src/Options/Definitions.js Normal file
View File

@@ -0,0 +1,385 @@
/*
**** GENERATED CODE ****
This code has been generated by resources/buildConfigDefinitions.js
Do not edit manually, but update Options/index.js
*/"use strict";
var parsers = require("./parsers");
module.exports.ParseServerOptions = {
"appId": {
"env": "PARSE_SERVER_APPLICATION_ID",
"help": "Your Parse Application ID",
"required": true
},
"masterKey": {
"env": "PARSE_SERVER_MASTER_KEY",
"help": "Your Parse Master Key",
"required": true
},
"serverURL": {
"env": "PARSE_SERVER_URL",
"help": "URL to your parse server with http:// or https://.",
"required": true
},
"masterKeyIps": {
"env": "PARSE_SERVER_MASTER_KEY_IPS",
"help": "Restrict masterKey to be used by only these ips. defaults to [] (allow all ips)",
"action": parsers.arrayParser,
"default": []
},
"appName": {
"env": "PARSE_SERVER_APP_NAME",
"help": "Sets the app name"
},
"analyticsAdapter": {
"env": "PARSE_SERVER_ANALYTICS_ADAPTER",
"help": "Adapter module for the analytics",
"action": parsers.moduleOrObjectParser
},
"filesAdapter": {
"env": "PARSE_SERVER_FILES_ADAPTER",
"help": "Adapter module for the files sub-system",
"action": parsers.moduleOrObjectParser
},
"push": {
"env": "PARSE_SERVER_PUSH",
"help": "Configuration for push, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#push-notifications",
"action": parsers.objectParser
},
"scheduledPush": {
"env": "PARSE_SERVER_SCHEDULED_PUSH",
"help": "Configuration for push scheduling. Defaults to false.",
"action": parsers.booleanParser,
"default": false
},
"loggerAdapter": {
"env": "PARSE_SERVER_LOGGER_ADAPTER",
"help": "Adapter module for the logging sub-system",
"action": parsers.moduleOrObjectParser
},
"jsonLogs": {
"env": "JSON_LOGS",
"help": "Log as structured JSON objects",
"action": parsers.booleanParser
},
"logsFolder": {
"env": "PARSE_SERVER_LOGS_FOLDER",
"help": "Folder for the logs (defaults to './logs'); set to null to disable file based logging",
"default": "./logs"
},
"verbose": {
"env": "VERBOSE",
"help": "Set the logging to verbose",
"action": parsers.booleanParser
},
"logLevel": {
"env": "PARSE_SERVER_LOG_LEVEL",
"help": "Sets the level for logs"
},
"silent": {
"env": "SILENT",
"help": "Disables console output",
"action": parsers.booleanParser
},
"databaseURI": {
"env": "PARSE_SERVER_DATABASE_URI",
"help": "The full URI to your mongodb database",
"required": true,
"default": "mongodb://localhost:27017/parse"
},
"databaseOptions": {
"env": "PARSE_SERVER_DATABASE_OPTIONS",
"help": "Options to pass to the mongodb client",
"action": parsers.objectParser
},
"databaseAdapter": {
"env": "PARSE_SERVER_DATABASE_ADAPTER",
"help": "Adapter module for the database",
"action": parsers.moduleOrObjectParser
},
"cloud": {
"env": "PARSE_SERVER_CLOUD",
"help": "Full path to your cloud code main.js",
"action": parsers.objectParser
},
"collectionPrefix": {
"env": "PARSE_SERVER_COLLECTION_PREFIX",
"help": "A collection prefix for the classes",
"default": ""
},
"clientKey": {
"env": "PARSE_SERVER_CLIENT_KEY",
"help": "Key for iOS, MacOS, tvOS clients"
},
"javascriptKey": {
"env": "PARSE_SERVER_JAVASCRIPT_KEY",
"help": "Key for the Javascript SDK"
},
"dotNetKey": {
"env": "PARSE_SERVER_DOT_NET_KEY",
"help": "Key for Unity and .Net SDK"
},
"restAPIKey": {
"env": "PARSE_SERVER_REST_APIKEY",
"help": "Key for REST calls"
},
"webhookKey": {
"env": "PARSE_SERVER_WEBHOOK_KEY",
"help": "Key sent with outgoing webhook calls"
},
"fileKey": {
"env": "PARSE_SERVER_FILE_KEY",
"help": "Key for your files"
},
"userSensitiveFields": {
"env": "PARSE_SERVER_USER_SENSITIVE_FIELDS",
"help": "Personally identifiable information fields in the user table the should be removed for non-authorized users.",
"action": parsers.arrayParser,
"default": ["email"]
},
"enableAnonymousUsers": {
"env": "PARSE_SERVER_ENABLE_ANON_USERS",
"help": "Enable (or disable) anon users, defaults to true",
"action": parsers.booleanParser,
"default": true
},
"allowClientClassCreation": {
"env": "PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION",
"help": "Enable (or disable) client class creation, defaults to true",
"action": parsers.booleanParser,
"default": true
},
"auth": {
"env": "PARSE_SERVER_AUTH_PROVIDERS",
"help": "Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication",
"action": parsers.objectParser
},
"maxUploadSize": {
"env": "PARSE_SERVER_MAX_UPLOAD_SIZE",
"help": "Max file size for uploads. defaults to 20mb",
"default": "20mb"
},
"verifyUserEmails": {
"env": "PARSE_SERVER_VERIFY_USER_EMAILS",
"help": "Enable (or disable) user email validation, defaults to false",
"action": parsers.booleanParser,
"default": false
},
"preventLoginWithUnverifiedEmail": {
"env": "PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL",
"help": "Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false",
"action": parsers.booleanParser,
"default": false
},
"emailVerifyTokenValidityDuration": {
"env": "PARSE_SERVER_EMAIL_VERIFY_TOKEN_VALIDITY_DURATION",
"help": "Email verification token validity duration",
"action": parsers.numberParser("emailVerifyTokenValidityDuration")
},
"accountLockout": {
"env": "PARSE_SERVER_ACCOUNT_LOCKOUT",
"help": "account lockout policy for failed login attempts",
"action": parsers.objectParser
},
"passwordPolicy": {
"env": "PARSE_SERVER_PASSWORD_POLICY",
"help": "Password policy for enforcing password related rules",
"action": parsers.objectParser
},
"cacheAdapter": {
"env": "PARSE_SERVER_CACHE_ADAPTER",
"help": "Adapter module for the cache",
"action": parsers.moduleOrObjectParser
},
"emailAdapter": {
"env": "PARSE_SERVER_EMAIL_ADAPTER",
"help": "Adapter module for the email sending",
"action": parsers.moduleOrObjectParser
},
"publicServerURL": {
"env": "PARSE_PUBLIC_SERVER_URL",
"help": "Public URL to your parse server with http:// or https://."
},
"customPages": {
"env": "PARSE_SERVER_CUSTOM_PAGES",
"help": "custom pages for password validation and reset",
"action": parsers.objectParser,
"default": {}
},
"liveQuery": {
"env": "PARSE_SERVER_LIVE_QUERY",
"help": "parse-server's LiveQuery configuration object",
"action": parsers.objectParser
},
"sessionLength": {
"env": "PARSE_SERVER_SESSION_LENGTH",
"help": "Session duration, in seconds, defaults to 1 year",
"action": parsers.numberParser("sessionLength"),
"default": 31536000
},
"maxLimit": {
"env": "PARSE_SERVER_MAX_LIMIT",
"help": "Max value for limit option on queries, defaults to unlimited",
"action": parsers.numberParser("maxLimit")
},
"expireInactiveSessions": {
"env": "PARSE_SERVER_EXPIRE_INACTIVE_SESSIONS",
"help": "Sets wether we should expire the inactive sessions, defaults to true",
"action": parsers.booleanParser,
"default": true
},
"revokeSessionOnPasswordReset": {
"env": "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET",
"help": "When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.",
"action": parsers.booleanParser,
"default": true
},
"schemaCacheTTL": {
"env": "PARSE_SERVER_SCHEMA_CACHE_TTL",
"help": "The TTL for caching the schema for optimizing read/write operations. You should put a long TTL when your DB is in production. default to 5000; set 0 to disable.",
"action": parsers.numberParser("schemaCacheTTL"),
"default": 5000
},
"cacheTTL": {
"env": "PARSE_SERVER_CACHE_TTL",
"help": "Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds)",
"action": parsers.numberParser("cacheTTL"),
"default": 5000
},
"cacheMaxSize": {
"env": "PARSE_SERVER_CACHE_MAX_SIZE",
"help": "Sets the maximum size for the in memory cache, defaults to 10000",
"action": parsers.numberParser("cacheMaxSize"),
"default": 10000
},
"enableSingleSchemaCache": {
"env": "PARSE_SERVER_ENABLE_SINGLE_SCHEMA_CACHE",
"help": "Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA. Defaults to false, i.e. unique schema cache per request.",
"action": parsers.booleanParser,
"default": false
},
"objectIdSize": {
"env": "PARSE_SERVER_OBJECT_ID_SIZE",
"help": "Sets the number of characters in generated object id's, default 10",
"action": parsers.numberParser("objectIdSize"),
"default": 10
},
"port": {
"env": "PARSE_SERVER_PORT",
"help": "The port to run the ParseServer. defaults to 1337.",
"action": parsers.numberParser("port"),
"default": 1337
},
"host": {
"env": "PARSE_SERVER_HOST",
"help": "The host to serve ParseServer on. defaults to 0.0.0.0",
"default": "0.0.0.0"
},
"mountPath": {
"env": "PARSE_SERVER_MOUNT_PATH",
"help": "Mount path for the server, defaults to /parse",
"default": "/parse"
},
"cluster": {
"env": "PARSE_SERVER_CLUSTER",
"help": "Run with cluster, optionally set the number of processes default to os.cpus().length",
"action": parsers.numberOrBooleanParser
},
"middleware": {
"env": "PARSE_SERVER_MIDDLEWARE",
"help": "middleware for express server, can be string or function"
},
"startLiveQueryServer": {
"env": "PARSE_SERVER_START_LIVE_QUERY_SERVER",
"help": "Starts the liveQuery server",
"action": parsers.booleanParser
},
"liveQueryServerOptions": {
"env": "PARSE_SERVER_LIVE_QUERY_SERVER_OPTIONS",
"help": "Live query server configuration options (will start the liveQuery server)",
"action": parsers.objectParser
}
};
module.exports.CustomPagesOptions = {
"invalidLink": {
"env": "PARSE_SERVER_CUSTOM_PAGES_INVALID_LINK",
"help": "invalid link page path"
},
"verifyEmailSuccess": {
"env": "PARSE_SERVER_CUSTOM_PAGES_VERIFY_EMAIL_SUCCESS",
"help": "verify email success page path"
},
"choosePassword": {
"env": "PARSE_SERVER_CUSTOM_PAGES_CHOOSE_PASSWORD",
"help": "choose password page path"
},
"passwordResetSuccess": {
"env": "PARSE_SERVER_CUSTOM_PAGES_PASSWORD_RESET_SUCCESS",
"help": "password reset success page path"
}
};
module.exports.LiveQueryOptions = {
"classNames": {
"env": "PARSE_SERVER_LIVEQUERY_CLASSNAMES",
"help": "parse-server's LiveQuery classNames",
"action": parsers.arrayParser
},
"redisURL": {
"env": "undefinedREDIS_URL",
"help": "parse-server's LiveQuery redisURL"
},
"pubSubAdapter": {
"env": "undefinedPUB_SUB_ADAPTER",
"help": "LiveQuery pubsub adapter",
"action": parsers.moduleOrObjectParser
}
};
module.exports.LiveQueryServerOptions = {
"appId": {
"env": "PARSE_SERVER_LIVE_QUERY_APP_ID",
"help": "This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId."
},
"masterKey": {
"env": "PARSE_SERVER_LIVE_QUERY_MASTER_KEY",
"help": "This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey."
},
"serverURL": {
"env": "PARSE_SERVER_LIVE_QUERY_SERVER_URL",
"help": "This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL."
},
"keyPairs": {
"env": "PARSE_SERVER_LIVE_QUERY_KEY_PAIRS",
"help": "A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details.",
"action": parsers.objectParser
},
"websocketTimeout": {
"env": "PARSE_SERVER_LIVE_QUERY_WEBSOCKET_TIMEOUT",
"help": "Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients. Defaults to 10 * 1000 ms (10 s).",
"action": parsers.numberParser("websocketTimeout")
},
"cacheTimeout": {
"env": "PARSE_SERVER_LIVE_QUERY_CACHE_TIMEOUT",
"help": "Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details. Defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).",
"action": parsers.numberParser("cacheTimeout")
},
"logLevel": {
"env": "PARSE_SERVER_LIVE_QUERY_LOG_LEVEL",
"help": "This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE. Defaults to INFO."
},
"port": {
"env": "PARSE_SERVER_LIVE_QUERY_PORT",
"help": "The port to run the ParseServer. defaults to 1337.",
"action": parsers.numberParser("port"),
"default": 1337
},
"redisURL": {
"env": "PARSE_SERVER_LIVE_QUERY_REDIS_URL",
"help": "parse-server's LiveQuery redisURL"
},
"pubSubAdapter": {
"env": "PARSE_SERVER_LIVE_QUERY_PUB_SUB_ADAPTER",
"help": "LiveQuery pubsub adapter",
"action": parsers.moduleOrObjectParser
}
};

176
src/Options/index.js Normal file
View File

@@ -0,0 +1,176 @@
// @flow
type Adapter = string|any;
type NumberOrBoolean = number|boolean;
export interface ParseServerOptions {
/* Your Parse Application ID
:ENV: PARSE_SERVER_APPLICATION_ID */
appId: string;
/* Your Parse Master Key */
masterKey: string;
/* URL to your parse server with http:// or https://.
:ENV: PARSE_SERVER_URL */
serverURL: string;
/* Restrict masterKey to be used by only these ips. defaults to [] (allow all ips) */
masterKeyIps: ?string[]; // = []
/* Sets the app name */
appName: ?string;
/* Adapter module for the analytics */
analyticsAdapter: ?Adapter;
/* Adapter module for the files sub-system */
filesAdapter: ?Adapter;
/* Configuration for push, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#push-notifications */
push: ?any;
/* Configuration for push scheduling. Defaults to false. */
scheduledPush: ?boolean; // = false
/* Adapter module for the logging sub-system */
loggerAdapter: ?Adapter;
/* Log as structured JSON objects
:ENV: JSON_LOGS */
jsonLogs: ?boolean;
/* Folder for the logs (defaults to './logs'); set to null to disable file based logging
:ENV: PARSE_SERVER_LOGS_FOLDER */
logsFolder: ?string; // = ./logs
/* Set the logging to verbose
:ENV: VERBOSE */
verbose: ?boolean;
/* Sets the level for logs */
logLevel: ?string;
/* Disables console output
:ENV: SILENT */
silent: ?boolean;
/* The full URI to your mongodb database */
databaseURI: string; // = mongodb://localhost:27017/parse
/* Options to pass to the mongodb client */
databaseOptions: ?any;
/* Adapter module for the database */
databaseAdapter: ?Adapter;
/* Full path to your cloud code main.js */
cloud: ?any;
/* A collection prefix for the classes */
collectionPrefix: ?string; // = ''
/* Key for iOS, MacOS, tvOS clients */
clientKey: ?string;
/* Key for the Javascript SDK */
javascriptKey: ?string;
/* Key for Unity and .Net SDK */
dotNetKey: ?string;
/* Key for REST calls */
restAPIKey: ?string;
/* Key sent with outgoing webhook calls */
webhookKey: ?string;
/* Key for your files */
fileKey: ?string;
/* Personally identifiable information fields in the user table the should be removed for non-authorized users. */
userSensitiveFields: ?string[]; // = ["email"]
/* Enable (or disable) anon users, defaults to true
:ENV: PARSE_SERVER_ENABLE_ANON_USERS */
enableAnonymousUsers: ?boolean; // = true
/* Enable (or disable) client class creation, defaults to true
:ENV: PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION */
allowClientClassCreation: ?boolean; // = true
/* Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication
:ENV: PARSE_SERVER_AUTH_PROVIDERS */
auth: ?any;
/* Max file size for uploads. defaults to 20mb */
maxUploadSize: ?string; // = 20mb
/* Enable (or disable) user email validation, defaults to false */
verifyUserEmails: ?boolean; // = false
/* Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false */
preventLoginWithUnverifiedEmail: ?boolean; // = false
/* Email verification token validity duration */
emailVerifyTokenValidityDuration: ?number;
/* account lockout policy for failed login attempts */
accountLockout: ?any;
/* Password policy for enforcing password related rules */
passwordPolicy: ?any;
/* Adapter module for the cache */
cacheAdapter: ?Adapter;
/* Adapter module for the email sending */
emailAdapter: ?Adapter;
/* Public URL to your parse server with http:// or https://.
:ENV: PARSE_PUBLIC_SERVER_URL */
publicServerURL: ?string;
/* custom pages for password validation and reset */
customPages: ?CustomPagesOptions; // = {}
/* parse-server's LiveQuery configuration object */
liveQuery: ?LiveQueryOptions;
/* Session duration, in seconds, defaults to 1 year */
sessionLength: ?number; // = 31536000
/* Max value for limit option on queries, defaults to unlimited */
maxLimit: ?number;
/* Sets wether we should expire the inactive sessions, defaults to true */
expireInactiveSessions: ?boolean; // = true
/* When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions. */
revokeSessionOnPasswordReset: ?boolean; // = true
/* The TTL for caching the schema for optimizing read/write operations. You should put a long TTL when your DB is in production. default to 5000; set 0 to disable. */
schemaCacheTTL: ?number; // = 5000
/* Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds) */
cacheTTL: ?number; // = 5000
/* Sets the maximum size for the in memory cache, defaults to 10000 */
cacheMaxSize : ?number; // = 10000
/* Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA. Defaults to false, i.e. unique schema cache per request. */
enableSingleSchemaCache: ?boolean; // = false
/* Sets the number of characters in generated object id's, default 10 */
objectIdSize: ?number; // = 10
/* The port to run the ParseServer. defaults to 1337. */
port: ?number; // = 1337
/* The host to serve ParseServer on. defaults to 0.0.0.0 */
host: ?string; // = 0.0.0.0
/* Mount path for the server, defaults to /parse */
mountPath: ?string; // = /parse
/* Run with cluster, optionally set the number of processes default to os.cpus().length */
cluster: ?NumberOrBoolean;
/* middleware for express server, can be string or function */
middleware: ?((()=>void)|string);
/* Starts the liveQuery server */
startLiveQueryServer: ?boolean;
/* Live query server configuration options (will start the liveQuery server) */
liveQueryServerOptions: ?LiveQueryServerOptions;
__indexBuildCompletionCallbackForTests: ?()=>void;
}
export interface CustomPagesOptions {
/* invalid link page path */
invalidLink: ?string;
/* verify email success page path */
verifyEmailSuccess: ?string;
/* choose password page path */
choosePassword: ?string;
/* password reset success page path */
passwordResetSuccess: ?string;
}
export interface LiveQueryOptions {
/* parse-server's LiveQuery classNames
:ENV: PARSE_SERVER_LIVEQUERY_CLASSNAMES */
classNames: ?string[],
/* parse-server's LiveQuery redisURL */
redisURL: ?string,
/* LiveQuery pubsub adapter */
pubSubAdapter: ?Adapter,
}
export interface LiveQueryServerOptions {
/* This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId.*/
appId: ?string,
/* This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey.*/
masterKey: ?string,
/* This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL.*/
serverURL: ?string,
/* A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details.*/
keyPairs: ?any,
/* Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients. Defaults to 10 * 1000 ms (10 s).*/
websocketTimeout: ?number,
/* Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details. Defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).*/
cacheTimeout: ?number,
/* This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE. Defaults to INFO.*/
logLevel: ?string,
/* The port to run the ParseServer. defaults to 1337.*/
port: ?number, // = 1337
/* parse-server's LiveQuery redisURL */
redisURL: ?string,
/* LiveQuery pubsub adapter */
pubSubAdapter: ?Adapter,
}

View File

@@ -1,4 +1,4 @@
export function numberParser(key) {
function numberParser(key) {
return function(opt) {
const intOpt = parseInt(opt);
if (!Number.isInteger(intOpt)) {
@@ -8,7 +8,7 @@ export function numberParser(key) {
}
}
export function numberOrBoolParser(key) {
function numberOrBoolParser(key) {
return function(opt) {
if (typeof opt === 'boolean') {
return opt;
@@ -23,14 +23,14 @@ export function numberOrBoolParser(key) {
}
}
export function objectParser(opt) {
function objectParser(opt) {
if (typeof opt == 'object') {
return opt;
}
return JSON.parse(opt)
}
export function arrayParser(opt) {
function arrayParser(opt) {
if (Array.isArray(opt)) {
return opt;
} else if (typeof opt === 'string') {
@@ -40,7 +40,7 @@ export function arrayParser(opt) {
}
}
export function moduleOrObjectParser(opt) {
function moduleOrObjectParser(opt) {
if (typeof opt == 'object') {
return opt;
}
@@ -50,16 +50,26 @@ export function moduleOrObjectParser(opt) {
return opt;
}
export function booleanParser(opt) {
function booleanParser(opt) {
if (opt == true || opt == 'true' || opt == '1') {
return true;
}
return false;
}
export function nullParser(opt) {
function nullParser(opt) {
if (opt == 'null') {
return null;
}
return opt;
}
module.exports = {
numberParser,
numberOrBoolParser,
nullParser,
booleanParser,
moduleOrObjectParser,
arrayParser,
objectParser
};

View File

@@ -5,59 +5,38 @@ var batch = require('./batch'),
express = require('express'),
middlewares = require('./middlewares'),
Parse = require('parse/node').Parse,
path = require('path'),
url = require('url'),
authDataManager = require('./Adapters/Auth');
path = require('path');
import { ParseServerOptions,
LiveQueryServerOptions } from './Options';
import defaults from './defaults';
import * as logging from './logger';
import AppCache from './cache';
import Config from './Config';
import PromiseRouter from './PromiseRouter';
import requiredParameter from './requiredParameter';
import { AnalyticsRouter } from './Routers/AnalyticsRouter';
import { ClassesRouter } from './Routers/ClassesRouter';
import { FeaturesRouter } from './Routers/FeaturesRouter';
import { InMemoryCacheAdapter } from './Adapters/Cache/InMemoryCacheAdapter';
import { AnalyticsController } from './Controllers/AnalyticsController';
import { CacheController } from './Controllers/CacheController';
import { AnalyticsAdapter } from './Adapters/Analytics/AnalyticsAdapter';
import { WinstonLoggerAdapter } from './Adapters/Logger/WinstonLoggerAdapter';
import { FilesController } from './Controllers/FilesController';
import { FilesRouter } from './Routers/FilesRouter';
import { FunctionsRouter } from './Routers/FunctionsRouter';
import { GlobalConfigRouter } from './Routers/GlobalConfigRouter';
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
import { HooksController } from './Controllers/HooksController';
import { HooksRouter } from './Routers/HooksRouter';
import { IAPValidationRouter } from './Routers/IAPValidationRouter';
import { InstallationsRouter } from './Routers/InstallationsRouter';
import { loadAdapter } from './Adapters/AdapterLoader';
import { LiveQueryController } from './Controllers/LiveQueryController';
import { LoggerController } from './Controllers/LoggerController';
import { LogsRouter } from './Routers/LogsRouter';
import { ParseLiveQueryServer } from './LiveQuery/ParseLiveQueryServer';
import { PublicAPIRouter } from './Routers/PublicAPIRouter';
import { PushController } from './Controllers/PushController';
import { PushQueue } from './Push/PushQueue';
import { PushWorker } from './Push/PushWorker';
import { PushRouter } from './Routers/PushRouter';
import { CloudCodeRouter } from './Routers/CloudCodeRouter';
import { RolesRouter } from './Routers/RolesRouter';
import { SchemasRouter } from './Routers/SchemasRouter';
import { SessionsRouter } from './Routers/SessionsRouter';
import { UserController } from './Controllers/UserController';
import { UsersRouter } from './Routers/UsersRouter';
import { PurgeRouter } from './Routers/PurgeRouter';
import { AudiencesRouter } from './Routers/AudiencesRouter';
import DatabaseController from './Controllers/DatabaseController';
import SchemaCache from './Controllers/SchemaCache';
import ParsePushAdapter from 'parse-server-push-adapter';
import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter';
import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageAdapter';
import { ParseServerRESTController } from './ParseServerRESTController';
import * as controllers from './Controllers';
// Mutate the Parse object to add the Cloud Code handlers
addParseCloud();
@@ -90,200 +69,31 @@ addParseCloud();
class ParseServer {
constructor({
appId = requiredParameter('You must provide an appId!'),
masterKey = requiredParameter('You must provide a masterKey!'),
masterKeyIps = [],
appName,
analyticsAdapter,
filesAdapter,
push,
scheduledPush = false,
loggerAdapter,
jsonLogs = defaults.jsonLogs,
logsFolder = defaults.logsFolder,
verbose = defaults.verbose,
logLevel = defaults.level,
silent = defaults.silent,
databaseURI = defaults.DefaultMongoURI,
databaseOptions,
databaseAdapter,
cloud,
collectionPrefix = '',
clientKey,
javascriptKey,
dotNetKey,
restAPIKey,
webhookKey,
fileKey,
userSensitiveFields = [],
enableAnonymousUsers = defaults.enableAnonymousUsers,
allowClientClassCreation = defaults.allowClientClassCreation,
oauth = {},
auth = {},
serverURL = requiredParameter('You must provide a serverURL!'),
maxUploadSize = defaults.maxUploadSize,
verifyUserEmails = defaults.verifyUserEmails,
preventLoginWithUnverifiedEmail = defaults.preventLoginWithUnverifiedEmail,
emailVerifyTokenValidityDuration,
accountLockout,
passwordPolicy,
cacheAdapter,
emailAdapter,
publicServerURL,
customPages = {
invalidLink: undefined,
verifyEmailSuccess: undefined,
choosePassword: undefined,
passwordResetSuccess: undefined
},
liveQuery = {},
sessionLength = defaults.sessionLength, // 1 Year in seconds
maxLimit,
expireInactiveSessions = defaults.expireInactiveSessions,
revokeSessionOnPasswordReset = defaults.revokeSessionOnPasswordReset,
schemaCacheTTL = defaults.schemaCacheTTL, // cache for 5s
cacheTTL = defaults.cacheTTL, // cache for 5s
cacheMaxSize = defaults.cacheMaxSize, // 10000
enableSingleSchemaCache = false,
objectIdSize = defaults.objectIdSize,
__indexBuildCompletionCallbackForTests = () => {},
}) {
constructor(options: ParseServerOptions) {
injectDefaults(options);
const {
appId = requiredParameter('You must provide an appId!'),
masterKey = requiredParameter('You must provide a masterKey!'),
cloud,
javascriptKey,
serverURL = requiredParameter('You must provide a serverURL!'),
__indexBuildCompletionCallbackForTests = () => {},
} = options;
// Initialize the node client SDK automatically
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
Parse.serverURL = serverURL;
if ((databaseOptions || (databaseURI && databaseURI != defaults.DefaultMongoURI) || collectionPrefix !== '') && databaseAdapter) {
throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/collectionPrefix.';
} else if (!databaseAdapter) {
databaseAdapter = this.getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions)
} else {
databaseAdapter = loadAdapter(databaseAdapter)
}
if (!filesAdapter && !databaseURI) {
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
}
userSensitiveFields = Array.from(new Set(userSensitiveFields.concat(
defaults.userSensitiveFields,
userSensitiveFields
)));
masterKeyIps = Array.from(new Set(masterKeyIps.concat(
defaults.masterKeyIps,
masterKeyIps
)));
const loggerOptions = { jsonLogs, logsFolder, verbose, logLevel, silent };
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, loggerOptions);
const loggerController = new LoggerController(loggerControllerAdapter, appId, loggerOptions);
logging.setLogger(loggerController);
const filesControllerAdapter = loadAdapter(filesAdapter, () => {
return new GridStoreAdapter(databaseURI);
});
const filesController = new FilesController(filesControllerAdapter, appId);
const pushOptions = Object.assign({}, push);
const pushQueueOptions = pushOptions.queueOptions || {};
if (pushOptions.queueOptions) {
delete pushOptions.queueOptions;
}
// Pass the push options too as it works with the default
const pushAdapter = loadAdapter(pushOptions && pushOptions.adapter, ParsePushAdapter, pushOptions);
// We pass the options and the base class for the adatper,
// Note that passing an instance would work too
const pushController = new PushController();
const hasPushSupport = !!(pushAdapter && push);
const hasPushScheduledSupport = hasPushSupport && (scheduledPush === true);
const allControllers = controllers.getControllers(options);
const {
disablePushWorker
} = pushQueueOptions;
const pushControllerQueue = new PushQueue(pushQueueOptions);
let pushWorker;
if (!disablePushWorker) {
pushWorker = new PushWorker(pushAdapter, pushQueueOptions);
}
const emailControllerAdapter = loadAdapter(emailAdapter);
const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails });
const cacheControllerAdapter = loadAdapter(cacheAdapter, InMemoryCacheAdapter, {appId: appId, ttl: cacheTTL, maxSize: cacheMaxSize });
const cacheController = new CacheController(cacheControllerAdapter, appId);
const analyticsControllerAdapter = loadAdapter(analyticsAdapter, AnalyticsAdapter);
const analyticsController = new AnalyticsController(analyticsControllerAdapter);
const liveQueryController = new LiveQueryController(liveQuery);
const databaseController = new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache));
const hooksController = new HooksController(appId, databaseController, webhookKey);
const dbInitPromise = databaseController.performInitialization();
if (Object.keys(oauth).length > 0) {
/* eslint-disable no-console */
console.warn('oauth option is deprecated and will be removed in a future release, please use auth option instead');
if (Object.keys(auth).length > 0) {
console.warn('You should use only the auth option.');
}
/* eslint-enable */
}
auth = Object.assign({}, oauth, auth);
AppCache.put(appId, {
appId,
masterKey: masterKey,
masterKeyIps:masterKeyIps,
serverURL: serverURL,
collectionPrefix: collectionPrefix,
clientKey: clientKey,
javascriptKey: javascriptKey,
dotNetKey: dotNetKey,
restAPIKey: restAPIKey,
webhookKey: webhookKey,
fileKey: fileKey,
analyticsController: analyticsController,
cacheController: cacheController,
filesController: filesController,
pushController: pushController,
loggerController: loggerController,
hooksController: hooksController,
userController: userController,
verifyUserEmails: verifyUserEmails,
preventLoginWithUnverifiedEmail: preventLoginWithUnverifiedEmail,
emailVerifyTokenValidityDuration: emailVerifyTokenValidityDuration,
accountLockout: accountLockout,
passwordPolicy: passwordPolicy,
allowClientClassCreation: allowClientClassCreation,
authDataManager: authDataManager(auth, enableAnonymousUsers),
appName: appName,
publicServerURL: publicServerURL,
customPages: customPages,
maxUploadSize: maxUploadSize,
liveQueryController: liveQueryController,
sessionLength: Number(sessionLength),
maxLimit: Number(maxLimit),
expireInactiveSessions: expireInactiveSessions,
jsonLogs,
revokeSessionOnPasswordReset,
loggerController,
databaseController,
schemaCacheTTL,
enableSingleSchemaCache,
userSensitiveFields,
pushWorker,
pushControllerQueue,
hasPushSupport,
hasPushScheduledSupport,
objectIdSize
});
hooksController,
} = allControllers;
this.config = Config.put(Object.assign({}, options, allControllers));
Config.validate(AppCache.get(appId));
this.config = AppCache.get(appId);
Config.setupPasswordValidator(this.config.passwordPolicy);
logging.setLogger(loggerController);
const dbInitPromise = databaseController.performInitialization();
hooksController.load();
// Note: Tests will start to fail if any validation happens after this is called.
@@ -303,30 +113,11 @@ class ParseServer {
}
}
getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions) {
let protocol;
try {
const parsedURI = url.parse(databaseURI);
protocol = parsedURI.protocol ? parsedURI.protocol.toLowerCase() : null;
} catch(e) { /* */ }
switch (protocol) {
case 'postgres:':
return new PostgresStorageAdapter({
uri: databaseURI,
collectionPrefix,
databaseOptions
});
default:
return new MongoStorageAdapter({
uri: databaseURI,
collectionPrefix,
mongoOptions: databaseOptions,
});
}
}
get app() {
return ParseServer.app(this.config);
if (!this._app) {
this._app = ParseServer.app(this.config);
}
return this._app;
}
handleShutdown() {
@@ -363,17 +154,17 @@ class ParseServer {
// run the following when not testing
if (!process.env.TESTING) {
//This causes tests to spew some useless warnings, so disable in test
/* istanbul ignore next */
process.on('uncaughtException', (err) => {
if (err.code === "EADDRINUSE") { // user-friendly message for this common error
/* eslint-disable no-console */
console.error(`Unable to listen on port ${err.port}. The port is already in use.`);
/* eslint-enable no-console */
process.stderr.write(`Unable to listen on port ${err.port}. The port is already in use.`);
process.exit(0);
} else {
throw err;
}
});
// verify the server url after a 'mount' event is received
/* istanbul ignore next */
api.on('mount', function() {
ParseServer.verifyServerUrl();
});
@@ -415,7 +206,44 @@ class ParseServer {
return appRouter;
}
static createLiveQueryServer(httpServer, config) {
start(options: ParseServerOptions, callback: ?()=>void) {
const app = express();
if (options.middleware) {
let middleware;
if (typeof options.middleware == 'string') {
middleware = require(path.resolve(process.cwd(), options.middleware));
} else {
middleware = options.middleware; // use as-is let express fail
}
app.use(middleware);
}
app.use(options.mountPath, this.app);
const server = app.listen(options.port, options.host, callback);
this.server = server;
if (options.startLiveQueryServer || options.liveQueryServerOptions) {
this.liveQueryServer = ParseServer.createLiveQueryServer(server, options.liveQueryServerOptions);
}
/* istanbul ignore next */
if (!process.env.TESTING) {
configureListeners(this);
}
this.expressApp = app;
return this;
}
static start(options: ParseServerOptions, callback: ?()=>void) {
const parseServer = new ParseServer(options);
return parseServer.start(options, callback);
}
static createLiveQueryServer(httpServer, config: LiveQueryServerOptions) {
if (!httpServer || (config && config.port)) {
var app = express();
httpServer = require('http').createServer(app);
httpServer.listen(config.port);
}
return new ParseLiveQueryServer(httpServer, config);
}
@@ -447,4 +275,59 @@ function addParseCloud() {
global.Parse = Parse;
}
function injectDefaults(options: ParseServerOptions) {
Object.keys(defaults).forEach((key) => {
if (!options.hasOwnProperty(key)) {
options[key] = defaults[key];
}
});
if (!options.hasOwnProperty('serverURL')) {
options.serverURL = `http://localhost:${options.port}${options.mountPath}`;
}
options.userSensitiveFields = Array.from(new Set(options.userSensitiveFields.concat(
defaults.userSensitiveFields,
options.userSensitiveFields
)));
options.masterKeyIps = Array.from(new Set(options.masterKeyIps.concat(
defaults.masterKeyIps,
options.masterKeyIps
)));
}
// Those can't be tested as it requires a subprocess
/* istanbul ignore next */
function configureListeners(parseServer) {
const server = parseServer.server;
const sockets = {};
/* Currently, express doesn't shut down immediately after receiving SIGINT/SIGTERM if it has client connections that haven't timed out. (This is a known issue with node - https://github.com/nodejs/node/issues/2642)
This function, along with `destroyAliveConnections()`, intend to fix this behavior such that parse server will close all open connections and initiate the shutdown process as soon as it receives a SIGINT/SIGTERM signal. */
server.on('connection', (socket) => {
const socketId = socket.remoteAddress + ':' + socket.remotePort;
sockets[socketId] = socket;
socket.on('close', () => {
delete sockets[socketId];
});
});
const destroyAliveConnections = function() {
for (const socketId in sockets) {
try {
sockets[socketId].destroy();
} catch (e) { /* */ }
}
}
const handleShutdown = function() {
process.stdout.write('Termination signal received. Shutting down.');
destroyAliveConnections();
server.close();
parseServer.handleShutdown();
};
process.on('SIGTERM', handleShutdown);
process.on('SIGINT', handleShutdown);
}
export default ParseServer;

View File

@@ -35,7 +35,7 @@ function ParseServerRESTController(applicationId, router) {
// Store the arguments, for later use if internal fails
const args = arguments;
const config = new Config(applicationId);
const config = Config.get(applicationId);
const serverURL = URL.parse(config.serverURL);
if (path.indexOf(serverURL.path) === 0) {
path = path.slice(serverURL.path.length, path.length);

View File

@@ -48,7 +48,7 @@ export class PushWorker {
}
run({ body, query, pushStatus, applicationId, UTCOffset }: any): Promise<*> {
const config = new Config(applicationId);
const config = Config.get(applicationId);
const auth = master(config);
const where = utils.applyDeviceTokenExists(query.where);
delete query.where;

View File

@@ -34,7 +34,7 @@ export class FilesRouter {
}
getHandler(req, res) {
const config = new Config(req.params.appId);
const config = Config.get(req.params.appId);
const filesController = config.filesController;
const filename = req.params.filename;
const contentType = mime.lookup(filename);

View File

@@ -13,7 +13,7 @@ export class PublicAPIRouter extends PromiseRouter {
verifyEmail(req) {
const { token, username } = req.query;
const appId = req.params.appId;
const config = new Config(appId);
const config = Config.get(appId);
if (!config.publicServerURL) {
return this.missingPublicServerURL();
@@ -38,7 +38,7 @@ export class PublicAPIRouter extends PromiseRouter {
resendVerificationEmail(req) {
const username = req.body.username;
const appId = req.params.appId;
const config = new Config(appId);
const config = Config.get(appId);
if (!config.publicServerURL) {
return this.missingPublicServerURL();
@@ -65,7 +65,7 @@ export class PublicAPIRouter extends PromiseRouter {
changePassword(req) {
return new Promise((resolve, reject) => {
const config = new Config(req.query.id);
const config = Config.get(req.query.id);
if (!config.publicServerURL) {
return resolve({
status: 404,
@@ -172,7 +172,7 @@ export class PublicAPIRouter extends PromiseRouter {
}
setConfig(req) {
req.config = new Config(req.params.appId);
req.config = Config.get(req.params.appId);
return Promise.resolve();
}

View File

@@ -1,42 +1,2 @@
import {
numberParser
} from '../utils/parsers';
export default {
"appId": {
required: true,
help: "Required. This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId."
},
"masterKey": {
required: true,
help: "Required. This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey."
},
"serverURL": {
required: true,
help: "Required. This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL."
},
"redisURL": {
help: "Optional. This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey."
},
"keyPairs": {
help: "Optional. A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details."
},
"websocketTimeout": {
help: "Optional. Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients. Defaults to 10 * 1000 ms (10 s).",
action: numberParser("websocketTimeout")
},
"cacheTimeout": {
help: "Optional. Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details. Defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).",
action: numberParser("cacheTimeout")
},
"logLevel": {
help: "Optional. This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE. Defaults to INFO.",
},
"port": {
env: "PORT",
help: "The port to run the ParseServer. defaults to 1337.",
default: 1337,
action: numberParser("port")
},
};
const LiveQueryServerOptions = require('../../Options/Definitions').LiveQueryServerOptions;
export default LiveQueryServerOptions;

View File

@@ -1,284 +1,2 @@
import {
numberParser,
numberOrBoolParser,
objectParser,
arrayParser,
moduleOrObjectParser,
booleanParser,
nullParser
} from '../utils/parsers';
export default {
"appId": {
env: "PARSE_SERVER_APPLICATION_ID",
help: "Your Parse Application ID",
required: true
},
"masterKey": {
env: "PARSE_SERVER_MASTER_KEY",
help: "Your Parse Master Key",
required: true
},
"masterKeyIps": {
env: "PARSE_SERVER_MASTER_KEY_IPS",
help: "Restrict masterKey to be used by only these ips. defaults to [] (allow all ips)",
default: []
},
"port": {
env: "PORT",
help: "The port to run the ParseServer. defaults to 1337.",
default: 1337,
action: numberParser("port")
},
"host": {
env: "PARSE_SERVER_HOST",
help: "The host to serve ParseServer on. defaults to 0.0.0.0",
default: '0.0.0.0',
},
"databaseURI": {
env: "PARSE_SERVER_DATABASE_URI",
help: "The full URI to your mongodb database"
},
"databaseOptions": {
env: "PARSE_SERVER_DATABASE_OPTIONS",
help: "Options to pass to the mongodb client",
action: objectParser
},
"collectionPrefix": {
env: "PARSE_SERVER_COLLECTION_PREFIX",
help: 'A collection prefix for the classes'
},
"serverURL": {
env: "PARSE_SERVER_URL",
help: "URL to your parse server with http:// or https://.",
},
"publicServerURL": {
env: "PARSE_PUBLIC_SERVER_URL",
help: "Public URL to your parse server with http:// or https://.",
},
"clientKey": {
env: "PARSE_SERVER_CLIENT_KEY",
help: "Key for iOS, MacOS, tvOS clients"
},
"javascriptKey": {
env: "PARSE_SERVER_JAVASCRIPT_KEY",
help: "Key for the Javascript SDK"
},
"restAPIKey": {
env: "PARSE_SERVER_REST_API_KEY",
help: "Key for REST calls"
},
"dotNetKey": {
env: "PARSE_SERVER_DOT_NET_KEY",
help: "Key for Unity and .Net SDK"
},
"webhookKey": {
env: "PARSE_SERVER_WEBHOOK_KEY",
help: "Key sent with outgoing webhook calls"
},
"cloud": {
env: "PARSE_SERVER_CLOUD_CODE_MAIN",
help: "Full path to your cloud code main.js"
},
"push": {
env: "PARSE_SERVER_PUSH",
help: "Configuration for push, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#push-notifications",
action: objectParser
},
"scheduledPush": {
env: "PARSE_SERVER_SCHEDULED_PUSH",
help: "Configuration for push scheduling. Defaults to false.",
action: booleanParser
},
"oauth": {
env: "PARSE_SERVER_OAUTH_PROVIDERS",
help: "[DEPRECATED (use auth option)] Configuration for your oAuth providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication",
action: objectParser
},
"auth": {
env: "PARSE_SERVER_AUTH_PROVIDERS",
help: "Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication",
action: objectParser
},
"fileKey": {
env: "PARSE_SERVER_FILE_KEY",
help: "Key for your files",
},
"facebookAppIds": {
env: "PARSE_SERVER_FACEBOOK_APP_IDS",
help: "[DEPRECATED (use auth option)]",
action: function() {
throw 'facebookAppIds is deprecated, please use { auth: \
{facebook: \
{ appIds: [] } \
}\
}\
}';
}
},
"enableAnonymousUsers": {
env: "PARSE_SERVER_ENABLE_ANON_USERS",
help: "Enable (or disable) anon users, defaults to true",
action: booleanParser
},
"allowClientClassCreation": {
env: "PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION",
help: "Enable (or disable) client class creation, defaults to true",
action: booleanParser
},
"mountPath": {
env: "PARSE_SERVER_MOUNT_PATH",
help: "Mount path for the server, defaults to /parse",
default: "/parse"
},
"filesAdapter": {
env: "PARSE_SERVER_FILES_ADAPTER",
help: "Adapter module for the files sub-system",
action: moduleOrObjectParser
},
"emailAdapter": {
env: "PARSE_SERVER_EMAIL_ADAPTER",
help: "Adapter module for the email sending",
action: moduleOrObjectParser
},
"verifyUserEmails": {
env: "PARSE_SERVER_VERIFY_USER_EMAILS",
help: "Enable (or disable) user email validation, defaults to false",
action: booleanParser
},
"preventLoginWithUnverifiedEmail": {
env: "PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL",
help: "Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false",
action: booleanParser
},
"emailVerifyTokenValidityDuration": {
env: "PARSE_SERVER_EMAIL_VERIFY_TOKEN_VALIDITY_DURATION",
help: "Email verification token validity duration",
action: numberParser("emailVerifyTokenValidityDuration")
},
"accountLockout": {
env: "PARSE_SERVER_ACCOUNT_LOCKOUT",
help: "account lockout policy for failed login attempts",
action: objectParser
},
"passwordPolicy": {
env: "PARSE_SERVER_PASSWORD_POLICY",
help: "Password policy for enforcing password related rules",
action: objectParser
},
"appName": {
env: "PARSE_SERVER_APP_NAME",
help: "Sets the app name"
},
"loggerAdapter": {
env: "PARSE_SERVER_LOGGER_ADAPTER",
help: "Adapter module for the logging sub-system",
action: moduleOrObjectParser
},
"customPages": {
env: "PARSE_SERVER_CUSTOM_PAGES",
help: "custom pages for password validation and reset",
action: objectParser
},
"maxUploadSize": {
env: "PARSE_SERVER_MAX_UPLOAD_SIZE",
help: "Max file size for uploads.",
default: "20mb"
},
"userSensitiveFields": {
help: "Personally identifiable information fields in the user table the should be removed for non-authorized users.",
default: ["email"]
},
"sessionLength": {
env: "PARSE_SERVER_SESSION_LENGTH",
help: "Session duration, defaults to 1 year",
action: numberParser("sessionLength")
},
"maxLimit": {
env: "PARSE_SERVER_MAX_LIMIT",
help: "Max value for limit option on queries, defaults to unlimited",
action: numberParser("maxLimit")
},
"verbose": {
env: "VERBOSE",
help: "Set the logging to verbose"
},
"jsonLogs": {
env: "JSON_LOGS",
help: "Log as structured JSON objects"
},
"logLevel": {
env: "PARSE_SERVER_LOG_LEVEL",
help: "Sets the level for logs"
},
"logsFolder": {
env: "PARSE_SERVER_LOGS_FOLDER",
help: "Folder for the logs (defaults to './logs'); set to null to disable file based logging",
action: nullParser
},
"silent": {
help: "Disables console output",
},
"revokeSessionOnPasswordReset": {
env: "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET",
help: "When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.",
action: booleanParser
},
"schemaCacheTTL": {
env: "PARSE_SERVER_SCHEMA_CACHE_TTL",
help: "The TTL for caching the schema for optimizing read/write operations. You should put a long TTL when your DB is in production. default to 0; disabled.",
action: numberParser("schemaCacheTTL"),
},
"enableSingleSchemaCache": {
env: "PARSE_SERVER_ENABLE_SINGLE_SCHEMA_CACHE",
help: "Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA. Defaults to false, i.e. unique schema cache per request.",
action: booleanParser
},
"cacheTTL": {
env: "PARSE_SERVER_CACHE_TTL",
help: "Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds)",
action: numberParser("cacheTTL"),
},
"cacheMaxSize": {
env: "PARSE_SERVER_CACHE_MAX_SIZE",
help: "Sets the maximum size for the in memory cache, defaults to 10000",
action: numberParser("cacheMaxSize")
},
"cluster": {
env: "PARSE_SERVER_CLUSTER",
help: "Run with cluster, optionally set the number of processes default to os.cpus().length",
action: numberOrBoolParser("cluster")
},
"liveQuery": {
env: "PARSE_SERVER_LIVE_QUERY_OPTIONS",
help: "parse-server's LiveQuery configuration object",
action: objectParser
},
"liveQuery.classNames": {
help: "parse-server's LiveQuery classNames",
action: arrayParser
},
"liveQuery.redisURL": {
help: "parse-server's LiveQuery redisURL",
},
"startLiveQueryServer": {
help: "Starts the liveQuery server",
action: booleanParser
},
"liveQueryPort": {
help: 'Specific port to start the live query server',
action: numberParser("liveQueryPort")
},
"liveQueryServerOptions": {
help: "Live query server configuration options (will start the liveQuery server)",
action: objectParser
},
"middleware": {
help: "middleware for express server, can be string or function"
},
"objectIdSize": {
env: "PARSE_SERVER_OBJECT_ID_SIZE",
help: "Sets the number of characters in generated object id's, default 10",
action: numberParser("objectIdSize")
}
};
const ParseServerDefinitions = require('../../Options/Definitions').ParseServerOptions;
export default ParseServerDefinitions;

View File

@@ -1,15 +1,11 @@
import definitions from './definitions/parse-live-query-server';
import runner from './utils/runner';
import { ParseServer } from '../index';
import express from 'express';
runner({
definitions,
start: function(program, options, logOptions) {
logOptions();
var app = express();
var httpServer = require('http').createServer(app);
httpServer.listen(options.port);
ParseServer.createLiveQueryServer(httpServer, options);
ParseServer.createLiveQueryServer(undefined, options);
}
})

View File

@@ -1,11 +1,9 @@
/* eslint-disable no-console */
import express from 'express';
import ParseServer from '../index';
import definitions from './definitions/parse-server';
import cluster from 'cluster';
import os from 'os';
import runner from './utils/runner';
const path = require("path");
const help = function(){
console.log(' Get Started guide:');
@@ -29,79 +27,12 @@ const help = function(){
console.log('');
};
function startServer(options, callback) {
const app = express();
if (options.middleware) {
let middleware;
if (typeof options.middleware == 'function') {
middleware = options.middleware;
} if (typeof options.middleware == 'string') {
middleware = require(path.resolve(process.cwd(), options.middleware));
} else {
throw "middleware should be a string or a function";
}
app.use(middleware);
}
const parseServer = new ParseServer(options);
const sockets = {};
app.use(options.mountPath, parseServer.app);
const server = app.listen(options.port, options.host, callback);
server.on('connection', initializeConnections);
if (options.startLiveQueryServer || options.liveQueryServerOptions) {
let liveQueryServer = server;
if (options.liveQueryPort) {
liveQueryServer = express().listen(options.liveQueryPort, () => {
console.log('ParseLiveQuery listening on ' + options.liveQueryPort);
});
}
ParseServer.createLiveQueryServer(liveQueryServer, options.liveQueryServerOptions);
}
function initializeConnections(socket) {
/* Currently, express doesn't shut down immediately after receiving SIGINT/SIGTERM if it has client connections that haven't timed out. (This is a known issue with node - https://github.com/nodejs/node/issues/2642)
This function, along with `destroyAliveConnections()`, intend to fix this behavior such that parse server will close all open connections and initiate the shutdown process as soon as it receives a SIGINT/SIGTERM signal. */
const socketId = socket.remoteAddress + ':' + socket.remotePort;
sockets[socketId] = socket;
socket.on('close', () => {
delete sockets[socketId];
});
}
function destroyAliveConnections() {
for (const socketId in sockets) {
try {
sockets[socketId].destroy();
} catch (e) { /* */ }
}
}
const handleShutdown = function() {
console.log('Termination signal received. Shutting down.');
destroyAliveConnections();
server.close();
parseServer.handleShutdown();
};
process.on('SIGTERM', handleShutdown);
process.on('SIGINT', handleShutdown);
}
runner({
definitions,
help,
usage: '[options] <path/to/configuration.json>',
start: function(program, options, logOptions) {
if (!options.serverURL) {
options.serverURL = `http://localhost:${options.port}${options.mountPath}`;
}
if (!options.appId || !options.masterKey || !options.serverURL) {
if (!options.appId || !options.masterKey) {
program.outputHelp();
console.error("");
console.error('\u001b[31mERROR: appId and masterKey are required\u001b[0m');
@@ -132,12 +63,12 @@ runner({
cluster.fork();
});
} else {
startServer(options, () => {
ParseServer.start(options, () => {
console.log('[' + process.pid + '] parse-server running on ' + options.serverURL);
});
}
} else {
startServer(options, () => {
ParseServer.start(options, () => {
logOptions();
console.log('');
console.log('[' + process.pid + '] parse-server running on ' + options.serverURL);

View File

@@ -20,13 +20,6 @@ Command.prototype.loadDefinitions = function(definitions) {
return program.option(`--${opt} [${opt}]`);
}, this);
_defaults = Object.keys(definitions).reduce((defs, opt) => {
if(_definitions[opt].default) {
defs[opt] = _definitions[opt].default;
}
return defs;
}, {});
_reverseDefinitions = Object.keys(definitions).reduce((object, key) => {
let value = definitions[key];
if (typeof value == "object") {
@@ -38,6 +31,13 @@ Command.prototype.loadDefinitions = function(definitions) {
return object;
}, {});
_defaults = Object.keys(definitions).reduce((defs, opt) => {
if(_definitions[opt].default) {
defs[opt] = _definitions[opt].default;
}
return defs;
}, {});
/* istanbul ignore next */
this.on('--help', function(){
console.log(' Configure From Environment:');

View File

@@ -8,7 +8,13 @@ function logStartupOptions(options) {
value = "***REDACTED***";
}
if (typeof value === 'object') {
value = JSON.stringify(value);
try {
value = JSON.stringify(value)
} catch(e) {
if (value && value.constructor && value.constructor.name) {
value = value.constructor.name;
}
}
}
/* eslint-disable no-console */
console.log(`${key}: ${value}`);

View File

@@ -1,5 +1,5 @@
import {nullParser} from './cli/utils/parsers';
import { nullParser } from './Options/parsers';
const { ParseServerOptions } = require('./Options/Definitions');
const logsFolder = (() => {
let folder = './logs/';
if (typeof process !== 'undefined' && process.env.TESTING === '1') {
@@ -16,25 +16,21 @@ const { verbose, level } = (() => {
return { verbose, level: verbose ? 'verbose' : undefined }
})();
export default {
DefaultMongoURI: 'mongodb://localhost:27017/parse',
const DefinitionDefaults = Object.keys(ParseServerOptions).reduce((memo, key) => {
const def = ParseServerOptions[key];
if (def.hasOwnProperty('default')) {
memo[key] = def.default;
}
return memo;
}, {});
const computedDefaults = {
jsonLogs: process.env.JSON_LOGS || false,
logsFolder,
verbose,
level,
silent: false,
enableAnonymousUsers: true,
allowClientClassCreation: true,
maxUploadSize: '20mb',
verifyUserEmails: false,
preventLoginWithUnverifiedEmail: false,
sessionLength: 31536000,
expireInactiveSessions: true,
revokeSessionOnPasswordReset: true,
schemaCacheTTL: 5000, // in ms
cacheTTL: 5000,
cacheMaxSize: 10000,
userSensitiveFields: ['email'],
objectIdSize: 10,
masterKeyIps: []
}
export default Object.assign({}, DefinitionDefaults, computedDefaults);
export const DefaultMongoURI = DefinitionDefaults.databaseURI;

View File

@@ -9,14 +9,16 @@ import * as TestUtils from './TestUtils';
import { useExternal } from './deprecated';
import { getLogger } from './logger';
import { PushWorker } from './Push/PushWorker';
import { ParseServerOptions } from './Options';
// Factory function
const _ParseServer = function(options) {
const _ParseServer = function(options: ParseServerOptions) {
const server = new ParseServer(options);
return server.app;
}
// Mount the create liveQueryServer
_ParseServer.createLiveQueryServer = ParseServer.createLiveQueryServer;
_ParseServer.start = ParseServer.start;
const GCSAdapter = useExternal('GCSAdapter', 'parse-server-gcs-adapter');

View File

@@ -109,7 +109,7 @@ export function handleParseHeaders(req, res, next) {
const clientIp = getClientIp(req);
info.app = AppCache.get(info.appId);
req.config = new Config(info.appId, mount);
req.config = Config.get(info.appId, mount);
req.config.headers = req.headers || {};
req.config.ip = clientIp;
req.info = info;