Merge pull request #534 from flovilmart/refactor-to-routers
Refactors Controllers to split Controllers and Routers
This commit is contained in:
@@ -1,34 +1,38 @@
|
||||
// A Config object provides information about how a specific app is
|
||||
// configured.
|
||||
// mount is the URL for the root of the API; includes http, domain, etc.
|
||||
function Config(applicationId, mount) {
|
||||
var cache = require('./cache');
|
||||
var DatabaseAdapter = require('./DatabaseAdapter');
|
||||
export class Config {
|
||||
|
||||
var cacheInfo = cache.apps[applicationId];
|
||||
this.valid = !!cacheInfo;
|
||||
if (!this.valid) {
|
||||
return;
|
||||
constructor(applicationId, mount) {
|
||||
var cache = require('./cache');
|
||||
var DatabaseAdapter = require('./DatabaseAdapter');
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
export default Config;
|
||||
module.exports = Config;
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
// FilesController.js
|
||||
|
||||
import express from 'express';
|
||||
import mime from 'mime';
|
||||
import { Parse } from 'parse/node';
|
||||
import BodyParser from 'body-parser';
|
||||
import * as Middlewares from '../middlewares';
|
||||
import Config from '../Config';
|
||||
import { randomHexString } from '../cryptoUtils';
|
||||
|
||||
export class FilesController {
|
||||
@@ -13,98 +7,23 @@ export class FilesController {
|
||||
this._filesAdapter = filesAdapter;
|
||||
}
|
||||
|
||||
static getHandler() {
|
||||
return (req, res) => {
|
||||
let config = new Config(req.params.appId);
|
||||
return config.filesController.getHandler()(req, res);
|
||||
}
|
||||
getFileData(config, filename) {
|
||||
return this._filesAdapter.getFileData(config, filename);
|
||||
}
|
||||
|
||||
getHandler() {
|
||||
return (req, res) => {
|
||||
let config = new Config(req.params.appId);
|
||||
let filename = req.params.filename;
|
||||
this._filesAdapter.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.');
|
||||
|
||||
createFile(config, filename, data) {
|
||||
filename = randomHexString(32) + '_' + filename;
|
||||
var location = this._filesAdapter.getFileLocation(config, filename);
|
||||
return this._filesAdapter.createFile(config, filename, data).then(() => {
|
||||
return Promise.resolve({
|
||||
url: location,
|
||||
name: filename
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static createHandler() {
|
||||
return (req, res, next) => {
|
||||
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.'));
|
||||
});
|
||||
};
|
||||
deleteFile(config, filename) {
|
||||
return this._filesAdapter.deleteFile(config, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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;
|
||||
|
||||
@@ -1,35 +1,55 @@
|
||||
import { Parse } from 'parse/node';
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import rest from '../rest';
|
||||
|
||||
const Promise = Parse.Promise;
|
||||
const INFO = 'info';
|
||||
const ERROR = 'error';
|
||||
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
// 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 const LogLevel = {
|
||||
INFO: 'info',
|
||||
ERROR: 'error'
|
||||
}
|
||||
|
||||
// check that date input is valid
|
||||
let isValidDateTime = (date) => {
|
||||
if (!date || isNaN(Number(date))) {
|
||||
return false;
|
||||
}
|
||||
export const LogOrder = {
|
||||
DESCENDING: 'desc',
|
||||
ASCENDING: 'asc'
|
||||
}
|
||||
|
||||
export class LoggerController {
|
||||
|
||||
constructor(loggerAdapter) {
|
||||
|
||||
constructor(loggerAdapter, loggerOptions) {
|
||||
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.
|
||||
// query params:
|
||||
@@ -38,41 +58,21 @@ export class LoggerController {
|
||||
// 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) {
|
||||
getLogs(options= {}) {
|
||||
if (!this._loggerAdapter) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Logger adapter is not availabe');
|
||||
}
|
||||
|
||||
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);
|
||||
let until = (isValidDateTime(req.query.until) && new Date(req.query.until)) || new Date();
|
||||
let size = Number(req.query.size) || 10;
|
||||
let order = req.query.order || 'desc';
|
||||
let level = req.query.level || INFO;
|
||||
enforceSecurity(req.auth);
|
||||
this._loggerAdapter.query({
|
||||
from,
|
||||
until,
|
||||
size,
|
||||
order,
|
||||
level,
|
||||
}, (result) => {
|
||||
promise.resolve({
|
||||
response: result
|
||||
});
|
||||
|
||||
options = LoggerController.parseOptions(options);
|
||||
|
||||
this._loggerAdapter.query(options, (result) => {
|
||||
promise.resolve(result);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
getExpressRouter() {
|
||||
let router = new PromiseRouter();
|
||||
router.route('GET','/logs', (req) => {
|
||||
return this.handleGET(req);
|
||||
});
|
||||
return router;
|
||||
}
|
||||
}
|
||||
|
||||
export default LoggerController;
|
||||
|
||||
@@ -6,138 +6,85 @@ export class PushController {
|
||||
|
||||
constructor(pushAdapter) {
|
||||
this._pushAdapter = pushAdapter;
|
||||
}
|
||||
};
|
||||
|
||||
handlePOST(req) {
|
||||
if (!this._pushAdapter) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Push adapter is not availabe');
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
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']);
|
||||
}
|
||||
|
||||
validateMasterKey(req);
|
||||
var where = getQueryCondition(req);
|
||||
var pushAdapter = this._pushAdapter;
|
||||
validatePushType(where, pushAdapter.getValidPushTypes());
|
||||
// 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
|
||||
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.');
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the api call has master key or not.
|
||||
* @param {Object} request A request object
|
||||
*/
|
||||
function 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');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
||||
PushController.getQueryCondition = getQueryCondition;
|
||||
PushController.validateMasterKey = validateMasterKey;
|
||||
PushController.getExpirationTime = getExpirationTime;
|
||||
PushController.validatePushType = validatePushType;
|
||||
}
|
||||
sendPush(body = {}, where = {}, config, auth) {
|
||||
var pushAdapter = this._pushAdapter;
|
||||
if (!pushAdapter) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Push adapter is not available');
|
||||
}
|
||||
PushController.validateMasterKey(auth);
|
||||
|
||||
PushController.validatePushType(where, pushAdapter.getValidPushTypes());
|
||||
// Replace the expiration_time with a valid Unix epoch milliseconds time
|
||||
body['expiration_time'] = PushController.getExpirationTime(body);
|
||||
// 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(config, auth, '_Installation', where).then(function(response) {
|
||||
return pushAdapter.send(body, response.results);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// AnalyticsRouter.js
|
||||
|
||||
var Parse = require('parse/node').Parse;
|
||||
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
|
||||
// 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 { PushController } from './Controllers/PushController';
|
||||
|
||||
|
||||
import { ClassesRouter } from './Routers/ClassesRouter';
|
||||
import { InstallationsRouter } from './Routers/InstallationsRouter';
|
||||
import { UsersRouter } from './Routers/UsersRouter';
|
||||
@@ -27,7 +28,9 @@ import { AnalyticsRouter } from './Routers/AnalyticsRouter';
|
||||
import { FunctionsRouter } from './Routers/FunctionsRouter';
|
||||
import { SchemasRouter } from './Routers/SchemasRouter';
|
||||
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 { 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] = {
|
||||
masterKey: masterKey,
|
||||
@@ -122,6 +127,8 @@ function ParseServer({
|
||||
fileKey: fileKey,
|
||||
facebookAppIds: facebookAppIds,
|
||||
filesController: filesController,
|
||||
pushController: pushController,
|
||||
loggerController: loggerController,
|
||||
enableAnonymousUsers: enableAnonymousUsers,
|
||||
oauth: oauth,
|
||||
};
|
||||
@@ -140,7 +147,7 @@ function ParseServer({
|
||||
var api = express();
|
||||
|
||||
// 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
|
||||
if (process.env.TESTING == 1) {
|
||||
@@ -161,8 +168,8 @@ function ParseServer({
|
||||
new InstallationsRouter(),
|
||||
new FunctionsRouter(),
|
||||
new SchemasRouter(),
|
||||
PushController.getExpressRouter(),
|
||||
new LoggerController(loggerAdapter).getExpressRouter(),
|
||||
new PushRouter(),
|
||||
new LogsRouter(),
|
||||
new IAPValidationRouter()
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user