Skip to content

Refactors PushController and FilesController to support multiple apps #501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ function Config(applicationId, mount) {

this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
this.filesController = cacheInfo.filesController;

this.pushController = cacheInfo.pushController;
this.oauth = cacheInfo.oauth;

this.mount = mount;
}

Expand Down
34 changes: 28 additions & 6 deletions src/Controllers/FilesController.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export class FilesController {
this._filesAdapter = filesAdapter;
}

static getHandler() {
return (req, res) => {
let config = new Config(req.params.appId);
return config.filesController.getHandler()(req, res);
}
}

getHandler() {
return (req, res) => {
let config = new Config(req.params.appId);
Expand All @@ -30,6 +37,13 @@ export class FilesController {
};
}

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) {
Expand All @@ -50,6 +64,7 @@ export class FilesController {
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 = '';
Expand All @@ -60,9 +75,9 @@ export class FilesController {
}

let filename = randomHexString(32) + '_' + req.params.filename + extension;
this._filesAdapter.createFile(req.config, filename, req.body).then(() => {
filesController._filesAdapter.createFile(req.config, filename, req.body).then(() => {
res.status(201);
var location = this._filesAdapter.getFileLocation(req.config, filename);
var location = filesController._filesAdapter.getFileLocation(req.config, filename);
res.set('Location', location);
res.json({ url: location, name: filename });
}).catch((error) => {
Expand All @@ -72,6 +87,13 @@ export class FilesController {
};
}

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(() => {
Expand Down Expand Up @@ -114,9 +136,9 @@ export class FilesController {
}
}

getExpressRouter() {
static getExpressRouter() {
let router = express.Router();
router.get('/files/:appId/:filename', this.getHandler());
router.get('/files/:appId/:filename', FilesController.getHandler());

router.post('/files', function(req, res, next) {
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
Expand All @@ -127,14 +149,14 @@ export class FilesController {
Middlewares.allowCrossDomain,
BodyParser.raw({type: '*/*', limit: '20mb'}),
Middlewares.handleParseHeaders,
this.createHandler()
FilesController.createHandler()
);

router.delete('/files/:filename',
Middlewares.allowCrossDomain,
Middlewares.handleParseHeaders,
Middlewares.enforceMasterKeyAccess,
this.deleteHandler()
FilesController.deleteHandler()
);

return router;
Expand Down
6 changes: 3 additions & 3 deletions src/Controllers/PushController.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ export class PushController {
}
});
}

getExpressRouter() {
static getExpressRouter() {
var router = new PromiseRouter();
router.route('POST','/push', (req) => {
return this.handlePOST(req);
return req.config.pushController.handlePOST(req);
});
return router;
}
Expand Down
173 changes: 89 additions & 84 deletions src/PromiseRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// themselves use our routing information, without disturbing express
// components that external developers may be modifying.

function PromiseRouter() {
export default class PromiseRouter {
// Each entry should be an object with:
// path: the path to route, in express format
// method: the HTTP method that this route handles.
Expand All @@ -15,73 +15,102 @@ function PromiseRouter() {
// status: optional. the http status code. defaults to 200
// response: a json object with the content of the response
// location: optional. a location header
this.routes = [];
}

// Global flag. Set this to true to log every request and response.
PromiseRouter.verbose = process.env.VERBOSE || false;

// Merge the routes into this one
PromiseRouter.prototype.merge = function(router) {
for (var route of router.routes) {
this.routes.push(route);
}
};

PromiseRouter.prototype.route = function(method, path, handler) {
switch(method) {
case 'POST':
case 'GET':
case 'PUT':
case 'DELETE':
break;
default:
throw 'cannot route method: ' + method;
constructor() {
this.routes = [];
this.mountRoutes();
}

// Leave the opportunity to
// subclasses to mount their routes by overriding
mountRoutes() {}

// Merge the routes into this one
merge(router) {
for (var route of router.routes) {
this.routes.push(route);
}
};

route(method, path, handler) {
switch(method) {
case 'POST':
case 'GET':
case 'PUT':
case 'DELETE':
break;
default:
throw 'cannot route method: ' + method;
}

this.routes.push({
path: path,
method: method,
handler: handler
});
};
this.routes.push({
path: path,
method: method,
handler: handler
});
};

// Returns an object with:
// handler: the handler that should deal with this request
// params: any :-params that got parsed from the path
// Returns undefined if there is no match.
match(method, path) {
for (var route of this.routes) {
if (route.method != method) {
continue;
}

// Returns an object with:
// handler: the handler that should deal with this request
// params: any :-params that got parsed from the path
// Returns undefined if there is no match.
PromiseRouter.prototype.match = function(method, path) {
for (var route of this.routes) {
if (route.method != method) {
continue;
}
// NOTE: we can only route the specific wildcards :className and
// :objectId, and in that order.
// This is pretty hacky but I don't want to rebuild the entire
// express route matcher. Maybe there's a way to reuse its logic.
var pattern = '^' + route.path + '$';

// NOTE: we can only route the specific wildcards :className and
// :objectId, and in that order.
// This is pretty hacky but I don't want to rebuild the entire
// express route matcher. Maybe there's a way to reuse its logic.
var pattern = '^' + route.path + '$';
pattern = pattern.replace(':className',
'(_?[A-Za-z][A-Za-z_0-9]*)');
pattern = pattern.replace(':objectId',
'([A-Za-z0-9]+)');
var re = new RegExp(pattern);
var m = path.match(re);
if (!m) {
continue;
}
var params = {};
if (m[1]) {
params.className = m[1];
}
if (m[2]) {
params.objectId = m[2];
}

pattern = pattern.replace(':className',
'(_?[A-Za-z][A-Za-z_0-9]*)');
pattern = pattern.replace(':objectId',
'([A-Za-z0-9]+)');
var re = new RegExp(pattern);
var m = path.match(re);
if (!m) {
continue;
return {params: params, handler: route.handler};
}
var params = {};
if (m[1]) {
params.className = m[1];
}
if (m[2]) {
params.objectId = m[2];
};

// Mount the routes on this router onto an express app (or express router)
mountOnto(expressApp) {
for (var route of this.routes) {
switch(route.method) {
case 'POST':
expressApp.post(route.path, makeExpressHandler(route.handler));
break;
case 'GET':
expressApp.get(route.path, makeExpressHandler(route.handler));
break;
case 'PUT':
expressApp.put(route.path, makeExpressHandler(route.handler));
break;
case 'DELETE':
expressApp.delete(route.path, makeExpressHandler(route.handler));
break;
default:
throw 'unexpected code branch';
}
}
};
}

return {params: params, handler: route.handler};
}
};
// Global flag. Set this to true to log every request and response.
PromiseRouter.verbose = process.env.VERBOSE || false;

// A helper function to make an express handler out of a a promise
// handler.
Expand Down Expand Up @@ -122,27 +151,3 @@ function makeExpressHandler(promiseHandler) {
}
}
}

// Mount the routes on this router onto an express app (or express router)
PromiseRouter.prototype.mountOnto = function(expressApp) {
for (var route of this.routes) {
switch(route.method) {
case 'POST':
expressApp.post(route.path, makeExpressHandler(route.handler));
break;
case 'GET':
expressApp.get(route.path, makeExpressHandler(route.handler));
break;
case 'PUT':
expressApp.put(route.path, makeExpressHandler(route.handler));
break;
case 'DELETE':
expressApp.delete(route.path, makeExpressHandler(route.handler));
break;
default:
throw 'unexpected code branch';
}
}
};

module.exports = PromiseRouter;
20 changes: 20 additions & 0 deletions src/Routers/AnalyticsRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// AnalyticsRouter.js

var Parse = require('parse/node').Parse;

import PromiseRouter from '../PromiseRouter';

// Returns a promise that resolves to an empty object response
function ignoreAndSucceed(req) {
return Promise.resolve({
response: {}
});
}


export class AnalyticsRouter extends PromiseRouter {
mountRoutes() {
this.route('POST','/events/AppOpened', ignoreAndSucceed);
this.route('POST','/events/:eventName', ignoreAndSucceed);
}
}
20 changes: 9 additions & 11 deletions src/Routers/ClassesRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import rest from '../rest';

import url from 'url';

export class ClassesRouter {
// Returns a promise that resolves to a {response} object.
export class ClassesRouter extends PromiseRouter {

handleFind(req) {
let body = Object.assign(req.body, req.query);
let options = {};
Expand Down Expand Up @@ -97,15 +97,13 @@ export class ClassesRouter {
return {response: {}};
});
}

getExpressRouter() {
var router = new PromiseRouter();
router.route('GET', '/classes/:className', (req) => { return this.handleFind(req); });
router.route('GET', '/classes/:className/:objectId', (req) => { return this.handleGet(req); });
router.route('POST', '/classes/:className', (req) => { return this.handleCreate(req); });
router.route('PUT', '/classes/:className/:objectId', (req) => { return this.handleUpdate(req); });
router.route('DELETE', '/classes/:className/:objectId', (req) => { return this.handleDelete(req); });
return router;

mountRoutes() {
this.route('GET', '/classes/:className', (req) => { return this.handleFind(req); });
this.route('GET', '/classes/:className/:objectId', (req) => { return this.handleGet(req); });
this.route('POST', '/classes/:className', (req) => { return this.handleCreate(req); });
this.route('PUT', '/classes/:className/:objectId', (req) => { return this.handleUpdate(req); });
this.route('DELETE', '/classes/:className/:objectId', (req) => { return this.handleDelete(req); });
}
}

Expand Down
Loading