Skip to content

Commit 305879a

Browse files
committed
Refactors FilesController in FilesRouter and FilesController
1 parent b490688 commit 305879a

File tree

6 files changed

+177
-150
lines changed

6 files changed

+177
-150
lines changed

spec/FilesController.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
var FilesController = require('../src/Controllers/FilesController').FilesController;
2+
var Config = require("../src/Config");
3+
4+
// Small additional tests to improve overall coverage
5+
describe("FilesController",()=>{
6+
7+
it("should properly expand objects", (done) => {
8+
var config = new Config(Parse.applicationId);
9+
var filesController = new FilesController();
10+
var result = filesController.expandFilesInObject(config, function(){});
11+
12+
expect(result).toBeUndefined();
13+
14+
var fullFile = {
15+
type: '__type',
16+
url: "http://an.url"
17+
}
18+
19+
var anObject = {
20+
aFile: fullFile
21+
}
22+
filesController.expandFilesInObject(config, anObject);
23+
expect(anObject.aFile.url).toEqual("http://an.url");
24+
25+
done();
26+
})
27+
})

src/Config.js

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,37 @@
11
// A Config object provides information about how a specific app is
22
// configured.
33
// mount is the URL for the root of the API; includes http, domain, etc.
4-
function Config(applicationId, mount) {
5-
var cache = require('./cache');
6-
var DatabaseAdapter = require('./DatabaseAdapter');
4+
export class Config {
75

8-
var cacheInfo = cache.apps[applicationId];
9-
this.valid = !!cacheInfo;
10-
if (!this.valid) {
11-
return;
12-
}
6+
constructor(applicationId, mount) {
7+
var cache = require('./cache');
8+
var DatabaseAdapter = require('./DatabaseAdapter');
139

14-
this.applicationId = applicationId;
15-
this.collectionPrefix = cacheInfo.collectionPrefix || '';
16-
this.masterKey = cacheInfo.masterKey;
17-
this.clientKey = cacheInfo.clientKey;
18-
this.javascriptKey = cacheInfo.javascriptKey;
19-
this.dotNetKey = cacheInfo.dotNetKey;
20-
this.restAPIKey = cacheInfo.restAPIKey;
21-
this.fileKey = cacheInfo.fileKey;
22-
this.facebookAppIds = cacheInfo.facebookAppIds;
23-
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
10+
var cacheInfo = cache.apps[applicationId];
11+
this.valid = !!cacheInfo;
12+
if (!this.valid) {
13+
return;
14+
}
2415

25-
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
26-
this.filesController = cacheInfo.filesController;
27-
this.pushController = cacheInfo.pushController;
28-
this.oauth = cacheInfo.oauth;
16+
this.applicationId = applicationId;
17+
this.collectionPrefix = cacheInfo.collectionPrefix || '';
18+
this.masterKey = cacheInfo.masterKey;
19+
this.clientKey = cacheInfo.clientKey;
20+
this.javascriptKey = cacheInfo.javascriptKey;
21+
this.dotNetKey = cacheInfo.dotNetKey;
22+
this.restAPIKey = cacheInfo.restAPIKey;
23+
this.fileKey = cacheInfo.fileKey;
24+
this.facebookAppIds = cacheInfo.facebookAppIds;
25+
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
2926

30-
this.mount = mount;
31-
}
27+
this.database = DatabaseAdapter.getDatabaseConnection(applicationId);
28+
this.filesController = cacheInfo.filesController;
29+
this.pushController = cacheInfo.pushController;
30+
this.oauth = cacheInfo.oauth;
3231

32+
this.mount = mount;
33+
}
34+
};
3335

36+
export default Config;
3437
module.exports = Config;

src/Controllers/FilesController.js

Lines changed: 13 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,29 @@
11
// FilesController.js
2-
3-
import express from 'express';
4-
import mime from 'mime';
52
import { Parse } from 'parse/node';
6-
import BodyParser from 'body-parser';
7-
import * as Middlewares from '../middlewares';
8-
import Config from '../Config';
93
import { randomHexString } from '../cryptoUtils';
104

