Skip to content

Commit

Permalink
Merge pull request #501 from flovilmart/app-agnostic-controllers
Browse files Browse the repository at this point in the history
Refactors PushController and FilesController to support multiple apps
  • Loading branch information
nlutsenko committed Feb 20, 2016
2 parents 8aaf8f2 + 3c4d515 commit eb03405
Show file tree
Hide file tree
Showing 17 changed files with 310 additions and 277 deletions.
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

0 comments on commit eb03405

Please sign in to comment.