diff --git a/.gitignore b/.gitignore index 8ea322c438..33a78f6cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ lib/ # Mac DS_Store files .DS_Store + +# Folder created by FileSystemAdapter +/files diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 3b2108e71e..c3a281dceb 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -2,6 +2,7 @@ var FilesController = require('../src/Controllers/FilesController').FilesControl var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter; var S3Adapter = require("../src/Adapters/Files/S3Adapter").S3Adapter; var GCSAdapter = require("../src/Adapters/Files/GCSAdapter").GCSAdapter; +var FileSystemAdapter = require("../src/Adapters/Files/FileSystemAdapter").FileSystemAdapter; var Config = require("../src/Config"); var FCTestFactory = require("./FilesControllerTestFactory"); @@ -49,4 +50,15 @@ describe("FilesController",()=>{ } else if (!process.env.TRAVIS) { console.log("set GCP_PROJECT_ID, GCP_KEYFILE_PATH, and GCS_BUCKET to test GCSAdapter") } + + try { + // Test the file system adapter + var fsAdapter = new FileSystemAdapter({ + filesSubDirectory: 'sub1/sub2' + }); + + FCTestFactory.testAdapter("FileSystemAdapter", fsAdapter); + } catch (e) { + console.log("Give write access to the file system to test the FileSystemAdapter. Error: " + e); + } }); diff --git a/src/Adapters/Files/FileSystemAdapter.js b/src/Adapters/Files/FileSystemAdapter.js new file mode 100644 index 0000000000..cd5a6cb857 --- /dev/null +++ b/src/Adapters/Files/FileSystemAdapter.js @@ -0,0 +1,122 @@ +// FileSystemAdapter +// +// Stores files in local file system +// Requires write access to the server's file system. + +import { FilesAdapter } from './FilesAdapter'; +import colors from 'colors'; +var fs = require('fs'); +var path = require('path'); +var pathSep = require('path').sep; + +export class FileSystemAdapter extends FilesAdapter { + + constructor({filesSubDirectory = ''} = {}) { + super(); + + this._filesDir = filesSubDirectory; + this._mkdir(this._getApplicationDir()); + if (!this._applicationDirExist()) { + throw "Files directory doesn't exist."; + } + } + + // For a given config object, filename, and data, store a file + // Returns a promise + createFile(config, filename, data) { + return new Promise((resolve, reject) => { + let filepath = this._getLocalFilePath(filename); + fs.writeFile(filepath, data, (err) => { + if(err !== null) { + return reject(err); + } + resolve(data); + }); + }); + } + + deleteFile(config, filename) { + return new Promise((resolve, reject) => { + let filepath = this._getLocalFilePath(filename); + fs.readFile( filepath , function (err, data) { + if(err !== null) { + return reject(err); + } + fs.unlink(filepath, (unlinkErr) => { + if(err !== null) { + return reject(unlinkErr); + } + resolve(data); + }); + }); + + }); + } + + getFileData(config, filename) { + return new Promise((resolve, reject) => { + let filepath = this._getLocalFilePath(filename); + fs.readFile( filepath , function (err, data) { + if(err !== null) { + return reject(err); + } + resolve(data); + }); + }); + } + + getFileLocation(config, filename) { + return (config.mount + '/' + this._getLocalFilePath(filename)); + } + + /* + Helpers + --------------- */ + _getApplicationDir() { + if (this._filesDir) { + return path.join('files', this._filesDir); + } else { + return 'files'; + } + } + + _applicationDirExist() { + return fs.existsSync(this._getApplicationDir()); + } + + _getLocalFilePath(filename) { + let applicationDir = this._getApplicationDir(); + if (!fs.existsSync(applicationDir)) { + this._mkdir(applicationDir); + } + return path.join(applicationDir, encodeURIComponent(filename)); + } + + _mkdir(path) { + // snippet found on -> https://gist.github.com/danherbert-epam/3960169 + var dirs = path.split(pathSep); + var root = ""; + + while (dirs.length > 0) { + var dir = dirs.shift(); + if (dir === "") { // If directory starts with a /, the first path will be an empty string. + root = pathSep; + } + if (!fs.existsSync(root + dir)) { + try { + fs.mkdirSync(root + dir); + } + catch (e) { + if ( e.code == 'EACCES' ) { + console.error(""); + console.error(colors.red("ERROR: In order to use the FileSystemAdapter, write access to the server's file system is required")); + console.error(""); + } + } + } + root += dir + pathSep; + } + } +} + +export default FileSystemAdapter; \ No newline at end of file diff --git a/src/cli/parse-server.js b/src/cli/parse-server.js index b4cbbb1263..50c98202f8 100755 --- a/src/cli/parse-server.js +++ b/src/cli/parse-server.js @@ -40,7 +40,7 @@ if (program.args.length > 0 ) { jsonPath = path.resolve(jsonPath); options = require(jsonPath); console.log(`Configuation loaded from ${jsonPath}`) -} +} options = Object.keys(definitions).reduce(function (options, key) { if (program[key]) { @@ -53,7 +53,7 @@ if (!options.serverURL) { options.serverURL = `http://localhost:${options.port}${options.mountPath}`; } -if (!options.appId || !options.masterKey || !options.serverURL) { +if (!program.appId || !program.masterKey || !program.serverURL) { program.outputHelp(); console.error(""); console.error(colors.red("ERROR: appId, masterKey and serverURL are required")); @@ -65,7 +65,7 @@ const app = express(); const api = new ParseServer(options); app.use(options.mountPath, api); -var server = app.listen(options.port, function() { +app.listen(options.port, function() { for (let key in options) { let value = options[key]; diff --git a/src/index.js b/src/index.js index 87ab0331eb..e35f7448e0 100644 --- a/src/index.js +++ b/src/index.js @@ -22,7 +22,6 @@ import { AnalyticsRouter } from './Routers/AnalyticsRouter'; import { ClassesRouter } from './Routers/ClassesRouter'; import { FeaturesRouter } from './Routers/FeaturesRouter'; import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter'; -import { FilesController } from './Controllers/FilesController'; import { FilesRouter } from './Routers/FilesRouter'; import { FunctionsRouter } from './Routers/FunctionsRouter'; import { GCSAdapter } from './Adapters/Files/GCSAdapter'; @@ -46,6 +45,8 @@ import { SessionsRouter } from './Routers/SessionsRouter'; import { setFeature } from './features'; import { UserController } from './Controllers/UserController'; import { UsersRouter } from './Routers/UsersRouter'; +import { FilesController } from './Controllers/FilesController'; +import { FileSystemAdapter } from './Adapters/Files/FileSystemAdapter'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -265,5 +266,6 @@ function addParseCloud() { module.exports = { ParseServer: ParseServer, S3Adapter: S3Adapter, - GCSAdapter: GCSAdapter + GCSAdapter: GCSAdapter, + FileSystemAdapter: FileSystemAdapter };