Refactors FilesController in FilesRouter and FilesController
This commit is contained in:
27
spec/FilesController.spec.js
Normal file
27
spec/FilesController.spec.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
var FilesController = require('../src/Controllers/FilesController').FilesController;
|
||||||
|
var Config = require("../src/Config");
|
||||||
|
|
||||||
|
// Small additional tests to improve overall coverage
|
||||||
|
describe("FilesController",()=>{
|
||||||
|
|
||||||
|
it("should properly expand objects", (done) => {
|
||||||
|
var config = new Config(Parse.applicationId);
|
||||||
|
var filesController = new FilesController();
|
||||||
|
var result = filesController.expandFilesInObject(config, function(){});
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
|
||||||
|
var fullFile = {
|
||||||
|
type: '__type',
|
||||||
|
url: "http://an.url"
|
||||||
|
}
|
||||||
|
|
||||||
|
var anObject = {
|
||||||
|
aFile: fullFile
|
||||||
|
}
|
||||||
|
filesController.expandFilesInObject(config, anObject);
|
||||||
|
expect(anObject.aFile.url).toEqual("http://an.url");
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,34 +1,37 @@
|
|||||||
// A Config object provides information about how a specific app is
|
// A Config object provides information about how a specific app is
|
||||||
// configured.
|
// configured.
|
||||||
// mount is the URL for the root of the API; includes http, domain, etc.
|
// mount is the URL for the root of the API; includes http, domain, etc.
|
||||||
function Config(applicationId, mount) {
|
export class Config {
|
||||||
var cache = require('./cache');
|
|
||||||
var DatabaseAdapter = require('./DatabaseAdapter');
|
|
||||||
|
|
||||||
var cacheInfo = cache.apps[applicationId];
|
constructor(applicationId, mount) {
|
||||||
this.valid = !!cacheInfo;
|
var cache = require('./cache');
|
||||||
if (!this.valid) {
|
var DatabaseAdapter = require('./DatabaseAdapter');
|
||||||
return;
|
|
||||||
|
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.oauth = cacheInfo.oauth;
|
||||||
|
|
||||||
|
this.mount = mount;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.applicationId = applicationId;
|
export default Config;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Config;
|
module.exports = Config;
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
// FilesController.js
|
// FilesController.js
|
||||||
|
|
||||||
import express from 'express';
|
|
||||||
import mime from 'mime';
|
|
||||||
import { Parse } from 'parse/node';
|
import { Parse } from 'parse/node';
|
||||||
import BodyParser from 'body-parser';
|
|
||||||
import * as Middlewares from '../middlewares';
|
|
||||||
import Config from '../Config';
|
|
||||||
import { randomHexString } from '../cryptoUtils';
|
import { randomHexString } from '../cryptoUtils';
|
||||||
|
|
||||||
export class FilesController {
|
export class FilesController {
|
||||||
@@ -13,98 +7,23 @@ export class FilesController {
|
|||||||
this._filesAdapter = filesAdapter;
|
this._filesAdapter = filesAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getHandler() {
|
getFileData(config, filename) {
|
||||||
return (req, res) => {
|
return this._filesAdapter.getFileData(config, filename);
|
||||||
let config = new Config(req.params.appId);
|
|
||||||
return config.filesController.getHandler()(req, res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getHandler() {
|
createFile(config, filename, data) {
|
||||||
return (req, res) => {
|
filename = randomHexString(32) + '_' + filename;
|
||||||
let config = new Config(req.params.appId);
|
var location = this._filesAdapter.getFileLocation(config, filename);
|
||||||
let filename = req.params.filename;
|
return this._filesAdapter.createFile(config, filename, data).then(() => {
|
||||||
this._filesAdapter.getFileData(config, filename).then((data) => {
|
return Promise.resolve({
|
||||||
res.status(200);
|
url: location,
|
||||||
var contentType = mime.lookup(filename);
|
name: 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.');
|
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static createHandler() {
|
deleteFile(config, filename) {
|
||||||
return (req, res, next) => {
|
return this._filesAdapter.deleteFile(config, filename);
|
||||||
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.'));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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;
|
export default FilesController;
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
// AnalyticsRouter.js
|
// AnalyticsRouter.js
|
||||||
|
|
||||||
var Parse = require('parse/node').Parse;
|
|
||||||
|
|
||||||
import PromiseRouter from '../PromiseRouter';
|
import PromiseRouter from '../PromiseRouter';
|
||||||
|
|
||||||
// Returns a promise that resolves to an empty object response
|
// 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.'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import { FunctionsRouter } from './Routers/FunctionsRouter';
|
|||||||
import { SchemasRouter } from './Routers/SchemasRouter';
|
import { SchemasRouter } from './Routers/SchemasRouter';
|
||||||
import { IAPValidationRouter } from './Routers/IAPValidationRouter';
|
import { IAPValidationRouter } from './Routers/IAPValidationRouter';
|
||||||
import { PushRouter } from './Routers/PushRouter';
|
import { PushRouter } from './Routers/PushRouter';
|
||||||
|
import { FilesRouter } from './Routers/FilesRouter';
|
||||||
|
|
||||||
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
||||||
import { LoggerController } from './Controllers/LoggerController';
|
import { LoggerController } from './Controllers/LoggerController';
|
||||||
@@ -111,8 +112,9 @@ function ParseServer({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let filesController = new FilesController(filesAdapter);
|
const filesController = new FilesController(filesAdapter);
|
||||||
let pushController = new PushController(pushAdapter);
|
const pushController = new PushController(pushAdapter);
|
||||||
|
const loggerController = new LoggerController(loggerAdapter);
|
||||||
|
|
||||||
cache.apps[appId] = {
|
cache.apps[appId] = {
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
@@ -125,6 +127,7 @@ function ParseServer({
|
|||||||
facebookAppIds: facebookAppIds,
|
facebookAppIds: facebookAppIds,
|
||||||
filesController: filesController,
|
filesController: filesController,
|
||||||
pushController: pushController,
|
pushController: pushController,
|
||||||
|
loggerController: loggerController,
|
||||||
enableAnonymousUsers: enableAnonymousUsers,
|
enableAnonymousUsers: enableAnonymousUsers,
|
||||||
oauth: oauth,
|
oauth: oauth,
|
||||||
};
|
};
|
||||||
@@ -143,7 +146,7 @@ function ParseServer({
|
|||||||
var api = express();
|
var api = express();
|
||||||
|
|
||||||
// File handling needs to be before default middlewares are applied
|
// 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
|
// TODO: separate this from the regular ParseServer object
|
||||||
if (process.env.TESTING == 1) {
|
if (process.env.TESTING == 1) {
|
||||||
|
|||||||
Reference in New Issue
Block a user