diff --git a/src/Controllers/FilesController.js b/src/Controllers/FilesController.js new file mode 100644 index 0000000000..4a929a6e1e --- /dev/null +++ b/src/Controllers/FilesController.js @@ -0,0 +1,97 @@ +// FilesController.js + +import express from 'express'; +import mime from 'mime'; +import { Parse } from 'parse/node'; +import BodyParser from 'body-parser'; +import hat from 'hat'; +import * as Middlewares from '../middlewares'; +import Config from '../Config'; + +const rack = hat.rack(); + +export class FilesController { + constructor(filesAdapter) { + this._filesAdapter = filesAdapter; + } + + getHandler() { + return (req, res) => { + let config = new Config(req.params.appId); + this._filesAdapter.getFileDataAsync(config, req.params.filename).then((data) => { + res.status(200); + var contentType = mime.lookup(req.params.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() { + 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; + } + + // 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 = rack() + '_' + req.params.filename + extension; + this._filesAdapter.createFileAsync(req.config, filename, req.body).then(() => { + res.status(201); + var location = this._filesAdapter.getFileLocation(req.config, req, filename); + res.set('Location', location); + res.json({ url: location, name: filename }); + }).catch((error) => { + console.log(error); + next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, + 'Could not store file.')); + }); + }; + } + + getExpressRouter() { + let 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() + ); + + return router; + } +} + +export default FilesController; diff --git a/src/FilesAdapter.js b/src/FilesAdapter.js index 94fd2bb879..62fe07018e 100644 --- a/src/FilesAdapter.js +++ b/src/FilesAdapter.js @@ -11,16 +11,6 @@ // and for the API server to be using the ExportAdapter // database adapter. -let adapter = null; - -export function setAdapter(filesAdapter) { - adapter = filesAdapter; -} - -export function getAdapter() { - return adapter; -} - export class FilesAdapter { createFileAsync(config, filename, data) { } @@ -28,3 +18,5 @@ export class FilesAdapter { getFileLocation(config, request, filename) { } } + +export default FilesAdapter; diff --git a/src/files.js b/src/files.js deleted file mode 100644 index 86cdbfbe13..0000000000 --- a/src/files.js +++ /dev/null @@ -1,85 +0,0 @@ -// files.js - -var bodyParser = require('body-parser'), - Config = require('./Config'), - express = require('express'), - middlewares = require('./middlewares.js'), - mime = require('mime'), - Parse = require('parse/node').Parse, - rack = require('hat').rack(); - -import { getAdapter as getFilesAdapter } from './FilesAdapter'; - -var router = express.Router(); - -var processCreate = function(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; - } - - // If a content-type is included, we'll add an extension so we can - // return the same content-type. - var extension = ''; - var hasExtension = req.params.filename.indexOf('.') > 0; - var contentType = req.get('Content-type'); - if (!hasExtension && contentType && mime.extension(contentType)) { - extension = '.' + mime.extension(contentType); - } - - var filename = rack() + '_' + req.params.filename + extension; - getFilesAdapter().createFileAsync(req.config, filename, req.body).then(() => { - res.status(201); - var location = getFilesAdapter().getFileLocation(req.config, req, filename); - res.set('Location', location); - res.json({ url: location, name: filename }); - }).catch((error) => { - console.log(error); - next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, - 'Could not store file.')); - }); -}; - -var processGet = function(req, res) { - var config = new Config(req.params.appId); - getFilesAdapter().getFileDataAsync(config, req.params.filename).then((data) => { - res.status(200); - var contentType = mime.lookup(req.params.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.'); - }); -}; - -router.get('/files/:appId/:filename', processGet); - -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, - processCreate); - -module.exports = { - router: router -}; diff --git a/src/index.js b/src/index.js index 48d3e8c9d7..93d363f76a 100644 --- a/src/index.js +++ b/src/index.js @@ -12,8 +12,8 @@ var batch = require('./batch'), PromiseRouter = require('./PromiseRouter'), httpRequest = require('./httpRequest'); -import { setAdapter as setFilesAdapter } from './FilesAdapter'; import { default as GridStoreAdapter } from './GridStoreAdapter'; +import { default as FilesController } from './Controllers/FilesController'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -48,11 +48,9 @@ function ParseServer(args) { if (args.databaseAdapter) { DatabaseAdapter.setAdapter(args.databaseAdapter); } - if (args.filesAdapter) { - setFilesAdapter(args.filesAdapter); - } else { - setFilesAdapter(new GridStoreAdapter()); - } + + let filesAdapter = args.filesAdapter || new GridStoreAdapter(); + if (args.databaseURI) { DatabaseAdapter.setAppDatabaseURI(args.appId, args.databaseURI); } @@ -95,7 +93,8 @@ function ParseServer(args) { var api = express(); // File handling needs to be before default middlewares are applied - api.use('/', require('./files').router); + let filesController = new FilesController(filesAdapter); + api.use('/', filesController.getExpressRouter()); // TODO: separate this from the regular ParseServer object if (process.env.TESTING == 1) {