diff --git a/src/FilesAdapter.js b/src/Adapters/Files/FilesAdapter.js similarity index 70% rename from src/FilesAdapter.js rename to src/Adapters/Files/FilesAdapter.js index dbfc923c75..9daed5177a 100644 --- a/src/FilesAdapter.js +++ b/src/Adapters/Files/FilesAdapter.js @@ -3,8 +3,8 @@ // Allows you to change the file storage mechanism. // // Adapter classes must implement the following functions: -// * createFileAsync(config, filename, data) -// * getFileDataAsync(config, filename) +// * createFile(config, filename, data) +// * getFileData(config, filename) // * getFileLocation(config, request, filename) // // Default is GridStoreAdapter, which requires mongo @@ -12,9 +12,9 @@ // database adapter. export class FilesAdapter { - createFileAsync(config, filename, data) { } + createFile(config, filename, data) { } - getFileDataAsync(config, filename) { } + getFileData(config, filename) { } getFileLocation(config, filename) { } } diff --git a/src/GridStoreAdapter.js b/src/Adapters/Files/GridStoreAdapter.js similarity index 89% rename from src/GridStoreAdapter.js rename to src/Adapters/Files/GridStoreAdapter.js index d221b0213b..8c95319dea 100644 --- a/src/GridStoreAdapter.js +++ b/src/Adapters/Files/GridStoreAdapter.js @@ -6,10 +6,10 @@ import { GridStore } from 'mongodb'; import { FilesAdapter } from './FilesAdapter'; -class GridStoreAdapter extends FilesAdapter { +export class GridStoreAdapter extends FilesAdapter { // For a given config object, filename, and data, store a file // Returns a promise - createFileAsync(config, filename, data) { + createFile(config, filename, data) { return config.database.connect().then(() => { let gridStore = new GridStore(config.database.db, filename, 'w'); return gridStore.open(); @@ -20,7 +20,7 @@ class GridStoreAdapter extends FilesAdapter { }); } - getFileDataAsync(config, filename) { + getFileData(config, filename) { return config.database.connect().then(() => { return GridStore.exist(config.database.db, filename); }).then(() => { diff --git a/src/Adapters/Files/S3Adapter.js b/src/Adapters/Files/S3Adapter.js new file mode 100644 index 0000000000..2c892246b8 --- /dev/null +++ b/src/Adapters/Files/S3Adapter.js @@ -0,0 +1,83 @@ +// S3Adapter +// +// Stores Parse files in AWS S3. + +import * as AWS from 'aws-sdk'; +import { FilesAdapter } from './FilesAdapter'; + +const DEFAULT_S3_REGION = "us-east-1"; +const DEFAULT_S3_BUCKET = "parse-files"; + +export class S3Adapter extends FilesAdapter { + // Creates an S3 session. + // Providing AWS access and secret keys is mandatory + // Region and bucket will use sane defaults if omitted + constructor( + accessKey, + secretKey, + { region = DEFAULT_S3_REGION, + bucket = DEFAULT_S3_BUCKET, + bucketPrefix = '', + directAccess = false } = {} + ) { + super(); + + this._region = region; + this._bucket = bucket; + this._bucketPrefix = bucketPrefix; + this._directAccess = directAccess; + + let s3Options = { + accessKeyId: accessKey, + secretAccessKey: secretKey, + params: { Bucket: this._bucket } + }; + AWS.config._region = this._region; + this._s3Client = new AWS.S3(s3Options); + } + + // For a given config object, filename, and data, store a file in S3 + // Returns a promise containing the S3 object creation response + createFile(config, filename, data) { + let params = { + Key: this._bucketPrefix + filename, + Body: data + }; + if (this._directAccess) { + params.ACL = "public-read" + } + return new Promise((resolve, reject) => { + this._s3Client.upload(params, (err, data) => { + if (err !== null) { + return reject(err); + } + resolve(data); + }); + }); + } + + // Search for and return a file if found by filename + // Returns a promise that succeeds with the buffer result from S3 + getFileData(config, filename) { + let params = {Key: this._bucketPrefix + filename}; + return new Promise((resolve, reject) => { + this._s3Client.getObject(params, (err, data) => { + if (err !== null) { + return reject(err); + } + resolve(data.Body); + }); + }); + } + + // Generates and returns the location of a file stored in S3 for the given request and filename + // The location is the direct S3 link if the option is set, otherwise we serve the file through parse-server + getFileLocation(config, filename) { + if (this._directAccess) { + return ('https://' + this.bucket + '._s3Client.amazonaws.com' + '/' + this._bucketPrefix + filename); + } + return (config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename)); + } +} + +export default S3Adapter; diff --git a/src/Controllers/FilesController.js b/src/Controllers/FilesController.js index a22e257208..47454f076c 100644 --- a/src/Controllers/FilesController.js +++ b/src/Controllers/FilesController.js @@ -19,7 +19,7 @@ export class FilesController { return (req, res) => { let config = new Config(req.params.appId); let filename = req.params.filename; - this._filesAdapter.getFileDataAsync(config, filename).then((data) => { + this._filesAdapter.getFileData(config, filename).then((data) => { res.status(200); var contentType = mime.lookup(filename); res.set('Content-type', contentType); @@ -62,7 +62,7 @@ export class FilesController { } let filename = rack() + '_' + req.params.filename + extension; - this._filesAdapter.createFileAsync(req.config, filename, req.body).then(() => { + this._filesAdapter.createFile(req.config, filename, req.body).then(() => { res.status(201); var location = this._filesAdapter.getFileLocation(req.config, filename); res.set('Location', location); diff --git a/src/S3Adapter.js b/src/S3Adapter.js deleted file mode 100644 index 736ebf8bd1..0000000000 --- a/src/S3Adapter.js +++ /dev/null @@ -1,77 +0,0 @@ -// S3Adapter -// -// Stores Parse files in AWS S3. - -var AWS = require('aws-sdk'); -var path = require('path'); - -var DEFAULT_REGION = "us-east-1"; -var DEFAULT_BUCKET = "parse-files"; - -// Creates an S3 session. -// Providing AWS access and secret keys is mandatory -// Region and bucket will use sane defaults if omitted -function S3Adapter(accessKey, secretKey, options) { - options = options || {}; - - this.region = options.region || DEFAULT_REGION; - this.bucket = options.bucket || DEFAULT_BUCKET; - this.bucketPrefix = options.bucketPrefix || ""; - this.directAccess = options.directAccess || false; - - s3Options = { - accessKeyId: accessKey, - secretAccessKey: secretKey, - params: {Bucket: this.bucket} - }; - AWS.config.region = this.region; - this.s3 = new AWS.S3(s3Options); -} - -// For a given config object, filename, and data, store a file in S3 -// Returns a promise containing the S3 object creation response -S3Adapter.prototype.create = function(config, filename, data) { - var params = { - Key: this.bucketPrefix + filename, - Body: data, - }; - if (this.directAccess) { - params.ACL = "public-read" - } - - return new Promise((resolve, reject) => { - this.s3.upload(params, (err, data) => { - if (err !== null) return reject(err); - resolve(data); - }); - }); -} - -// Search for and return a file if found by filename -// Returns a promise that succeeds with the buffer result from S3 -S3Adapter.prototype.get = function(config, filename) { - var params = {Key: this.bucketPrefix + filename}; - - return new Promise((resolve, reject) => { - this.s3.getObject(params, (err, data) => { - if (err !== null) return reject(err); - resolve(data.Body); - }); - }); -} - -// Generates and returns the location of a file stored in S3 for the given request and -// filename -// The location is the direct S3 link if the option is set, otherwise we serve -// the file through parse-server -S3Adapter.prototype.location = function(config, req, filename) { - if (this.directAccess) { - return ('https://' + this.bucket + '.s3.amazonaws.com' + '/' + - this.bucketPrefix + filename); - } - return (req.protocol + '://' + req.get('host') + - path.dirname(req.originalUrl) + '/' + req.config.applicationId + - '/' + encodeURIComponent(filename)); -} - -module.exports = S3Adapter; diff --git a/src/index.js b/src/index.js index d685e79fcc..73d48298d0 100644 --- a/src/index.js +++ b/src/index.js @@ -5,15 +5,16 @@ var batch = require('./batch'), cache = require('./cache'), DatabaseAdapter = require('./DatabaseAdapter'), express = require('express'), - S3Adapter = require('./S3Adapter'), middlewares = require('./middlewares'), multer = require('multer'), Parse = require('parse/node').Parse, PromiseRouter = require('./PromiseRouter'), httpRequest = require('./httpRequest'); -import { default as GridStoreAdapter } from './GridStoreAdapter'; -import { default as FilesController } from './Controllers/FilesController'; +import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter'; +import { S3Adapter } from './Adapters/Files/S3Adapter'; + +import { FilesController } from './Controllers/FilesController'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud();