Skip to content

Commit 3c4d515

Browse files
committed
Refactors routers
1 parent 067946c commit 3c4d515

14 files changed

+273
-263
lines changed

src/PromiseRouter.js

Lines changed: 89 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// themselves use our routing information, without disturbing express
66
// components that external developers may be modifying.
77

8-
function PromiseRouter() {
8+
export default class PromiseRouter {
99
// Each entry should be an object with:
1010
// path: the path to route, in express format
1111
// method: the HTTP method that this route handles.
@@ -15,73 +15,102 @@ function PromiseRouter() {
1515
// status: optional. the http status code. defaults to 200
1616
// response: a json object with the content of the response
1717
// location: optional. a location header
18-
this.routes = [];
19-
}
20-
21-
// Global flag. Set this to true to log every request and response.
22-
PromiseRouter.verbose = process.env.VERBOSE || false;
23-
24-
// Merge the routes into this one
25-
PromiseRouter.prototype.merge = function(router) {
26-
for (var route of router.routes) {
27-
this.routes.push(route);
28-
}
29-
};
30-
31-
PromiseRouter.prototype.route = function(method, path, handler) {
32-
switch(method) {
33-
case 'POST':
34-
case 'GET':
35-
case 'PUT':
36-
case 'DELETE':
37-
break;
38-
default:
39-
throw 'cannot route method: ' + method;
18+
constructor() {
19+
this.routes = [];
20+
this.mountRoutes();
4021
}
22+
23+
// Leave the opportunity to
24+
// subclasses to mount their routes by overriding
25+
mountRoutes() {}
26+
27+
// Merge the routes into this one
28+
merge(router) {
29+
for (var route of router.routes) {
30+
this.routes.push(route);
31+
}
32+
};
33+
34+
route(method, path, handler) {
35+
switch(method) {
36+
case 'POST':
37+
case 'GET':
38+
case 'PUT':
39+
case 'DELETE':
40+
break;
41+
default:
42+
throw 'cannot route method: ' + method;
43+
}
4144

42-
this.routes.push({
43-
path: path,
44-
method: method,
45-
handler: handler
46-
});
47-
};
45+
this.routes.push({
46+
path: path,
47+
method: method,
48+
handler: handler
49+
});
50+
};
51+
52+
// Returns an object with:
53+
// handler: the handler that should deal with this request
54+
// params: any :-params that got parsed from the path
55+
// Returns undefined if there is no match.
56+
match(method, path) {
57+
for (var route of this.routes) {
58+
if (route.method != method) {
59+
continue;
60+
}
4861

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

59-
// NOTE: we can only route the specific wildcards :className and
60-
// :objectId, and in that order.
61-
// This is pretty hacky but I don't want to rebuild the entire
62-
// express route matcher. Maybe there's a way to reuse its logic.
63-
var pattern = '^' + route.path + '$';
68+
pattern = pattern.replace(':className',
69+
'(_?[A-Za-z][A-Za-z_0-9]*)');
70+
pattern = pattern.replace(':objectId',
71+
'([A-Za-z0-9]+)');
72+
var re = new RegExp(pattern);
73+
var m = path.match(re);
74+
if (!m) {
75+
continue;
76+
}
77+
var params = {};
78+
if (m[1]) {
79+
params.className = m[1];
80+
}
81+
if (m[2]) {
82+
params.objectId = m[2];
83+
}
6484

65-
pattern = pattern.replace(':className',
66-
'(_?[A-Za-z][A-Za-z_0-9]*)');
67-
pattern = pattern.replace(':objectId',
68-
'([A-Za-z0-9]+)');
69-
var re = new RegExp(pattern);
70-
var m = path.match(re);
71-
if (!m) {
72-
continue;
85+
return {params: params, handler: route.handler};
7386
}
74-
var params = {};
75-
if (m[1]) {
76-
params.className = m[1];
77-
}
78-
if (m[2]) {
79-
params.objectId = m[2];
87+
};
88+
89+
// Mount the routes on this router onto an express app (or express router)
90+
mountOnto(expressApp) {
91+
for (var route of this.routes) {
92+
switch(route.method) {
93+
case 'POST':
94+
expressApp.post(route.path, makeExpressHandler(route.handler));
95+
break;
96+
case 'GET':
97+
expressApp.get(route.path, makeExpressHandler(route.handler));
98+
break;
99+
case 'PUT':
100+
expressApp.put(route.path, makeExpressHandler(route.handler));
101+
break;
102+
case 'DELETE':
103+
expressApp.delete(route.path, makeExpressHandler(route.handler));
104+
break;
105+
default:
106+
throw 'unexpected code branch';
107+
}
80108
}
109+
};
110+
}
81111

82-
return {params: params, handler: route.handler};
83-
}
84-
};
112+
// Global flag. Set this to true to log every request and response.
113+
PromiseRouter.verbose = process.env.VERBOSE || false;
85114

86115
// A helper function to make an express handler out of a a promise
87116
// handler.
@@ -122,27 +151,3 @@ function makeExpressHandler(promiseHandler) {
122151
}
123152
}
124153
}
125-
126-
// Mount the routes on this router onto an express app (or express router)
127-
PromiseRouter.prototype.mountOnto = function(expressApp) {
128-
for (var route of this.routes) {
129-
switch(route.method) {
130-
case 'POST':
131-
expressApp.post(route.path, makeExpressHandler(route.handler));
132-
break;
133-
case 'GET':
134-
expressApp.get(route.path, makeExpressHandler(route.handler));
135-
break;
136-
case 'PUT':
137-
expressApp.put(route.path, makeExpressHandler(route.handler));
138-
break;
139-
case 'DELETE':
140-
expressApp.delete(route.path, makeExpressHandler(route.handler));
141-
break;
142-
default:
143-
throw 'unexpected code branch';
144-
}
145-
}
146-
};
147-
148-
module.exports = PromiseRouter;

