diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..5e3ded83 --- /dev/null +++ b/.npmignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +# Emacs +*~ + +# WebStorm/IntelliJ +.idea diff --git a/bin/parse-server b/bin/parse-server index 902e43b2..7ec17927 100755 --- a/bin/parse-server +++ b/bin/parse-server @@ -21,7 +21,6 @@ if (process.env.PARSE_SERVER_OPTIONS) { options.restAPIKey = process.env.PARSE_SERVER_REST_API_KEY; options.dotNetKey = process.env.PARSE_SERVER_DOTNET_KEY; options.javascriptKey = process.env.PARSE_SERVER_JAVASCRIPT_KEY; - options.dotNetKey = process.env.PARSE_SERVER_DOTNET_KEY; options.masterKey = process.env.PARSE_SERVER_MASTER_KEY; options.fileKey = process.env.PARSE_SERVER_FILE_KEY; // Comma separated list of facebook app ids diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index c9f25bd8..6c7ec26b 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -64,6 +64,22 @@ describe('Parse.User testing', () => { }); }); + it("user login with files", (done) => { + "use strict"; + + let file = new Parse.File("yolo.txt", [1,2,3], "text/plain"); + file.save().then((file) => { + return Parse.User.signUp("asdf", "zxcv", { "file" : file }); + }).then(() => { + return Parse.User.logIn("asdf", "zxcv"); + }).then((user) => { + let fileAgain = user.get('file'); + ok(fileAgain.name()); + ok(fileAgain.url()); + done(); + }); + }); + it("become", (done) => { var user = null; var sessionToken = null; diff --git a/src/Adapters/Files/FilesAdapter.js b/src/Adapters/Files/FilesAdapter.js new file mode 100644 index 00000000..9daed517 --- /dev/null +++ b/src/Adapters/Files/FilesAdapter.js @@ -0,0 +1,22 @@ +// Files Adapter +// +// Allows you to change the file storage mechanism. +// +// Adapter classes must implement the following functions: +// * createFile(config, filename, data) +// * getFileData(config, filename) +// * getFileLocation(config, request, filename) +// +// Default is GridStoreAdapter, which requires mongo +// and for the API server to be using the ExportAdapter +// database adapter. + +export class FilesAdapter { + createFile(config, filename, data) { } + + getFileData(config, filename) { } + + getFileLocation(config, filename) { } +} + +export default FilesAdapter; diff --git a/src/Adapters/Files/GridStoreAdapter.js b/src/Adapters/Files/GridStoreAdapter.js new file mode 100644 index 00000000..8c95319d --- /dev/null +++ b/src/Adapters/Files/GridStoreAdapter.js @@ -0,0 +1,39 @@ +// GridStoreAdapter +// +// Stores files in Mongo using GridStore +// Requires the database adapter to be based on mongoclient + +import { GridStore } from 'mongodb'; +import { FilesAdapter } from './FilesAdapter'; + +export class GridStoreAdapter extends FilesAdapter { + // For a given config object, filename, and data, store a file + // Returns a promise + createFile(config, filename, data) { + return config.database.connect().then(() => { + let gridStore = new GridStore(config.database.db, filename, 'w'); + return gridStore.open(); + }).then((gridStore) => { + return gridStore.write(data); + }).then((gridStore) => { + return gridStore.close(); + }); + } + + getFileData(config, filename) { + return config.database.connect().then(() => { + return GridStore.exist(config.database.db, filename); + }).then(() => { + let gridStore = new GridStore(config.database.db, filename, 'r'); + return gridStore.open(); + }).then((gridStore) => { + return gridStore.read(); + }); + } + + getFileLocation(config, filename) { + return (config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename)); + } +} + +export default GridStoreAdapter; diff --git a/src/Adapters/Files/S3Adapter.js b/src/Adapters/Files/S3Adapter.js new file mode 100644 index 00000000..2c892246 --- /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/Config.js b/src/Config.js index df44f8b1..06d7af94 100644 --- a/src/Config.js +++ b/src/Config.js @@ -13,7 +13,6 @@ function Config(applicationId, mount) { this.applicationId = applicationId; this.collectionPrefix = cacheInfo.collectionPrefix || ''; - this.database = DatabaseAdapter.getDatabaseConnection(applicationId); this.masterKey = cacheInfo.masterKey; this.clientKey = cacheInfo.clientKey; this.javascriptKey = cacheInfo.javascriptKey; @@ -21,6 +20,10 @@ function Config(applicationId, mount) { this.restAPIKey = cacheInfo.restAPIKey; this.fileKey = cacheInfo.fileKey; this.facebookAppIds = cacheInfo.facebookAppIds; + + this.database = DatabaseAdapter.getDatabaseConnection(applicationId); + this.filesController = cacheInfo.filesController; + this.mount = mount; } diff --git a/src/Controllers/FilesController.js b/src/Controllers/FilesController.js new file mode 100644 index 00000000..47454f07 --- /dev/null +++ b/src/Controllers/FilesController.js @@ -0,0 +1,126 @@ +// 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); + let filename = req.params.filename; + this._filesAdapter.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() { + 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.createFile(req.config, filename, req.body).then(() => { + res.status(201); + var location = this._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.')); + }); + }; + } + + /** + * Find file references in REST-format object and adds the url key + * with the current mount point and app id. + * Object may be a single object or list of REST-format objects. + */ + expandFilesInObject(config, object) { + if (object instanceof Array) { + object.map((obj) => this.expandFilesInObject(config, obj)); + return; + } + if (typeof object !== 'object') { + return; + } + for (let key in object) { + let fileObject = object[key]; + if (fileObject && fileObject['__type'] === 'File') { + if (fileObject['url']) { + continue; + } + let filename = fileObject['name']; + if (filename.indexOf('tfss-') === 0) { + fileObject['url'] = 'http://files.parsetfss.com/' + config.fileKey + '/' + encodeURIComponent(filename); + } else { + fileObject['url'] = this._filesAdapter.getFileLocation(config, filename); + } + } + } + } + + 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/ExportAdapter.js b/src/ExportAdapter.js index 139096c9..98705328 100644 --- a/src/ExportAdapter.js +++ b/src/ExportAdapter.js @@ -10,9 +10,8 @@ var transform = require('./transform'); // options can contain: // collectionPrefix: the string to put in front of every collection name. -function ExportAdapter(mongoURI, options) { +function ExportAdapter(mongoURI, options = {}) { this.mongoURI = mongoURI; - options = options || {}; this.collectionPrefix = options.collectionPrefix; @@ -67,8 +66,7 @@ function returnsTrue() { // Returns a promise for a schema object. // If we are provided a acceptor, then we run it on the schema. // If the schema isn't accepted, we reload it at most once. -ExportAdapter.prototype.loadSchema = function(acceptor) { - acceptor = acceptor || returnsTrue; +ExportAdapter.prototype.loadSchema = function(acceptor = returnsTrue) { if (!this.schemaPromise) { this.schemaPromise = this.collection('_SCHEMA').then((coll) => { @@ -281,8 +279,7 @@ ExportAdapter.prototype.removeRelation = function(key, fromClassName, // acl: a list of strings. If the object to be updated has an ACL, // one of the provided strings must provide the caller with // write permissions. -ExportAdapter.prototype.destroy = function(className, query, options) { - options = options || {}; +ExportAdapter.prototype.destroy = function(className, query, options = {}) { var isMaster = !('acl' in options); var aclGroup = options.acl || []; @@ -350,8 +347,7 @@ ExportAdapter.prototype.create = function(className, object, options) { // This should only be used for testing - use 'find' for normal code // to avoid Mongo-format dependencies. // Returns a promise that resolves to a list of items. -ExportAdapter.prototype.mongoFind = function(className, query, options) { - options = options || {}; +ExportAdapter.prototype.mongoFind = function(className, query, options = {}) { return this.collection(className).then((coll) => { return coll.find(query, options).toArray(); }); @@ -507,8 +503,7 @@ ExportAdapter.prototype.smartFind = function(coll, where, options) { // TODO: make userIds not needed here. The db adapter shouldn't know // anything about users, ideally. Then, improve the format of the ACL // arg to work like the others. -ExportAdapter.prototype.find = function(className, query, options) { - options = options || {}; +ExportAdapter.prototype.find = function(className, query, options = {}) { var mongoOptions = {}; if (options.skip) { mongoOptions.skip = options.skip; diff --git a/src/FilesAdapter.js b/src/FilesAdapter.js deleted file mode 100644 index 427e20d9..00000000 --- a/src/FilesAdapter.js +++ /dev/null @@ -1,29 +0,0 @@ -// Files Adapter -// -// Allows you to change the file storage mechanism. -// -// Adapter classes must implement the following functions: -// * create(config, filename, data) -// * get(config, filename) -// * location(config, req, filename) -// -// Default is GridStoreAdapter, which requires mongo -// and for the API server to be using the ExportAdapter -// database adapter. - -var GridStoreAdapter = require('./GridStoreAdapter'); - -var adapter = GridStoreAdapter; - -function setAdapter(filesAdapter) { - adapter = filesAdapter; -} - -function getAdapter() { - return adapter; -} - -module.exports = { - getAdapter: getAdapter, - setAdapter: setAdapter -}; diff --git a/src/GridStoreAdapter.js b/src/GridStoreAdapter.js deleted file mode 100644 index 0d1e8965..00000000 --- a/src/GridStoreAdapter.js +++ /dev/null @@ -1,48 +0,0 @@ -// GridStoreAdapter -// -// Stores files in Mongo using GridStore -// Requires the database adapter to be based on mongoclient - -var GridStore = require('mongodb').GridStore; -var path = require('path'); - -// For a given config object, filename, and data, store a file -// Returns a promise -function create(config, filename, data) { - return config.database.connect().then(() => { - var gridStore = new GridStore(config.database.db, filename, 'w'); - return gridStore.open(); - }).then((gridStore) => { - return gridStore.write(data); - }).then((gridStore) => { - return gridStore.close(); - }); -} - -// Search for and return a file if found by filename -// Resolves a promise that succeeds with the buffer result -// from GridStore -function get(config, filename) { - return config.database.connect().then(() => { - return GridStore.exist(config.database.db, filename); - }).then(() => { - var gridStore = new GridStore(config.database.db, filename, 'r'); - return gridStore.open(); - }).then((gridStore) => { - return gridStore.read(); - }); -} - -// Generates and returns the location of a file stored in GridStore for the -// given request and filename -function location(config, req, filename) { - return (req.protocol + '://' + req.get('host') + - path.dirname(req.originalUrl) + '/' + req.config.applicationId + - '/' + encodeURIComponent(filename)); -} - -module.exports = { - create: create, - get: get, - location: location -}; diff --git a/src/RestQuery.js b/src/RestQuery.js index 8c9bf712..91ebe536 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -3,6 +3,8 @@ var Parse = require('parse/node').Parse; +import { default as FilesController } from './Controllers/FilesController'; + // restOptions can include: // skip // limit @@ -11,13 +13,12 @@ var Parse = require('parse/node').Parse; // include // keys // redirectClassNameForKey -function RestQuery(config, auth, className, restWhere, restOptions) { - restOptions = restOptions || {}; +function RestQuery(config, auth, className, restWhere = {}, restOptions = {}) { this.config = config; this.auth = auth; this.className = className; - this.restWhere = restWhere || {}; + this.restWhere = restWhere; this.response = null; this.findOptions = {}; @@ -317,35 +318,35 @@ RestQuery.prototype.replaceDontSelect = function() { RestQuery.prototype.runFind = function() { return this.config.database.find( this.className, this.restWhere, this.findOptions).then((results) => { - if (this.className == '_User') { - for (var result of results) { - delete result.password; - } + if (this.className == '_User') { + for (var result of results) { + delete result.password; } + } - updateParseFiles(this.config, results); + this.config.filesController.expandFilesInObject(this.config, results); - if (this.keys) { - var keySet = this.keys; - results = results.map((object) => { - var newObject = {}; - for (var key in object) { - if (keySet.has(key)) { - newObject[key] = object[key]; - } + if (this.keys) { + var keySet = this.keys; + results = results.map((object) => { + var newObject = {}; + for (var key in object) { + if (keySet.has(key)) { + newObject[key] = object[key]; } - return newObject; - }); - } - - if (this.redirectClassName) { - for (var r of results) { - r.className = this.redirectClassName; } - } + return newObject; + }); + } - this.response = {results: results}; - }); + if (this.redirectClassName) { + for (var r of results) { + r.className = this.redirectClassName; + } + } + + this.response = {results: results}; + }); }; // Returns a promise for whether it was successful. @@ -498,35 +499,6 @@ function replacePointers(object, path, replace) { return answer; } -// Find file references in REST-format object and adds the url key -// with the current mount point and app id -// Object may be a single object or list of REST-format objects -function updateParseFiles(config, object) { - if (object instanceof Array) { - object.map((obj) => updateParseFiles(config, obj)); - return; - } - if (typeof object !== 'object') { - return; - } - for (var key in object) { - if (object[key] && object[key]['__type'] && - object[key]['__type'] == 'File') { - var filename = object[key]['name']; - var encoded = encodeURIComponent(filename); - encoded = encoded.replace('%40', '@'); - if (filename.indexOf('tfss-') === 0) { - object[key]['url'] = 'http://files.parsetfss.com/' + - config.fileKey + '/' + encoded; - } else { - object[key]['url'] = config.mount + '/files/' + - config.applicationId + '/' + - encoded; - } - } - } -} - // Finds a subobject that has the given key, if there is one. // Returns undefined otherwise. function findObjectWithKey(root, key) { diff --git a/src/S3Adapter.js b/src/S3Adapter.js deleted file mode 100644 index 736ebf8b..00000000 --- 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/files.js b/src/files.js deleted file mode 100644 index a840e098..00000000 --- a/src/files.js +++ /dev/null @@ -1,85 +0,0 @@ -// files.js - -var bodyParser = require('body-parser'), - Config = require('./Config'), - express = require('express'), - FilesAdapter = require('./FilesAdapter'), - middlewares = require('./middlewares.js'), - mime = require('mime'), - Parse = require('parse/node').Parse, - rack = require('hat').rack(); - -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; - FilesAdapter.getAdapter().create(req.config, filename, req.body) - .then(() => { - res.status(201); - var location = FilesAdapter.getAdapter().location(req.config, req, 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.')); - }); -}; - -var processGet = function(req, res) { - var config = new Config(req.params.appId); - FilesAdapter.getAdapter().get(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 95b390e3..25e13641 100644 --- a/src/index.js +++ b/src/index.js @@ -5,14 +5,17 @@ var batch = require('./batch'), cache = require('./cache'), DatabaseAdapter = require('./DatabaseAdapter'), express = require('express'), - FilesAdapter = require('./FilesAdapter'), - S3Adapter = require('./S3Adapter'), middlewares = require('./middlewares'), multer = require('multer'), Parse = require('parse/node').Parse, PromiseRouter = require('./PromiseRouter'), httpRequest = require('./httpRequest'); +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(); @@ -46,9 +49,9 @@ function ParseServer(args) { if (args.databaseAdapter) { DatabaseAdapter.setAdapter(args.databaseAdapter); } - if (args.filesAdapter) { - FilesAdapter.setAdapter(args.filesAdapter); - } + + let filesAdapter = args.filesAdapter || new GridStoreAdapter(); + if (args.databaseURI) { DatabaseAdapter.setAppDatabaseURI(args.appId, args.databaseURI); } @@ -64,6 +67,8 @@ function ParseServer(args) { } + let filesController = new FilesController(filesAdapter); + cache.apps[args.appId] = { masterKey: args.masterKey, collectionPrefix: args.collectionPrefix || '', @@ -72,7 +77,8 @@ function ParseServer(args) { dotNetKey: args.dotNetKey || '', restAPIKey: args.restAPIKey || '', fileKey: args.fileKey || 'invalid-file-key', - facebookAppIds: args.facebookAppIds || [] + facebookAppIds: args.facebookAppIds || [], + filesController: filesController }; // To maintain compatibility. TODO: Remove in v2.1 @@ -91,7 +97,7 @@ function ParseServer(args) { var api = express(); // File handling needs to be before default middlewares are applied - api.use('/', require('./files').router); + api.use('/', filesController.getExpressRouter()); // TODO: separate this from the regular ParseServer object if (process.env.TESTING == 1) { diff --git a/src/users.js b/src/users.js index d769b9c5..5f0e01e7 100644 --- a/src/users.js +++ b/src/users.js @@ -58,6 +58,8 @@ function handleLogIn(req) { user.sessionToken = token; delete user.password; + req.config.filesController.expandFilesInObject(req.config, user); + var expiresAt = new Date(); expiresAt.setFullYear(expiresAt.getFullYear() + 1);