Merge pull request #534 from flovilmart/refactor-to-routers
Refactors Controllers to split Controllers and Routers
This commit is contained in:
27
spec/FilesController.spec.js
Normal file
27
spec/FilesController.spec.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
var FilesController = require('../src/Controllers/FilesController').FilesController;
|
||||||
|
var Config = require("../src/Config");
|
||||||
|
|
||||||
|
// Small additional tests to improve overall coverage
|
||||||
|
describe("FilesController",()=>{
|
||||||
|
|
||||||
|
it("should properly expand objects", (done) => {
|
||||||
|
var config = new Config(Parse.applicationId);
|
||||||
|
var filesController = new FilesController();
|
||||||
|
var result = filesController.expandFilesInObject(config, function(){});
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
|
||||||
|
var fullFile = {
|
||||||
|
type: '__type',
|
||||||
|
url: "http://an.url"
|
||||||
|
}
|
||||||
|
|
||||||
|
var anObject = {
|
||||||
|
aFile: fullFile
|
||||||
|
}
|
||||||
|
filesController.expandFilesInObject(config, anObject);
|
||||||
|
expect(anObject.aFile.url).toEqual("http://an.url");
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2,53 +2,85 @@ var LoggerController = require('../src/Controllers/LoggerController').LoggerCont
|
|||||||
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
|
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
|
||||||
|
|
||||||
describe('LoggerController', () => {
|
describe('LoggerController', () => {
|
||||||
it('can check valid master key of request', (done) => {
|
it('can check process a query witout throwing', (done) => {
|
||||||
// Make mock request
|
// Make mock request
|
||||||
var request = {
|
var query = {};
|
||||||
auth: {
|
|
||||||
isMaster: true
|
var loggerController = new LoggerController(new FileLoggerAdapter());
|
||||||
},
|
|
||||||
query: {}
|
expect(() => {
|
||||||
|
loggerController.getLogs(query).then(function(res) {
|
||||||
|
expect(res.length).toBe(0);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('properly validates dateTimes', (done) => {
|
||||||
|
expect(LoggerController.validDateTime()).toBe(null);
|
||||||
|
expect(LoggerController.validDateTime("String")).toBe(null);
|
||||||
|
expect(LoggerController.validDateTime(123456).getTime()).toBe(123456);
|
||||||
|
expect(LoggerController.validDateTime("2016-01-01Z00:00:00").getTime()).toBe(1451606400000);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can set the proper default values', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var result = LoggerController.parseOptions();
|
||||||
|
expect(result.size).toEqual(10);
|
||||||
|
expect(result.order).toEqual('desc');
|
||||||
|
expect(result.level).toEqual('info');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can process a query witout throwing', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var query = {
|
||||||
|
from: "2016-01-01Z00:00:00",
|
||||||
|
until: "2016-01-01Z00:00:00",
|
||||||
|
size: 5,
|
||||||
|
order: 'asc',
|
||||||
|
level: 'error'
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = LoggerController.parseOptions(query);
|
||||||
|
|
||||||
|
expect(result.from.getTime()).toEqual(1451606400000);
|
||||||
|
expect(result.until.getTime()).toEqual(1451606400000);
|
||||||
|
expect(result.size).toEqual(5);
|
||||||
|
expect(result.order).toEqual('asc');
|
||||||
|
expect(result.level).toEqual('error');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can check process a query witout throwing', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var query = {
|
||||||
|
from: "2015-01-01",
|
||||||
|
until: "2016-01-01",
|
||||||
|
size: 5,
|
||||||
|
order: 'desc',
|
||||||
|
level: 'error'
|
||||||
};
|
};
|
||||||
|
|
||||||
var loggerController = new LoggerController(new FileLoggerAdapter());
|
var loggerController = new LoggerController(new FileLoggerAdapter());
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
loggerController.handleGET(request);
|
loggerController.getLogs(query).then(function(res) {
|
||||||
|
expect(res.length).toBe(0);
|
||||||
|
done();
|
||||||
|
})
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can check invalid construction of controller', (done) => {
|
it('should throw without an adapter', (done) => {
|
||||||
// Make mock request
|
|
||||||
var request = {
|
|
||||||
auth: {
|
|
||||||
isMaster: true
|
|
||||||
},
|
|
||||||
query: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
var loggerController = new LoggerController();
|
var loggerController = new LoggerController();
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
loggerController.handleGET(request);
|
loggerController.getLogs();
|
||||||
}).toThrow();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can check invalid master key of request', (done) => {
|
|
||||||
// Make mock request
|
|
||||||
var request = {
|
|
||||||
auth: {
|
|
||||||
isMaster: false
|
|
||||||
},
|
|
||||||
query: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
var loggerController = new LoggerController(new FileLoggerAdapter());
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
loggerController.handleGET(request);
|
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
67
spec/LogsRouter.spec.js
Normal file
67
spec/LogsRouter.spec.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
var LogsRouter = require('../src/Routers/LogsRouter').LogsRouter;
|
||||||
|
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
|
||||||
|
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
|
||||||
|
|
||||||
|
const loggerController = new LoggerController(new FileLoggerAdapter());
|
||||||
|
|
||||||
|
describe('LogsRouter', () => {
|
||||||
|
it('can check valid master key of request', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
auth: {
|
||||||
|
isMaster: true
|
||||||
|
},
|
||||||
|
query: {},
|
||||||
|
config: {
|
||||||
|
loggerController: loggerController
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var router = new LogsRouter();
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
router.handleGET(request);
|
||||||
|
}).not.toThrow();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can check invalid construction of controller', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
auth: {
|
||||||
|
isMaster: true
|
||||||
|
},
|
||||||
|
query: {},
|
||||||
|
config: {
|
||||||
|
loggerController: undefined // missing controller
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var router = new LogsRouter();
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
router.handleGET(request);
|
||||||
|
}).toThrow();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can check invalid master key of request', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
auth: {
|
||||||
|
isMaster: false
|
||||||
|
},
|
||||||
|
query: {},
|
||||||
|
config: {
|
||||||
|
loggerController: loggerController
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var router = new LogsRouter();
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
router.handleGET(request);
|
||||||
|
}).toThrow();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,103 +3,28 @@ var PushController = require('../src/Controllers/PushController').PushController
|
|||||||
describe('PushController', () => {
|
describe('PushController', () => {
|
||||||
it('can check valid master key of request', (done) => {
|
it('can check valid master key of request', (done) => {
|
||||||
// Make mock request
|
// Make mock request
|
||||||
var request = {
|
var auth = {
|
||||||
info: {
|
isMaster: true
|
||||||
masterKey: 'masterKey'
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
masterKey: 'masterKey'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
PushController.validateMasterKey(request);
|
PushController.validateMasterKey(auth);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can check invalid master key of request', (done) => {
|
it('can check invalid master key of request', (done) => {
|
||||||
// Make mock request
|
// Make mock request
|
||||||
var request = {
|
var auth = {
|
||||||
info: {
|
isMaster: false
|
||||||
masterKey: 'masterKey'
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
masterKey: 'masterKeyAgain'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
PushController.validateMasterKey(request);
|
PushController.validateMasterKey(auth);
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can get query condition when channels is set', (done) => {
|
|
||||||
// Make mock request
|
|
||||||
var request = {
|
|
||||||
body: {
|
|
||||||
channels: ['Giants', 'Mets']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var where = PushController.getQueryCondition(request);
|
|
||||||
expect(where).toEqual({
|
|
||||||
'channels': {
|
|
||||||
'$in': ['Giants', 'Mets']
|
|
||||||
}
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can get query condition when where is set', (done) => {
|
|
||||||
// Make mock request
|
|
||||||
var request = {
|
|
||||||
body: {
|
|
||||||
'where': {
|
|
||||||
'injuryReports': true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var where = PushController.getQueryCondition(request);
|
|
||||||
expect(where).toEqual({
|
|
||||||
'injuryReports': true
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can get query condition when nothing is set', (done) => {
|
|
||||||
// Make mock request
|
|
||||||
var request = {
|
|
||||||
body: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(function() {
|
|
||||||
PushController.getQueryCondition(request);
|
|
||||||
}).toThrow();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can throw on getQueryCondition when channels and where are set', (done) => {
|
|
||||||
// Make mock request
|
|
||||||
var request = {
|
|
||||||
body: {
|
|
||||||
'channels': {
|
|
||||||
'$in': ['Giants', 'Mets']
|
|
||||||
},
|
|
||||||
'where': {
|
|
||||||
'injuryReports': true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(function() {
|
|
||||||
PushController.getQueryCondition(request);
|
|
||||||
}).toThrow();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can validate device type when no device type is set', (done) => {
|
it('can validate device type when no device type is set', (done) => {
|
||||||
// Make query condition
|
// Make query condition
|
||||||
@@ -170,13 +95,11 @@ describe('PushController', () => {
|
|||||||
it('can get expiration time in string format', (done) => {
|
it('can get expiration time in string format', (done) => {
|
||||||
// Make mock request
|
// Make mock request
|
||||||
var timeStr = '2015-03-19T22:05:08Z';
|
var timeStr = '2015-03-19T22:05:08Z';
|
||||||
var request = {
|
var body = {
|
||||||
body: {
|
|
||||||
'expiration_time': timeStr
|
'expiration_time': timeStr
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var time = PushController.getExpirationTime(request);
|
var time = PushController.getExpirationTime(body);
|
||||||
expect(time).toEqual(new Date(timeStr).valueOf());
|
expect(time).toEqual(new Date(timeStr).valueOf());
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -184,28 +107,25 @@ describe('PushController', () => {
|
|||||||
it('can get expiration time in number format', (done) => {
|
it('can get expiration time in number format', (done) => {
|
||||||
// Make mock request
|
// Make mock request
|
||||||
var timeNumber = 1426802708;
|
var timeNumber = 1426802708;
|
||||||
var request = {
|
var body = {
|
||||||
body: {
|
'expiration_time': timeNumber
|
||||||
'expiration_time': timeNumber
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var time = PushController.getExpirationTime(request);
|
var time = PushController.getExpirationTime(body);
|
||||||
expect(time).toEqual(timeNumber * 1000);
|
expect(time).toEqual(timeNumber * 1000);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can throw on getExpirationTime in invalid format', (done) => {
|
it('can throw on getExpirationTime in invalid format', (done) => {
|
||||||
// Make mock request
|
// Make mock request
|
||||||
var request = {
|
var body = {
|
||||||
body: {
|
'expiration_time': 'abcd'
|
||||||
'expiration_time': 'abcd'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(function(){
|
expect(function(){
|
||||||
PushController.getExpirationTime(request);
|
PushController.getExpirationTime(body);
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
123
spec/PushRouter.spec.js
Normal file
123
spec/PushRouter.spec.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
var PushRouter = require('../src/Routers/PushRouter').PushRouter;
|
||||||
|
var request = require('request');
|
||||||
|
|
||||||
|
describe('PushRouter', () => {
|
||||||
|
it('can check valid master key of request', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
info: {
|
||||||
|
masterKey: 'masterKey'
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
masterKey: 'masterKey'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
PushRouter.validateMasterKey(request);
|
||||||
|
}).not.toThrow();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can check invalid master key of request', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
info: {
|
||||||
|
masterKey: 'masterKey'
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
masterKey: 'masterKeyAgain'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
PushRouter.validateMasterKey(request);
|
||||||
|
}).toThrow();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can get query condition when channels is set', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
body: {
|
||||||
|
channels: ['Giants', 'Mets']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var where = PushRouter.getQueryCondition(request);
|
||||||
|
expect(where).toEqual({
|
||||||
|
'channels': {
|
||||||
|
'$in': ['Giants', 'Mets']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can get query condition when where is set', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
body: {
|
||||||
|
'where': {
|
||||||
|
'injuryReports': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var where = PushRouter.getQueryCondition(request);
|
||||||
|
expect(where).toEqual({
|
||||||
|
'injuryReports': true
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can get query condition when nothing is set', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
body: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
PushRouter.getQueryCondition(request);
|
||||||
|
}).toThrow();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can throw on getQueryCondition when channels and where are set', (done) => {
|
||||||
|
// Make mock request
|
||||||
|
var request = {
|
||||||
|
body: {
|
||||||
|
'channels': {
|
||||||
|
'$in': ['Giants', 'Mets']
|
||||||
|
},
|
||||||
|
'where': {
|
||||||
|
'injuryReports': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
PushRouter.getQueryCondition(request);
|
||||||
|
}).toThrow();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends a push through REST', (done) => {
|
||||||
|
request.post({
|
||||||
|
url: Parse.serverURL+"/push",
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
'channels': {
|
||||||
|
'$in': ['Giants', 'Mets']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': Parse.applicationId,
|
||||||
|
'X-Parse-Master-Key': Parse.masterKey
|
||||||
|
}
|
||||||
|
}, function(err, res, body){
|
||||||
|
expect(body.result).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -26,6 +26,14 @@ var defaultConfiguration = {
|
|||||||
masterKey: 'test',
|
masterKey: 'test',
|
||||||
collectionPrefix: 'test_',
|
collectionPrefix: 'test_',
|
||||||
fileKey: 'test',
|
fileKey: 'test',
|
||||||
|
push: {
|
||||||
|
'ios': {
|
||||||
|
cert: 'prodCert.pem',
|
||||||
|
key: 'prodKey.pem',
|
||||||
|
production: true,
|
||||||
|
bundleId: 'bundleId'
|
||||||
|
}
|
||||||
|
},
|
||||||
oauth: { // Override the facebook provider
|
oauth: { // Override the facebook provider
|
||||||
facebook: mockFacebook(),
|
facebook: mockFacebook(),
|
||||||
myoauth: {
|
myoauth: {
|
||||||
|
|||||||
@@ -1,34 +1,38 @@
|
|||||||
// A Config object provides information about how a specific app is
|
// A Config object provides information about how a specific app is
|
||||||
// configured.
|
// configured.
|
||||||
// mount is the URL for the root of the API; includes http, domain, etc.
|
// mount is the URL for the root of the API; includes http, domain, etc.
|
||||||
function Config(applicationId, mount) {
|
export class Config {
|
||||||
var cache = require('./cache');
|
|
||||||
var DatabaseAdapter = require('./DatabaseAdapter');
|
|
||||||
|
|
||||||
var cacheInfo = cache.apps[applicationId];
|
constructor(applicationId, mount) {
|
||||||
this.valid = !!cacheInfo;
|
var cache = require('./cache');
|
||||||
if (!this.valid) {
|
var DatabaseAdapter = require('./DatabaseAdapter');
|
||||||
return;
|
|
||||||
|
var cacheInfo = cache.apps[applicationId];
|
||||||
|
this.valid = !!cacheInfo;
|
||||||
|
if (!this.valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.applicationId = applicationId;
|
||||||
|
this.collectionPrefix = cacheInfo.collectionPrefix || '';
|
||||||
|
this.masterKey = cacheInfo.masterKey;
|
||||||
|
this.clientKey = cacheInfo.clientKey;
|
||||||
|
this.javascriptKey = cacheInfo.javascriptKey;
|
||||||
|
this.dotNetKey = cacheInfo.dotNetKey;
|
||||||
|
this.restAPIKey = cacheInfo.restAPIKey;
|
||||||
|
this.fileKey = cacheInfo.fileKey;
|
||||||
|
this.facebookAppIds = cacheInfo.facebookAppIds;
|
||||||
|
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
|
||||||
|
|
||||||
|
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
|
||||||
|
this.filesController = cacheInfo.filesController;
|
||||||
|
this.pushController = cacheInfo.pushController;
|
||||||
|
this.loggerController = cacheInfo.loggerController;
|
||||||
|
this.oauth = cacheInfo.oauth;
|
||||||
|
|
||||||
|
this.mount = mount;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.applicationId = applicationId;
|
export default Config;
|
||||||
this.collectionPrefix = cacheInfo.collectionPrefix || '';
|
|
||||||
this.masterKey = cacheInfo.masterKey;
|
|
||||||
this.clientKey = cacheInfo.clientKey;
|
|
||||||
this.javascriptKey = cacheInfo.javascriptKey;
|
|
||||||
this.dotNetKey = cacheInfo.dotNetKey;
|
|
||||||
this.restAPIKey = cacheInfo.restAPIKey;
|
|
||||||
this.fileKey = cacheInfo.fileKey;
|
|
||||||
this.facebookAppIds = cacheInfo.facebookAppIds;
|
|
||||||
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
|
|
||||||
|
|
||||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
|
|
||||||
this.filesController = cacheInfo.filesController;
|
|
||||||
this.pushController = cacheInfo.pushController;
|
|
||||||
this.oauth = cacheInfo.oauth;
|
|
||||||
|
|
||||||
this.mount = mount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Config;
|
module.exports = Config;
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
// FilesController.js
|
// FilesController.js
|
||||||
|
|
||||||
import express from 'express';
|
|
||||||
import mime from 'mime';
|
|
||||||
import { Parse } from 'parse/node';
|
import { Parse } from 'parse/node';
|
||||||
import BodyParser from 'body-parser';
|
|
||||||
import * as Middlewares from '../middlewares';
|
|
||||||
import Config from '../Config';
|
|
||||||
import { randomHexString } from '../cryptoUtils';
|
import { randomHexString } from '../cryptoUtils';
|
||||||
|
|
||||||
export class FilesController {
|
export class FilesController {
|
||||||
@@ -13,98 +7,23 @@ export class FilesController {
|
|||||||
this._filesAdapter = filesAdapter;
|
this._filesAdapter = filesAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getHandler() {
|
getFileData(config, filename) {
|
||||||
return (req, res) => {
|
return this._filesAdapter.getFileData(config, filename);
|
||||||
let config = new Config(req.params.appId);
|
|
||||||
return config.filesController.getHandler()(req, res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getHandler() {
|
createFile(config, filename, data) {
|
||||||
return (req, res) => {
|
filename = randomHexString(32) + '_' + filename;
|
||||||
let config = new Config(req.params.appId);
|
var location = this._filesAdapter.getFileLocation(config, filename);
|
||||||
let filename = req.params.filename;
|
return this._filesAdapter.createFile(config, filename, data).then(() => {
|
||||||
this._filesAdapter.getFileData(config, filename).then((data) => {
|
return Promise.resolve({
|
||||||
res.status(200);
|
url: location,
|
||||||
var contentType = mime.lookup(filename);
|
name: filename
|
||||||
res.set('Content-type', contentType);
|
|
||||||
res.end(data);
|
|
||||||
}).catch((error) => {
|
|
||||||
res.status(404);
|
|
||||||
res.set('Content-type', 'text/plain');
|
|
||||||
res.end('File not found.');
|
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static createHandler() {
|
deleteFile(config, filename) {
|
||||||
return (req, res, next) => {
|
return this._filesAdapter.deleteFile(config, filename);
|
||||||
let config = req.config;
|
|
||||||
return config.filesController.createHandler()(req, res, next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createHandler() {
|
|
||||||
return (req, res, next) => {
|
|
||||||
if (!req.body || !req.body.length) {
|
|
||||||
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
|
|
||||||
'Invalid file upload.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.params.filename.length > 128) {
|
|
||||||
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
|
|
||||||
'Filename too long.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.params.filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
|
||||||
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
|
|
||||||
'Filename contains invalid characters.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filesController = req.config.filesController;
|
|
||||||
// If a content-type is included, we'll add an extension so we can
|
|
||||||
// return the same content-type.
|
|
||||||
let extension = '';
|
|
||||||
let hasExtension = req.params.filename.indexOf('.') > 0;
|
|
||||||
let contentType = req.get('Content-type');
|
|
||||||
if (!hasExtension && contentType && mime.extension(contentType)) {
|
|
||||||
extension = '.' + mime.extension(contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
let filename = randomHexString(32) + '_' + req.params.filename + extension;
|
|
||||||
filesController._filesAdapter.createFile(req.config, filename, req.body).then(() => {
|
|
||||||
res.status(201);
|
|
||||||
var location = filesController._filesAdapter.getFileLocation(req.config, filename);
|
|
||||||
res.set('Location', location);
|
|
||||||
res.json({ url: location, name: filename });
|
|
||||||
}).catch((error) => {
|
|
||||||
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
|
|
||||||
'Could not store file.'));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static deleteHandler() {
|
|
||||||
return (req, res, next) => {
|
|
||||||
let config = req.config;
|
|
||||||
return config.filesController.deleteHandler()(req, res, next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteHandler() {
|
|
||||||
return (req, res, next) => {
|
|
||||||
this._filesAdapter.deleteFile(req.config, req.params.filename).then(() => {
|
|
||||||
res.status(200);
|
|
||||||
// TODO: return useful JSON here?
|
|
||||||
res.end();
|
|
||||||
}).catch((error) => {
|
|
||||||
next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR,
|
|
||||||
'Could not delete file.'));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,32 +54,6 @@ export class FilesController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getExpressRouter() {
|
|
||||||
let router = express.Router();
|
|
||||||
router.get('/files/:appId/:filename', FilesController.getHandler());
|
|
||||||
|
|
||||||
router.post('/files', function(req, res, next) {
|
|
||||||
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
|
|
||||||
'Filename not provided.'));
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/files/:filename',
|
|
||||||
Middlewares.allowCrossDomain,
|
|
||||||
BodyParser.raw({type: '*/*', limit: '20mb'}),
|
|
||||||
Middlewares.handleParseHeaders,
|
|
||||||
FilesController.createHandler()
|
|
||||||
);
|
|
||||||
|
|
||||||
router.delete('/files/:filename',
|
|
||||||
Middlewares.allowCrossDomain,
|
|
||||||
Middlewares.handleParseHeaders,
|
|
||||||
Middlewares.enforceMasterKeyAccess,
|
|
||||||
FilesController.deleteHandler()
|
|
||||||
);
|
|
||||||
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilesController;
|
export default FilesController;
|
||||||
|
|||||||
@@ -1,35 +1,55 @@
|
|||||||
import { Parse } from 'parse/node';
|
import { Parse } from 'parse/node';
|
||||||
import PromiseRouter from '../PromiseRouter';
|
import PromiseRouter from '../PromiseRouter';
|
||||||
import rest from '../rest';
|
|
||||||
|
|
||||||
const Promise = Parse.Promise;
|
const Promise = Parse.Promise;
|
||||||
const INFO = 'info';
|
|
||||||
const ERROR = 'error';
|
|
||||||
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
|
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
// only allow request with master key
|
export const LogLevel = {
|
||||||
let enforceSecurity = (auth) => {
|
INFO: 'info',
|
||||||
if (!auth || !auth.isMaster) {
|
ERROR: 'error'
|
||||||
throw new Parse.Error(
|
|
||||||
Parse.Error.OPERATION_FORBIDDEN,
|
|
||||||
'Clients aren\'t allowed to perform the ' +
|
|
||||||
'get' + ' operation on logs.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that date input is valid
|
export const LogOrder = {
|
||||||
let isValidDateTime = (date) => {
|
DESCENDING: 'desc',
|
||||||
if (!date || isNaN(Number(date))) {
|
ASCENDING: 'asc'
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LoggerController {
|
export class LoggerController {
|
||||||
|
|
||||||
constructor(loggerAdapter) {
|
constructor(loggerAdapter, loggerOptions) {
|
||||||
this._loggerAdapter = loggerAdapter;
|
this._loggerAdapter = loggerAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check that date input is valid
|
||||||
|
static validDateTime(date) {
|
||||||
|
if (!date) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
date = new Date(date);
|
||||||
|
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseOptions(options = {}) {
|
||||||
|
let from = LoggerController.validDateTime(options.from) ||
|
||||||
|
new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY);
|
||||||
|
let until = LoggerController.validDateTime(options.until) || new Date();
|
||||||
|
let size = Number(options.size) || 10;
|
||||||
|
let order = options.order || LogOrder.DESCENDING;
|
||||||
|
let level = options.level || LogLevel.INFO;
|
||||||
|
|
||||||
|
return {
|
||||||
|
from,
|
||||||
|
until,
|
||||||
|
size,
|
||||||
|
order,
|
||||||
|
level,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a promise for a {response} object.
|
// Returns a promise for a {response} object.
|
||||||
// query params:
|
// query params:
|
||||||
@@ -38,41 +58,21 @@ export class LoggerController {
|
|||||||
// until (optional) End time for the search. Defaults to current time.
|
// until (optional) End time for the search. Defaults to current time.
|
||||||
// order (optional) Direction of results returned, either “asc” or “desc”. Defaults to “desc”.
|
// order (optional) Direction of results returned, either “asc” or “desc”. Defaults to “desc”.
|
||||||
// size (optional) Number of rows returned by search. Defaults to 10
|
// size (optional) Number of rows returned by search. Defaults to 10
|
||||||
handleGET(req) {
|
getLogs(options= {}) {
|
||||||
if (!this._loggerAdapter) {
|
if (!this._loggerAdapter) {
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
'Logger adapter is not availabe');
|
'Logger adapter is not availabe');
|
||||||
}
|
}
|
||||||
|
|
||||||
let promise = new Parse.Promise();
|
let promise = new Parse.Promise();
|
||||||
let from = (isValidDateTime(req.query.from) && new Date(req.query.from)) ||
|
|
||||||
new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY);
|
options = LoggerController.parseOptions(options);
|
||||||
let until = (isValidDateTime(req.query.until) && new Date(req.query.until)) || new Date();
|
|
||||||
let size = Number(req.query.size) || 10;
|
this._loggerAdapter.query(options, (result) => {
|
||||||
let order = req.query.order || 'desc';
|
promise.resolve(result);
|
||||||
let level = req.query.level || INFO;
|
|
||||||
enforceSecurity(req.auth);
|
|
||||||
this._loggerAdapter.query({
|
|
||||||
from,
|
|
||||||
until,
|
|
||||||
size,
|
|
||||||
order,
|
|
||||||
level,
|
|
||||||
}, (result) => {
|
|
||||||
promise.resolve({
|
|
||||||
response: result
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExpressRouter() {
|
|
||||||
let router = new PromiseRouter();
|
|
||||||
router.route('GET','/logs', (req) => {
|
|
||||||
return this.handleGET(req);
|
|
||||||
});
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoggerController;
|
export default LoggerController;
|
||||||
|
|||||||
@@ -6,138 +6,85 @@ export class PushController {
|
|||||||
|
|
||||||
constructor(pushAdapter) {
|
constructor(pushAdapter) {
|
||||||
this._pushAdapter = pushAdapter;
|
this._pushAdapter = pushAdapter;
|
||||||
}
|
};
|
||||||
|
|
||||||
handlePOST(req) {
|
/**
|
||||||
if (!this._pushAdapter) {
|
* Check whether the deviceType parameter in qury condition is valid or not.
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
* @param {Object} where A query condition
|
||||||
'Push adapter is not availabe');
|
* @param {Array} validPushTypes An array of valid push types(string)
|
||||||
|
*/
|
||||||
|
static validatePushType(where = {}, validPushTypes = []) {
|
||||||
|
var deviceTypeField = where.deviceType || {};
|
||||||
|
var deviceTypes = [];
|
||||||
|
if (typeof deviceTypeField === 'string') {
|
||||||
|
deviceTypes.push(deviceTypeField);
|
||||||
|
} else if (typeof deviceTypeField['$in'] === 'array') {
|
||||||
|
deviceTypes.concat(deviceTypeField['$in']);
|
||||||
}
|
}
|
||||||
|
for (var i = 0; i < deviceTypes.length; i++) {
|
||||||
validateMasterKey(req);
|
var deviceType = deviceTypes[i];
|
||||||
var where = getQueryCondition(req);
|
if (validPushTypes.indexOf(deviceType) < 0) {
|
||||||
var pushAdapter = this._pushAdapter;
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
validatePushType(where, pushAdapter.getValidPushTypes());
|
deviceType + ' is not supported push type.');
|
||||||
// Replace the expiration_time with a valid Unix epoch milliseconds time
|
|
||||||
req.body['expiration_time'] = getExpirationTime(req);
|
|
||||||
// TODO: If the req can pass the checking, we return immediately instead of waiting
|
|
||||||
// pushes to be sent. We probably change this behaviour in the future.
|
|
||||||
rest.find(req.config, req.auth, '_Installation', where).then(function(response) {
|
|
||||||
return pushAdapter.send(req.body, response.results);
|
|
||||||
});
|
|
||||||
return Parse.Promise.as({
|
|
||||||
response: {
|
|
||||||
'result': true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static getExpressRouter() {
|
|
||||||
var router = new PromiseRouter();
|
|
||||||
router.route('POST','/push', (req) => {
|
|
||||||
return req.config.pushController.handlePOST(req);
|
|
||||||
});
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the deviceType parameter in qury condition is valid or not.
|
|
||||||
* @param {Object} where A query condition
|
|
||||||
* @param {Array} validPushTypes An array of valid push types(string)
|
|
||||||
*/
|
|
||||||
function validatePushType(where, validPushTypes) {
|
|
||||||
var where = where || {};
|
|
||||||
var deviceTypeField = where.deviceType || {};
|
|
||||||
var deviceTypes = [];
|
|
||||||
if (typeof deviceTypeField === 'string') {
|
|
||||||
deviceTypes.push(deviceTypeField);
|
|
||||||
} else if (typeof deviceTypeField['$in'] === 'array') {
|
|
||||||
deviceTypes.concat(deviceTypeField['$in']);
|
|
||||||
}
|
|
||||||
for (var i = 0; i < deviceTypes.length; i++) {
|
|
||||||
var deviceType = deviceTypes[i];
|
|
||||||
if (validPushTypes.indexOf(deviceType) < 0) {
|
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
deviceType + ' is not supported push type.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get expiration time from the request body.
|
|
||||||
* @param {Object} request A request object
|
|
||||||
* @returns {Number|undefined} The expiration time if it exists in the request
|
|
||||||
*/
|
|
||||||
function getExpirationTime(req) {
|
|
||||||
var body = req.body || {};
|
|
||||||
var hasExpirationTime = !!body['expiration_time'];
|
|
||||||
if (!hasExpirationTime) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var expirationTimeParam = body['expiration_time'];
|
|
||||||
var expirationTime;
|
|
||||||
if (typeof expirationTimeParam === 'number') {
|
|
||||||
expirationTime = new Date(expirationTimeParam * 1000);
|
|
||||||
} else if (typeof expirationTimeParam === 'string') {
|
|
||||||
expirationTime = new Date(expirationTimeParam);
|
|
||||||
} else {
|
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
body['expiration_time'] + ' is not valid time.');
|
|
||||||
}
|
|
||||||
// Check expirationTime is valid or not, if it is not valid, expirationTime is NaN
|
|
||||||
if (!isFinite(expirationTime)) {
|
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
body['expiration_time'] + ' is not valid time.');
|
|
||||||
}
|
|
||||||
return expirationTime.valueOf();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get query condition from the request body.
|
|
||||||
* @param {Object} request A request object
|
|
||||||
* @returns {Object} The query condition, the where field in a query api call
|
|
||||||
*/
|
|
||||||
function getQueryCondition(req) {
|
|
||||||
var body = req.body || {};
|
|
||||||
var hasWhere = typeof body.where !== 'undefined';
|
|
||||||
var hasChannels = typeof body.channels !== 'undefined';
|
|
||||||
|
|
||||||
var where;
|
|
||||||
if (hasWhere && hasChannels) {
|
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
'Channels and query can not be set at the same time.');
|
|
||||||
} else if (hasWhere) {
|
|
||||||
where = body.where;
|
|
||||||
} else if (hasChannels) {
|
|
||||||
where = {
|
|
||||||
"channels": {
|
|
||||||
"$in": body.channels
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
|
||||||
'Channels and query should be set at least one.');
|
/**
|
||||||
|
* Check whether the api call has master key or not.
|
||||||
|
* @param {Object} request A request object
|
||||||
|
*/
|
||||||
|
static validateMasterKey(auth = {}) {
|
||||||
|
if (!auth.isMaster) {
|
||||||
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
|
'Master key is invalid, you should only use master key to send push');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return where;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
sendPush(body = {}, where = {}, config, auth) {
|
||||||
* Check whether the api call has master key or not.
|
var pushAdapter = this._pushAdapter;
|
||||||
* @param {Object} request A request object
|
if (!pushAdapter) {
|
||||||
*/
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
function validateMasterKey(req) {
|
'Push adapter is not available');
|
||||||
if (req.info.masterKey !== req.config.masterKey) {
|
}
|
||||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
PushController.validateMasterKey(auth);
|
||||||
'Master key is invalid, you should only use master key to send push');
|
|
||||||
}
|
PushController.validatePushType(where, pushAdapter.getValidPushTypes());
|
||||||
}
|
// Replace the expiration_time with a valid Unix epoch milliseconds time
|
||||||
|
body['expiration_time'] = PushController.getExpirationTime(body);
|
||||||
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
// TODO: If the req can pass the checking, we return immediately instead of waiting
|
||||||
PushController.getQueryCondition = getQueryCondition;
|
// pushes to be sent. We probably change this behaviour in the future.
|
||||||
PushController.validateMasterKey = validateMasterKey;
|
rest.find(config, auth, '_Installation', where).then(function(response) {
|
||||||
PushController.getExpirationTime = getExpirationTime;
|
return pushAdapter.send(body, response.results);
|
||||||
PushController.validatePushType = validatePushType;
|
});
|
||||||
}
|
};
|
||||||
|
/**
|
||||||
|
* Get expiration time from the request body.
|
||||||
|
* @param {Object} request A request object
|
||||||
|
* @returns {Number|undefined} The expiration time if it exists in the request
|
||||||
|
*/
|
||||||
|
static getExpirationTime(body = {}) {
|
||||||
|
var hasExpirationTime = !!body['expiration_time'];
|
||||||
|
if (!hasExpirationTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var expirationTimeParam = body['expiration_time'];
|
||||||
|
var expirationTime;
|
||||||
|
if (typeof expirationTimeParam === 'number') {
|
||||||
|
expirationTime = new Date(expirationTimeParam * 1000);
|
||||||
|
} else if (typeof expirationTimeParam === 'string') {
|
||||||
|
expirationTime = new Date(expirationTimeParam);
|
||||||
|
} else {
|
||||||
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
|
body['expiration_time'] + ' is not valid time.');
|
||||||
|
}
|
||||||
|
// Check expirationTime is valid or not, if it is not valid, expirationTime is NaN
|
||||||
|
if (!isFinite(expirationTime)) {
|
||||||
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
|
body['expiration_time'] + ' is not valid time.');
|
||||||
|
}
|
||||||
|
return expirationTime.valueOf();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default PushController;
|
export default PushController;
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
// AnalyticsRouter.js
|
// AnalyticsRouter.js
|
||||||
|
|
||||||
var Parse = require('parse/node').Parse;
|
|
||||||
|
|
||||||
import PromiseRouter from '../PromiseRouter';
|
import PromiseRouter from '../PromiseRouter';
|
||||||
|
|
||||||
// Returns a promise that resolves to an empty object response
|
// Returns a promise that resolves to an empty object response
|
||||||
|
|||||||
104
src/Routers/FilesRouter.js
Normal file
104
src/Routers/FilesRouter.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import PromiseRouter from '../PromiseRouter';
|
||||||
|
import express from 'express';
|
||||||
|
import BodyParser from 'body-parser';
|
||||||
|
import * as Middlewares from '../middlewares';
|
||||||
|
import { randomHexString } from '../cryptoUtils';
|
||||||
|
import mime from 'mime';
|
||||||
|
import Config from '../Config';
|
||||||
|
|
||||||
|
export class FilesRouter {
|
||||||
|
|
||||||
|
getExpressRouter() {
|
||||||
|
var router = express.Router();
|
||||||
|
router.get('/files/:appId/:filename', this.getHandler);
|
||||||
|
|
||||||
|
router.post('/files', function(req, res, next) {
|
||||||
|
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
|
||||||
|
'Filename not provided.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/files/:filename',
|
||||||
|
Middlewares.allowCrossDomain,
|
||||||
|
BodyParser.raw({type: '*/*', limit: '20mb'}),
|
||||||
|
Middlewares.handleParseHeaders,
|
||||||
|
this.createHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete('/files/:filename',
|
||||||
|
Middlewares.allowCrossDomain,
|
||||||
|
Middlewares.handleParseHeaders,
|
||||||
|
Middlewares.enforceMasterKeyAccess,
|
||||||
|
this.deleteHandler
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHandler(req, res, next) {
|
||||||
|
const config = new Config(req.params.appId);
|
||||||
|
const filesController = config.filesController;
|
||||||
|
const filename = req.params.filename;
|
||||||
|
filesController.getFileData(config, filename).then((data) => {
|
||||||
|
res.status(200);
|
||||||
|
var contentType = mime.lookup(filename);
|
||||||
|
res.set('Content-type', contentType);
|
||||||
|
res.end(data);
|
||||||
|
}).catch((error) => {
|
||||||
|
res.status(404);
|
||||||
|
res.set('Content-type', 'text/plain');
|
||||||
|
res.end('File not found.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createHandler(req, res, next) {
|
||||||
|
if (!req.body || !req.body.length) {
|
||||||
|
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
|
||||||
|
'Invalid file upload.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.params.filename.length > 128) {
|
||||||
|
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
|
||||||
|
'Filename too long.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.params.filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
|
||||||
|
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
|
||||||
|
'Filename contains invalid characters.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let extension = '';
|
||||||
|
|
||||||
|
// Not very safe there.
|
||||||
|
const hasExtension = req.params.filename.indexOf('.') > 0;
|
||||||
|
const contentType = req.get('Content-type');
|
||||||
|
if (!hasExtension && contentType && mime.extension(contentType)) {
|
||||||
|
extension = '.' + mime.extension(contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = req.params.filename + extension;
|
||||||
|
const config = req.config;
|
||||||
|
const filesController = config.filesController;
|
||||||
|
|
||||||
|
filesController.createFile(config, filename, req.body).then((result) => {
|
||||||
|
res.status(201);
|
||||||
|
res.set('Location', result.url);
|
||||||
|
res.json(result);
|
||||||
|
}).catch((err) => {
|
||||||
|
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
|
||||||
|
'Could not store file.'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteHandler(req, res, next) {
|
||||||
|
const filesController = req.config.filesController;
|
||||||
|
filesController.deleteFile(req.config, req.params.filename).then(() => {
|
||||||
|
res.status(200);
|
||||||
|
// TODO: return useful JSON here?
|
||||||
|
res.end();
|
||||||
|
}).catch((error) => {
|
||||||
|
next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR,
|
||||||
|
'Could not delete file.'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/Routers/LogsRouter.js
Normal file
60
src/Routers/LogsRouter.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Parse } from 'parse/node';
|
||||||
|
import PromiseRouter from '../PromiseRouter';
|
||||||
|
|
||||||
|
// only allow request with master key
|
||||||
|
let enforceSecurity = (auth) => {
|
||||||
|
if (!auth || !auth.isMaster) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.OPERATION_FORBIDDEN,
|
||||||
|
'Clients aren\'t allowed to perform the ' +
|
||||||
|
'get' + ' operation on logs.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LogsRouter extends PromiseRouter {
|
||||||
|
|
||||||
|
mountRoutes() {
|
||||||
|
this.route('GET','/logs', (req) => {
|
||||||
|
return this.handleGET(req);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a promise for a {response} object.
|
||||||
|
// query params:
|
||||||
|
// level (optional) Level of logging you want to query for (info || error)
|
||||||
|
// from (optional) Start time for the search. Defaults to 1 week ago.
|
||||||
|
// until (optional) End time for the search. Defaults to current time.
|
||||||
|
// order (optional) Direction of results returned, either “asc” or “desc”. Defaults to “desc”.
|
||||||
|
// size (optional) Number of rows returned by search. Defaults to 10
|
||||||
|
handleGET(req) {
|
||||||
|
if (!req.config || !req.config.loggerController) {
|
||||||
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
|
'Logger adapter is not availabe');
|
||||||
|
}
|
||||||
|
|
||||||
|
let promise = new Parse.Promise();
|
||||||
|
let from = req.query.from;
|
||||||
|
let until = req.query.until;
|
||||||
|
let size = req.query.size;
|
||||||
|
let order = req.query.order
|
||||||
|
let level = req.query.level;
|
||||||
|
enforceSecurity(req.auth);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
from,
|
||||||
|
until,
|
||||||
|
size,
|
||||||
|
order,
|
||||||
|
level,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req.config.loggerController.getLogs(options).then((result) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
response: result
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogsRouter;
|
||||||
72
src/Routers/PushRouter.js
Normal file
72
src/Routers/PushRouter.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import PushController from '../Controllers/PushController'
|
||||||
|
import PromiseRouter from '../PromiseRouter';
|
||||||
|
|
||||||
|
export class PushRouter extends PromiseRouter {
|
||||||
|
|
||||||
|
mountRoutes() {
|
||||||
|
this.route("POST", "/push", req => { return this.handlePOST(req); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the api call has master key or not.
|
||||||
|
* @param {Object} request A request object
|
||||||
|
*/
|
||||||
|
static validateMasterKey(req) {
|
||||||
|
if (req.info.masterKey !== req.config.masterKey) {
|
||||||
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
|
'Master key is invalid, you should only use master key to send push');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePOST(req) {
|
||||||
|
// TODO: move to middlewares when support for Promise middlewares
|
||||||
|
PushRouter.validateMasterKey(req);
|
||||||
|
|
||||||
|
const pushController = req.config.pushController;
|
||||||
|
if (!pushController) {
|
||||||
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
|
'Push controller is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
var where = PushRouter.getQueryCondition(req);
|
||||||
|
|
||||||
|
pushController.sendPush(req.body, where, req.config, req.auth);
|
||||||
|
return Promise.resolve({
|
||||||
|
response: {
|
||||||
|
'result': true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query condition from the request body.
|
||||||
|
* @param {Object} request A request object
|
||||||
|
* @returns {Object} The query condition, the where field in a query api call
|
||||||
|
*/
|
||||||
|
static getQueryCondition(req) {
|
||||||
|
var body = req.body || {};
|
||||||
|
var hasWhere = typeof body.where !== 'undefined';
|
||||||
|
var hasChannels = typeof body.channels !== 'undefined';
|
||||||
|
|
||||||
|
var where;
|
||||||
|
if (hasWhere && hasChannels) {
|
||||||
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
|
'Channels and query can not be set at the same time.');
|
||||||
|
} else if (hasWhere) {
|
||||||
|
where = body.where;
|
||||||
|
} else if (hasChannels) {
|
||||||
|
where = {
|
||||||
|
"channels": {
|
||||||
|
"$in": body.channels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||||
|
'Channels and query should be set at least one.');
|
||||||
|
}
|
||||||
|
return where;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PushRouter;
|
||||||
17
src/index.js
17
src/index.js
@@ -18,6 +18,7 @@ import { FilesController } from './Controllers/FilesController';
|
|||||||
import ParsePushAdapter from './Adapters/Push/ParsePushAdapter';
|
import ParsePushAdapter from './Adapters/Push/ParsePushAdapter';
|
||||||
import { PushController } from './Controllers/PushController';
|
import { PushController } from './Controllers/PushController';
|
||||||
|
|
||||||
|
|
||||||
import { ClassesRouter } from './Routers/ClassesRouter';
|
import { ClassesRouter } from './Routers/ClassesRouter';
|
||||||
import { InstallationsRouter } from './Routers/InstallationsRouter';
|
import { InstallationsRouter } from './Routers/InstallationsRouter';
|
||||||
import { UsersRouter } from './Routers/UsersRouter';
|
import { UsersRouter } from './Routers/UsersRouter';
|
||||||
@@ -27,7 +28,9 @@ import { AnalyticsRouter } from './Routers/AnalyticsRouter';
|
|||||||
import { FunctionsRouter } from './Routers/FunctionsRouter';
|
import { FunctionsRouter } from './Routers/FunctionsRouter';
|
||||||
import { SchemasRouter } from './Routers/SchemasRouter';
|
import { SchemasRouter } from './Routers/SchemasRouter';
|
||||||
import { IAPValidationRouter } from './Routers/IAPValidationRouter';
|
import { IAPValidationRouter } from './Routers/IAPValidationRouter';
|
||||||
|
import { PushRouter } from './Routers/PushRouter';
|
||||||
|
import { FilesRouter } from './Routers/FilesRouter';
|
||||||
|
import { LogsRouter } from './Routers/LogsRouter';
|
||||||
|
|
||||||
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
||||||
import { LoggerController } from './Controllers/LoggerController';
|
import { LoggerController } from './Controllers/LoggerController';
|
||||||
@@ -110,7 +113,9 @@ function ParseServer({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let filesController = new FilesController(filesAdapter);
|
const filesController = new FilesController(filesAdapter);
|
||||||
|
const pushController = new PushController(pushAdapter);
|
||||||
|
const loggerController = new LoggerController(loggerAdapter);
|
||||||
|
|
||||||
cache.apps[appId] = {
|
cache.apps[appId] = {
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
@@ -122,6 +127,8 @@ function ParseServer({
|
|||||||
fileKey: fileKey,
|
fileKey: fileKey,
|
||||||
facebookAppIds: facebookAppIds,
|
facebookAppIds: facebookAppIds,
|
||||||
filesController: filesController,
|
filesController: filesController,
|
||||||
|
pushController: pushController,
|
||||||
|
loggerController: loggerController,
|
||||||
enableAnonymousUsers: enableAnonymousUsers,
|
enableAnonymousUsers: enableAnonymousUsers,
|
||||||
oauth: oauth,
|
oauth: oauth,
|
||||||
};
|
};
|
||||||
@@ -140,7 +147,7 @@ function ParseServer({
|
|||||||
var api = express();
|
var api = express();
|
||||||
|
|
||||||
// File handling needs to be before default middlewares are applied
|
// File handling needs to be before default middlewares are applied
|
||||||
api.use('/', FilesController.getExpressRouter());
|
api.use('/', new FilesRouter().getExpressRouter());
|
||||||
|
|
||||||
// TODO: separate this from the regular ParseServer object
|
// TODO: separate this from the regular ParseServer object
|
||||||
if (process.env.TESTING == 1) {
|
if (process.env.TESTING == 1) {
|
||||||
@@ -161,8 +168,8 @@ function ParseServer({
|
|||||||
new InstallationsRouter(),
|
new InstallationsRouter(),
|
||||||
new FunctionsRouter(),
|
new FunctionsRouter(),
|
||||||
new SchemasRouter(),
|
new SchemasRouter(),
|
||||||
PushController.getExpressRouter(),
|
new PushRouter(),
|
||||||
new LoggerController(loggerAdapter).getExpressRouter(),
|
new LogsRouter(),
|
||||||
new IAPValidationRouter()
|
new IAPValidationRouter()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user