src/Routers/AnalyticsRouter.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// AnalyticsRouter.js
2+
3+
var Parse = require('parse/node').Parse;
4+
5+
import PromiseRouter from '../PromiseRouter';
6+
7+
// Returns a promise that resolves to an empty object response
8+
function ignoreAndSucceed(req) {
9+
return Promise.resolve({
10+
response: {}
11+
});
12+
}
13+
14+
15+
export class AnalyticsRouter extends PromiseRouter {
16+
mountRoutes() {
17+
this.route('POST','/events/AppOpened', ignoreAndSucceed);
18+
this.route('POST','/events/:eventName', ignoreAndSucceed);
19+
}
20+
}

src/Routers/ClassesRouter.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import rest from '../rest';
44

55
import url from 'url';
66

7-
export class ClassesRouter {
8-
// Returns a promise that resolves to a {response} object.
7+
export class ClassesRouter extends PromiseRouter {
8+
99
handleFind(req) {
1010
let body = Object.assign(req.body, req.query);
1111
let options = {};
@@ -97,15 +97,13 @@ export class ClassesRouter {
9797
return {response: {}};
9898
});
9999
}
100-
101-
getExpressRouter() {
102-
var router = new PromiseRouter();
103-
router.route('GET', '/classes/:className', (req) => { return this.handleFind(req); });
104-
router.route('GET', '/classes/:className/:objectId', (req) => { return this.handleGet(req); });
105-
router.route('POST', '/classes/:className', (req) => { return this.handleCreate(req); });
106-
router.route('PUT', '/classes/:className/:objectId', (req) => { return this.handleUpdate(req); });
107-
router.route('DELETE', '/classes/:className/:objectId', (req) => { return this.handleDelete(req); });
108-
return router;
100+
101+
mountRoutes() {
102+
this.route('GET', '/classes/:className', (req) => { return this.handleFind(req); });
103+
this.route('GET', '/classes/:className/:objectId', (req) => { return this.handleGet(req); });
104+
this.route('POST', '/classes/:className', (req) => { return this.handleCreate(req); });
105+
this.route('PUT', '/classes/:className/:objectId', (req) => { return this.handleUpdate(req); });
106+
this.route('DELETE', '/classes/:className/:objectId', (req) => { return this.handleDelete(req); });
109107
}
110108
}
111109

src/Routers/FunctionsRouter.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// functions.js
2+
3+
var express = require('express'),
4+
Parse = require('parse/node').Parse;
5+
6+
import PromiseRouter from '../PromiseRouter';
7+
8+
export class FunctionsRouter extends PromiseRouter {
9+
10+
mountRoutes() {
11+
this.route('POST', '/functions/:functionName', FunctionsRouter.handleCloudFunction);
12+
}
13+
14+
static createResponseObject(resolve, reject) {
15+
return {
16+
success: function(result) {
17+
resolve({
18+
response: {
19+
result: Parse._encode(result)
20+
}
21+
});
22+
},
23+
error: function(error) {
24+
reject(new Parse.Error(Parse.Error.SCRIPT_FAILED, error));
25+
}
26+
}
27+
}
28+
29+
static handleCloudFunction(req) {
30+
if (Parse.Cloud.Functions[req.params.functionName]) {
31+
32+
var request = {
33+
params: Object.assign({}, req.body, req.query),
34+
master: req.auth && req.auth.isMaster,
35+
user: req.auth && req.auth.user,
36+
installationId: req.info.installationId
37+
};
38+
39+
if (Parse.Cloud.Validators[req.params.functionName]) {
40+
var result = Parse.Cloud.Validators[req.params.functionName](request);
41+
if (!result) {
42+
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Validation failed.');
43+
}
44+
}
45+
46+
return new Promise(function (resolve, reject) {
47+
var response = FunctionsRouter.createResponseObject(resolve, reject);
48+
Parse.Cloud.Functions[req.params.functionName](request, response);
49+
});
50+
} else {
51+
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid function.');
52+
}
53+
}
54+
}
55+

src/validate_purchase.js renamed to src/Routers/IAPValidationRouter.js

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
var PromiseRouter = require("./PromiseRouter");
1+
import PromiseRouter from '../PromiseRouter';
22
var request = require("request");
3-
var rest = require("./rest");
4-
var Auth = require("./Auth");
3+
var rest = require("../rest");
4+
var Auth = require("../Auth");
55

6+
// TODO move validation logic in IAPValidationController
67
const IAP_SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt";
78
const IAP_PRODUCTION_URL = "https://buy.itunes.apple.com/verifyReceipt";
89

@@ -57,34 +58,39 @@ function getFileForProductIdentifier(productIdentifier, req) {
5758
});
5859
}
5960