115
export class FilesController {
126
constructor(filesAdapter) {
137
this._filesAdapter = filesAdapter;
148
}
159

16-
static getHandler() {
17-
return (req, res) => {
18-
let config = new Config(req.params.appId);
19-
return config.filesController.getHandler()(req, res);
20-
}
21-
}
22-
23-
getHandler() {
24-
return (req, res) => {
25-
let config = new Config(req.params.appId);
26-
let filename = req.params.filename;
27-
this._filesAdapter.getFileData(config, filename).then((data) => {
28-
res.status(200);
29-
var contentType = mime.lookup(filename);
30-
res.set('Content-type', contentType);
31-
res.end(data);
32-
}).catch((error) => {
33-
res.status(404);
34-
res.set('Content-type', 'text/plain');
35-
res.end('File not found.');
36-
});
37-
};
10+
getFileData(config, filename) {
11+
return this._filesAdapter.getFileData(config, filename);
3812
}
3913

40-
static createHandler() {
41-
return (req, res, next) => {
42-
let config = req.config;
43-
return config.filesController.createHandler()(req, res, next);
44-
}
45-
}
46-
47-
createHandler() {
48-
return (req, res, next) => {
49-
if (!req.body || !req.body.length) {
50-
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
51-
'Invalid file upload.'));
52-
return;
53-
}
54-
55-
if (req.params.filename.length > 128) {
56-
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
57-
'Filename too long.'));
58-
return;
59-
}
60-
61-
if (!req.params.filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
62-
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
63-
'Filename contains invalid characters.'));
64-
return;
65-
}
66-
67-
const filesController = req.config.filesController;
68-
// If a content-type is included, we'll add an extension so we can
69-
// return the same content-type.
70-
let extension = '';
71-
let hasExtension = req.params.filename.indexOf('.') > 0;
72-
let contentType = req.get('Content-type');
73-
if (!hasExtension && contentType && mime.extension(contentType)) {
74-
extension = '.' + mime.extension(contentType);
75-
}
76-
77-
let filename = randomHexString(32) + '_' + req.params.filename + extension;
78-
filesController._filesAdapter.createFile(req.config, filename, req.body).then(() => {
79-
res.status(201);
80-
var location = filesController._filesAdapter.getFileLocation(req.config, filename);
81-
res.set('Location', location);
82-
res.json({ url: location, name: filename });
83-
}).catch((error) => {
84-
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
85-
'Could not store file.'));
14+
createFile(config, filename, data) {
15+
filename = randomHexString(32) + '_' + filename;
16+
var location = this._filesAdapter.getFileLocation(config, filename);
17+
return this._filesAdapter.createFile(config, filename, data).then(() => {
18+
return Promise.resolve({
19+
url: location,
20+
name: filename
8621
});
87-
};
88-
}
89-
90-
static deleteHandler() {
91-
return (req, res, next) => {
92-
let config = req.config;
93-
return config.filesController.deleteHandler()(req, res, next);
94-
}
95-
}
22+
});
23+
}
9624

97-
deleteHandler() {
98-
return (req, res, next) => {
99-
this._filesAdapter.deleteFile(req.config, req.params.filename).then(() => {
100-
res.status(200);
101-
// TODO: return useful JSON here?
102-
res.end();
103-
}).catch((error) => {
104-
next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR,
105-
'Could not delete file.'));
106-
});
107-
};
25+
deleteFile(config, filename) {
26+
return this._filesAdapter.deleteFile(config, filename);
10827
}
10928

11029
/**
@@ -135,32 +54,6 @@ export class FilesController {
13554
}
13655
}
13756
}
138-
139-
static getExpressRouter() {
140-
let router = express.Router();
141-
router.get('/files/:appId/:filename', FilesController.getHandler());
142-
143-
router.post('/files', function(req, res, next) {
144-
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
145-
'Filename not provided.'));
146-
});
147-
148-
router.post('/files/:filename',
149-
Middlewares.allowCrossDomain,
150-
BodyParser.raw({type: '*/*', limit: '20mb'}),
151-
Middlewares.handleParseHeaders,
152-
FilesController.createHandler()
153-
);
154-
155-
router.delete('/files/:filename',
156-
Middlewares.allowCrossDomain,
157-
Middlewares.handleParseHeaders,
158-
Middlewares.enforceMasterKeyAccess,
159-
FilesController.deleteHandler()
160-
);
161-
162-
return router;
163-
}
16457
}
16558

16659
export default FilesController;

src/Routers/AnalyticsRouter.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
// AnalyticsRouter.js
2-
3-
var Parse = require('parse/node').Parse;
4-
52
import PromiseRouter from '../PromiseRouter';
63

74
// Returns a promise that resolves to an empty object response

src/Routers/FilesRouter.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import PromiseRouter from '../PromiseRouter';
2+
import express from 'express';
3+
import BodyParser from 'body-parser';
4+
import * as Middlewares from '../middlewares';
5+
import { randomHexString } from '../cryptoUtils';
6+
import mime from 'mime';
7+
import Config from '../Config';
8+
9+
export class FilesRouter {
10+
11+
getExpressRouter() {
12+
var router = express.Router();
13+
router.get('/files/:appId/:filename', this.getHandler);
14+
15+
router.post('/files', function(req, res, next) {
16+
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
17+
'Filename not provided.'));
18+
});
19+
20+
router.post('/files/:filename',
21+
Middlewares.allowCrossDomain,
22+
BodyParser.raw({type: '*/*', limit: '20mb'}),
23+
Middlewares.handleParseHeaders,
24+
this.createHandler
25+
);
26+
27+
router.delete('/files/:filename',
28+
Middlewares.allowCrossDomain,
29+
Middlewares.handleParseHeaders,
30+
Middlewares.enforceMasterKeyAccess,
31+
this.deleteHandler
32+
);
33+
return router;
34+
}
35+
36+
getHandler(req, res, next) {
37+
const config = new Config(req.params.appId);
38+
const filesController = config.filesController;
39+
const filename = req.params.filename;
40+
filesController.getFileData(config, filename).then((data) => {
41+
res.status(200);
42+
var contentType = mime.lookup(filename);
43+
res.set('Content-type', contentType);
44+
res.end(data);
45+
}).catch((error) => {
46+
res.status(404);
47+
res.set('Content-type', 'text/plain');
48+
res.end('File not found.');
49+
});
50+
}
51+
52+
createHandler(req, res, next) {
53+
if (!req.body || !req.body.length) {
54+
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
55+
'Invalid file upload.'));
56+
return;
57+
}
58+
59+
if (req.params.filename.length > 128) {
60+
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
61+
'Filename too long.'));
62+
return;
63+
}
64+
65+
if (!req.params.filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
66+
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
67+
'Filename contains invalid characters.'));
68+
return;
69+
}
70+
let extension = '';
71+
72+
// Not very safe there.
73+
const hasExtension = req.params.filename.indexOf('.') > 0;
74+
const contentType = req.get('Content-type');
75+
if (!hasExtension && contentType && mime.extension(contentType)) {
76+
extension = '.' + mime.extension(contentType);
77+
}
78+
79+
const filename = req.params.filename + extension;
80+
const config = req.config;
81+
const filesController = config.filesController;
82+
83+
filesController.createFile(config, filename, req.body).then((result) => {
84+
res.status(201);
85+
res.set('Location', result.url);
86+
res.json(result);
87+
}).catch((err) => {
88+
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
89+
'Could not store file.'));
90+
});
91+
}
92+
93+
deleteHandler(req, res, next) {
94+
const filesController = req.config.filesController;
95+
filesController.deleteFile(req.config, req.params.filename).then(() => {
96+
res.status(200);
97+
// TODO: return useful JSON here?
98+
res.end();
99+
}).catch((error) => {
100+
next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR,
101+
'Could not delete file.'));
102+
});
103+
}
104+
}

src/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { FunctionsRouter } from './Routers/FunctionsRouter';
2929
import { SchemasRouter } from './Routers/SchemasRouter';
3030
import { IAPValidationRouter } from './Routers/IAPValidationRouter';
3131
import { PushRouter } from './Routers/PushRouter';
32+
import { FilesRouter } from './Routers/FilesRouter';
3233

3334
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
3435
import { LoggerController } from './Controllers/LoggerController';
@@ -111,8 +112,9 @@ function ParseServer({
111112
}
112113
}
113114

114-
let filesController = new FilesController(filesAdapter);
115-
let pushController = new PushController(pushAdapter);
115+
const filesController = new FilesController(filesAdapter);
116+
const pushController = new PushController(pushAdapter);
117+
const loggerController = new LoggerController(loggerAdapter);
116118

117119
cache.apps[appId] = {
118120
masterKey: masterKey,
@@ -125,6 +127,7 @@ function ParseServer({
125127
facebookAppIds: facebookAppIds,
126128
filesController: filesController,
127129
pushController: pushController,
130+
loggerController: loggerController,
128131
enableAnonymousUsers: enableAnonymousUsers,
129132
oauth: oauth,
130133
};
@@ -143,7 +146,7 @@ function ParseServer({
143146
var api = express();
144147

145148
// File handling needs to be before default middlewares are applied
146-
api.use('/', FilesController.getExpressRouter());
149+
api.use('/', new FilesRouter().getExpressRouter());
147150

148151
// TODO: separate this from the regular ParseServer object
149152
if (process.env.TESTING == 1) {

0 commit comments

Comments
 (0)