60-
function handleRequest(req) {
61-
let receipt = req.body.receipt;
62-
const productIdentifier = req.body.productIdentifier;
63-
64-
if (!receipt || ! productIdentifier) {
65-
// TODO: Error, malformed request
66-
throw new Parse.Error(Parse.Error.INVALID_JSON, "missing receipt or productIdentifier");
67-
}
68-
69-
// Transform the object if there
70-
// otherwise assume it's in Base64 already
71-
if (typeof receipt == "object") {
72-
if (receipt["__type"] == "Bytes") {
73-
receipt = receipt.base64;
61+
62+
63+
export class IAPValidationRouter extends PromiseRouter {
64+
65+
handleRequest(req) {
66+
let receipt = req.body.receipt;
67+
const productIdentifier = req.body.productIdentifier;
68+
69+
if (!receipt || ! productIdentifier) {
70+
// TODO: Error, malformed request
71+
throw new Parse.Error(Parse.Error.INVALID_JSON, "missing receipt or productIdentifier");
72+
}
73+
74+
// Transform the object if there
75+
// otherwise assume it's in Base64 already
76+
if (typeof receipt == "object") {
77+
if (receipt["__type"] == "Bytes") {
78+
receipt = receipt.base64;
79+
}
80+
}
81+
82+
if (process.env.NODE_ENV == "test" && req.body.bypassAppStoreValidation) {
83+
return getFileForProductIdentifier(productIdentifier, req);
7484
}
85+
86+
return validateWithAppStore(IAP_PRODUCTION_URL, receipt).then( () => {
87+
return getFileForProductIdentifier(productIdentifier, req);
88+
}, (error) => {
89+
return Promise.resolve({response: appStoreError(error.status) });
90+
});
7591
}
7692

77-
if (process.env.NODE_ENV == "test" && req.body.bypassAppStoreValidation) {
78-
return getFileForProductIdentifier(productIdentifier, req);
93+
mountRoutes() {
94+
this.route("POST","/validate_purchase", this.handleRequest);
7995
}
80-
81-
return validateWithAppStore(IAP_PRODUCTION_URL, receipt).then( () => {
82-
return getFileForProductIdentifier(productIdentifier, req);
83-
}, (error) => {
84-
return Promise.resolve({response: appStoreError(error.status) });
85-
});
8696
}
87-
88-
var router = new PromiseRouter();
89-
router.route("POST","/validate_purchase", handleRequest);
90-
module.exports = router;

0 commit comments

Comments
 (0)