Merge pull request #708 from mcdonamp/mcdonald-gcs-adapter
Yet Another FileAdapter: Google Cloud Storage
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -40,3 +40,6 @@ lib/
|
|||||||
|
|
||||||
# cache folder
|
# cache folder
|
||||||
.cache
|
.cache
|
||||||
|
|
||||||
|
# Mac DS_Store files
|
||||||
|
.DS_Store
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -135,9 +135,14 @@ PARSE_SERVER_MAX_UPLOAD_SIZE
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Configuring S3 Adapter
|
##### Configuring File Adapters
|
||||||
|
Parse Server allows developers to choose from several options when hosting files: the `GridStoreAdapter`, which backed by MongoDB; the `S3Adapter`, which is backed by [Amazon S3](https://aws.amazon.com/s3/); or the `GCSAdapter`, which is backed by [Google Cloud Storage](https://cloud.google.com/storage/).
|
||||||
|
|
||||||
You can use the following environment variable setup the S3 adapter
|
`GridStoreAdapter` is used by default and requires no setup, but if you're interested in using S3 or GCS, additional configuration information is available below.
|
||||||
|
|
||||||
|
###### Configuring `S3Adapter`
|
||||||
|
|
||||||
|
You can use the following environment variable setup to enable the S3 adapter:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
S3_ACCESS_KEY
|
S3_ACCESS_KEY
|
||||||
@@ -149,6 +154,19 @@ S3_DIRECT_ACCESS
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
###### Configuring `GCSAdapter`
|
||||||
|
|
||||||
|
You can use the following environment variable setup to enable the GCS adapter:
|
||||||
|
|
||||||
|
```js
|
||||||
|
GCP_PROJECT_ID
|
||||||
|
GCP_KEYFILE_PATH
|
||||||
|
GCS_BUCKET
|
||||||
|
GCS_BUCKET_PREFIX
|
||||||
|
GCS_DIRECT_ACCESS
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server guide](CONTRIBUTING.md).
|
We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server guide](CONTRIBUTING.md).
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"commander": "^2.9.0",
|
"commander": "^2.9.0",
|
||||||
"deepcopy": "^0.6.1",
|
"deepcopy": "^0.6.1",
|
||||||
"express": "^4.13.4",
|
"express": "^4.13.4",
|
||||||
|
"gcloud": "^0.28.0",
|
||||||
"mailgun-js": "^0.7.7",
|
"mailgun-js": "^0.7.7",
|
||||||
"mime": "^1.3.4",
|
"mime": "^1.3.4",
|
||||||
"mongodb": "~2.1.0",
|
"mongodb": "~2.1.0",
|
||||||
|
|||||||
@@ -3,44 +3,45 @@ var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
|
|||||||
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
|
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
|
||||||
var ParsePushAdapter = require("../src/Adapters/Push/ParsePushAdapter");
|
var ParsePushAdapter = require("../src/Adapters/Push/ParsePushAdapter");
|
||||||
var S3Adapter = require("../src/Adapters/Files/S3Adapter").default;
|
var S3Adapter = require("../src/Adapters/Files/S3Adapter").default;
|
||||||
|
var GCSAdapter = require("../src/Adapters/Files/GCSAdapter").default;
|
||||||
|
|
||||||
describe("AdapterLoader", ()=>{
|
describe("AdapterLoader", ()=>{
|
||||||
|
|
||||||
it("should instantiate an adapter from string in object", (done) => {
|
it("should instantiate an adapter from string in object", (done) => {
|
||||||
var adapterPath = require('path').resolve("./spec/MockAdapter");
|
var adapterPath = require('path').resolve("./spec/MockAdapter");
|
||||||
|
|
||||||
var adapter = loadAdapter({
|
var adapter = loadAdapter({
|
||||||
adapter: adapterPath,
|
adapter: adapterPath,
|
||||||
options: {
|
options: {
|
||||||
key: "value",
|
key: "value",
|
||||||
foo: "bar"
|
foo: "bar"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(adapter instanceof Object).toBe(true);
|
expect(adapter instanceof Object).toBe(true);
|
||||||
expect(adapter.options.key).toBe("value");
|
expect(adapter.options.key).toBe("value");
|
||||||
expect(adapter.options.foo).toBe("bar");
|
expect(adapter.options.foo).toBe("bar");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should instantiate an adapter from string", (done) => {
|
it("should instantiate an adapter from string", (done) => {
|
||||||
var adapterPath = require('path').resolve("./spec/MockAdapter");
|
var adapterPath = require('path').resolve("./spec/MockAdapter");
|
||||||
var adapter = loadAdapter(adapterPath);
|
var adapter = loadAdapter(adapterPath);
|
||||||
|
|
||||||
expect(adapter instanceof Object).toBe(true);
|
expect(adapter instanceof Object).toBe(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should instantiate an adapter from string that is module", (done) => {
|
it("should instantiate an adapter from string that is module", (done) => {
|
||||||
var adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
|
var adapterPath = require('path').resolve("./src/Adapters/Files/FilesAdapter");
|
||||||
var adapter = loadAdapter({
|
var adapter = loadAdapter({
|
||||||
adapter: adapterPath
|
adapter: adapterPath
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(adapter instanceof FilesAdapter).toBe(true);
|
expect(adapter instanceof FilesAdapter).toBe(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should instantiate an adapter from function/Class", (done) => {
|
it("should instantiate an adapter from function/Class", (done) => {
|
||||||
var adapter = loadAdapter({
|
var adapter = loadAdapter({
|
||||||
adapter: FilesAdapter
|
adapter: FilesAdapter
|
||||||
@@ -48,27 +49,27 @@ describe("AdapterLoader", ()=>{
|
|||||||
expect(adapter instanceof FilesAdapter).toBe(true);
|
expect(adapter instanceof FilesAdapter).toBe(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should instantiate the default adapter from Class", (done) => {
|
it("should instantiate the default adapter from Class", (done) => {
|
||||||
var adapter = loadAdapter(null, FilesAdapter);
|
var adapter = loadAdapter(null, FilesAdapter);
|
||||||
expect(adapter instanceof FilesAdapter).toBe(true);
|
expect(adapter instanceof FilesAdapter).toBe(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should use the default adapter", (done) => {
|
it("should use the default adapter", (done) => {
|
||||||
var defaultAdapter = new FilesAdapter();
|
var defaultAdapter = new FilesAdapter();
|
||||||
var adapter = loadAdapter(null, defaultAdapter);
|
var adapter = loadAdapter(null, defaultAdapter);
|
||||||
expect(adapter instanceof FilesAdapter).toBe(true);
|
expect(adapter instanceof FilesAdapter).toBe(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should use the provided adapter", (done) => {
|
it("should use the provided adapter", (done) => {
|
||||||
var originalAdapter = new FilesAdapter();
|
var originalAdapter = new FilesAdapter();
|
||||||
var adapter = loadAdapter(originalAdapter);
|
var adapter = loadAdapter(originalAdapter);
|
||||||
expect(adapter).toBe(originalAdapter);
|
expect(adapter).toBe(originalAdapter);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail loading an improperly configured adapter", (done) => {
|
it("should fail loading an improperly configured adapter", (done) => {
|
||||||
var Adapter = function(options) {
|
var Adapter = function(options) {
|
||||||
if (!options.foo) {
|
if (!options.foo) {
|
||||||
@@ -79,14 +80,14 @@ describe("AdapterLoader", ()=>{
|
|||||||
param: "key",
|
param: "key",
|
||||||
doSomething: function() {}
|
doSomething: function() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
var adapter = loadAdapter(adapterOptions, Adapter);
|
var adapter = loadAdapter(adapterOptions, Adapter);
|
||||||
expect(adapter).toEqual(adapterOptions);
|
expect(adapter).toEqual(adapterOptions);
|
||||||
}).not.toThrow("foo is required for that adapter");
|
}).not.toThrow("foo is required for that adapter");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should load push adapter from options", (done) => {
|
it("should load push adapter from options", (done) => {
|
||||||
var options = {
|
var options = {
|
||||||
ios: {
|
ios: {
|
||||||
@@ -100,7 +101,7 @@ describe("AdapterLoader", ()=>{
|
|||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should load S3Adapter from direct passing", (done) => {
|
it("should load S3Adapter from direct passing", (done) => {
|
||||||
var s3Adapter = new S3Adapter("key", "secret", "bucket")
|
var s3Adapter = new S3Adapter("key", "secret", "bucket")
|
||||||
expect(() => {
|
expect(() => {
|
||||||
@@ -109,4 +110,13 @@ describe("AdapterLoader", ()=>{
|
|||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should load GCSAdapter from direct passing", (done) => {
|
||||||
|
var gcsAdapter = new GCSAdapter("projectId", "path/to/keyfile", "bucket")
|
||||||
|
expect(() => {
|
||||||
|
var adapter = loadAdapter(gcsAdapter, FilesAdapter);
|
||||||
|
expect(adapter).toBe(gcsAdapter);
|
||||||
|
}).not.toThrow();
|
||||||
|
done();
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
var FilesController = require('../src/Controllers/FilesController').FilesController;
|
var FilesController = require('../src/Controllers/FilesController').FilesController;
|
||||||
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||||
var S3Adapter = require("../src/Adapters/Files/S3Adapter").S3Adapter;
|
var S3Adapter = require("../src/Adapters/Files/S3Adapter").S3Adapter;
|
||||||
|
var GCSAdapter = require("../src/Adapters/Files/GCSAdapter").GCSAdapter;
|
||||||
var Config = require("../src/Config");
|
var Config = require("../src/Config");
|
||||||
|
|
||||||
var FCTestFactory = require("./FilesControllerTestFactory");
|
var FCTestFactory = require("./FilesControllerTestFactory");
|
||||||
@@ -8,26 +9,44 @@ var FCTestFactory = require("./FilesControllerTestFactory");
|
|||||||
|
|
||||||
// Small additional tests to improve overall coverage
|
// Small additional tests to improve overall coverage
|
||||||
describe("FilesController",()=>{
|
describe("FilesController",()=>{
|
||||||
|
|
||||||
// Test the grid store adapter
|
// Test the grid store adapter
|
||||||
var gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse');
|
var gridStoreAdapter = new GridStoreAdapter('mongodb://localhost:27017/parse');
|
||||||
FCTestFactory.testAdapter("GridStoreAdapter", gridStoreAdapter);
|
FCTestFactory.testAdapter("GridStoreAdapter", gridStoreAdapter);
|
||||||
|
|
||||||
if (process.env.S3_ACCESS_KEY && process.env.S3_SECRET_KEY) {
|
if (process.env.S3_ACCESS_KEY && process.env.S3_SECRET_KEY) {
|
||||||
|
|
||||||
// Test the S3 Adapter
|
// Test the S3 Adapter
|
||||||
var s3Adapter = new S3Adapter(process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY, 'parse.server.tests');
|
var s3Adapter = new S3Adapter(process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY, 'parse.server.tests');
|
||||||
|
|
||||||
FCTestFactory.testAdapter("S3Adapter",s3Adapter);
|
FCTestFactory.testAdapter("S3Adapter",s3Adapter);
|
||||||
|
|
||||||
// Test S3 with direct access
|
// Test S3 with direct access
|
||||||
var s3DirectAccessAdapter = new S3Adapter(process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY, 'parse.server.tests', {
|
var s3DirectAccessAdapter = new S3Adapter(process.env.S3_ACCESS_KEY, process.env.S3_SECRET_KEY, 'parse.server.tests', {
|
||||||
directAccess: true
|
directAccess: true
|
||||||
});
|
});
|
||||||
|
|
||||||
FCTestFactory.testAdapter("S3AdapterDirect", s3DirectAccessAdapter);
|
FCTestFactory.testAdapter("S3AdapterDirect", s3DirectAccessAdapter);
|
||||||
|
|
||||||
} else if (!process.env.TRAVIS) {
|
} else if (!process.env.TRAVIS) {
|
||||||
console.log("set S3_ACCESS_KEY and S3_SECRET_KEY to test S3Adapter")
|
console.log("set S3_ACCESS_KEY and S3_SECRET_KEY to test S3Adapter")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.GCP_PROJECT_ID && process.env.GCP_KEYFILE_PATH && process.env.GCS_BUCKET) {
|
||||||
|
|
||||||
|
// Test the GCS Adapter
|
||||||
|
var gcsAdapter = new GCSAdapter(process.env.GCP_PROJECT_ID, process.env.GCP_KEYFILE_PATH, process.env.GCS_BUCKET);
|
||||||
|
|
||||||
|
FCTestFactory.testAdapter("GCSAdapter", gcsAdapter);
|
||||||
|
|
||||||
|
// Test GCS with direct access
|
||||||
|
var gcsDirectAccessAdapter = new GCSAdapter(process.env.GCP_PROJECT_ID, process.env.GCP_KEYFILE_PATH, process.env.GCS_BUCKET, {
|
||||||
|
directAccess: true
|
||||||
|
});
|
||||||
|
|
||||||
|
FCTestFactory.testAdapter("GCSAdapterDirect", gcsDirectAccessAdapter);
|
||||||
|
|
||||||
|
} else if (!process.env.TRAVIS) {
|
||||||
|
console.log("set GCP_PROJECT_ID, GCP_KEYFILE_PATH, and GCS_BUCKET to test GCSAdapter")
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
|
|
||||||
var FilesController = require('../src/Controllers/FilesController').FilesController;
|
var FilesController = require('../src/Controllers/FilesController').FilesController;
|
||||||
var Config = require("../src/Config");
|
var Config = require("../src/Config");
|
||||||
|
|
||||||
var testAdapter = function(name, adapter) {
|
var testAdapter = function(name, adapter) {
|
||||||
// Small additional tests to improve overall coverage
|
// Small additional tests to improve overall coverage
|
||||||
|
|
||||||
var config = new Config(Parse.applicationId);
|
var config = new Config(Parse.applicationId);
|
||||||
var filesController = new FilesController(adapter);
|
var filesController = new FilesController(adapter);
|
||||||
|
|
||||||
describe("FilesController with "+name,()=>{
|
describe("FilesController with "+name,()=>{
|
||||||
|
|
||||||
it("should properly expand objects", (done) => {
|
it("should properly expand objects", (done) => {
|
||||||
|
|
||||||
var result = filesController.expandFilesInObject(config, function(){});
|
var result = filesController.expandFilesInObject(config, function(){});
|
||||||
|
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
|
|
||||||
var fullFile = {
|
var fullFile = {
|
||||||
type: '__type',
|
type: '__type',
|
||||||
url: "http://an.url"
|
url: "http://an.url"
|
||||||
}
|
}
|
||||||
|
|
||||||
var anObject = {
|
var anObject = {
|
||||||
aFile: fullFile
|
aFile: fullFile
|
||||||
}
|
}
|
||||||
filesController.expandFilesInObject(config, anObject);
|
filesController.expandFilesInObject(config, anObject);
|
||||||
expect(anObject.aFile.url).toEqual("http://an.url");
|
expect(anObject.aFile.url).toEqual("http://an.url");
|
||||||
|
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should properly create, read, delete files", (done) => {
|
it("should properly create, read, delete files", (done) => {
|
||||||
var filename;
|
var filename;
|
||||||
filesController.createFile(config, "file.txt", "hello world").then( (result) => {
|
filesController.createFile(config, "file.txt", "hello world").then( (result) => {
|
||||||
@@ -51,14 +50,14 @@ var testAdapter = function(name, adapter) {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
done();
|
done();
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
|
|
||||||
filesController.getFileData(config, filename).then((res) => {
|
filesController.getFileData(config, filename).then((res) => {
|
||||||
fail("the file should be deleted");
|
fail("the file should be deleted");
|
||||||
done();
|
done();
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
fail("The adapter should delete the file");
|
fail("The adapter should delete the file");
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export function loadAdapter(adapter, defaultAdapter, options) {
|
export function loadAdapter(adapter, defaultAdapter, options) {
|
||||||
|
if (!adapter)
|
||||||
if (!adapter)
|
|
||||||
{
|
{
|
||||||
if (!defaultAdapter) {
|
if (!defaultAdapter) {
|
||||||
return options;
|
return options;
|
||||||
@@ -20,7 +19,7 @@ export function loadAdapter(adapter, defaultAdapter, options) {
|
|||||||
if (adapter.default) {
|
if (adapter.default) {
|
||||||
adapter = adapter.default;
|
adapter = adapter.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadAdapter(adapter, undefined, options);
|
return loadAdapter(adapter, undefined, options);
|
||||||
} else if (adapter.module) {
|
} else if (adapter.module) {
|
||||||
return loadAdapter(adapter.module, undefined, adapter.options);
|
return loadAdapter(adapter.module, undefined, adapter.options);
|
||||||
@@ -30,7 +29,7 @@ export function loadAdapter(adapter, defaultAdapter, options) {
|
|||||||
return loadAdapter(adapter.adapter, undefined, adapter.options);
|
return loadAdapter(adapter.adapter, undefined, adapter.options);
|
||||||
}
|
}
|
||||||
// return the adapter as provided
|
// return the adapter as provided
|
||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default loadAdapter;
|
export default loadAdapter;
|
||||||
|
|||||||
125
src/Adapters/Files/GCSAdapter.js
Normal file
125
src/Adapters/Files/GCSAdapter.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
// GCSAdapter
|
||||||
|
// Store Parse Files in Google Cloud Storage: https://cloud.google.com/storage
|
||||||
|
import { storage } from 'gcloud';
|
||||||
|
import { FilesAdapter } from './FilesAdapter';
|
||||||
|
import requiredParameter from '../../requiredParameter';
|
||||||
|
|
||||||
|
function requiredOrFromEnvironment(env, name) {
|
||||||
|
let environmentVariable = process.env[env];
|
||||||
|
if (!environmentVariable) {
|
||||||
|
requiredParameter(`GCSAdapter requires an ${name}`);
|
||||||
|
}
|
||||||
|
return environmentVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromEnvironmentOrDefault(env, defaultValue) {
|
||||||
|
let environmentVariable = process.env[env];
|
||||||
|
if (environmentVariable) {
|
||||||
|
return environmentVariable;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GCSAdapter extends FilesAdapter {
|
||||||
|
// GCS Project ID and the name of a corresponding Keyfile are required.
|
||||||
|
// Unlike the S3 adapter, you must create a new Cloud Storage bucket, as this is not created automatically.
|
||||||
|
// See https://googlecloudplatform.github.io/gcloud-node/#/docs/master/guides/authentication
|
||||||
|
// for more details.
|
||||||
|
constructor(
|
||||||
|
projectId = requiredOrFromEnvironment('GCP_PROJECT_ID', 'projectId'),
|
||||||
|
keyFilename = requiredOrFromEnvironment('GCP_KEYFILE_PATH', 'keyfile path'),
|
||||||
|
bucket = requiredOrFromEnvironment('GCS_BUCKET', 'bucket name'),
|
||||||
|
{ bucketPrefix = fromEnvironmentOrDefault('GCS_BUCKET_PREFIX', ''),
|
||||||
|
directAccess = fromEnvironmentOrDefault('GCS_DIRECT_ACCESS', false) } = {}) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._bucket = bucket;
|
||||||
|
this._bucketPrefix = bucketPrefix;
|
||||||
|
this._directAccess = directAccess;
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
projectId: projectId,
|
||||||
|
keyFilename: keyFilename
|
||||||
|
};
|
||||||
|
|
||||||
|
this._gcsClient = new storage(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a given config object, filename, and data, store a file in GCS.
|
||||||
|
// Resolves the promise or fails with an error.
|
||||||
|
createFile(config, filename, data, contentType) {
|
||||||
|
let params = {
|
||||||
|
contentType: contentType || 'application/octet-stream'
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let file = this._gcsClient.bucket(this._bucket).file(this._bucketPrefix + filename);
|
||||||
|
// gcloud supports upload(file) not upload(bytes), so we need to stream.
|
||||||
|
var uploadStream = file.createWriteStream(params);
|
||||||
|
uploadStream.on('error', (err) => {
|
||||||
|
return reject(err);
|
||||||
|
}).on('finish', () => {
|
||||||
|
// Second call to set public read ACL after object is uploaded.
|
||||||
|
if (this._directAccess) {
|
||||||
|
file.makePublic((err, res) => {
|
||||||
|
if (err !== null) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
uploadStream.write(data);
|
||||||
|
uploadStream.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletes a file with the given file name.
|
||||||
|
// Returns a promise that succeeds with the delete response, or fails with an error.
|
||||||
|
deleteFile(config, filename) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let file = this._gcsClient.bucket(this._bucket).file(this._bucketPrefix + filename);
|
||||||
|
file.delete((err, res) => {
|
||||||
|
if(err !== null) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for and return a file if found by filename.
|
||||||
|
// Returns a promise that succeeds with the buffer result from GCS, or fails with an error.
|
||||||
|
getFileData(config, filename) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let file = this._gcsClient.bucket(this._bucket).file(this._bucketPrefix + filename);
|
||||||
|
// Check for existence, since gcloud-node seemed to be caching the result
|
||||||
|
file.exists((err, exists) => {
|
||||||
|
if (exists) {
|
||||||
|
file.download((err, data) => {
|
||||||
|
if (err !== null) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
return resolve(data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates and returns the location of a file stored in GCS for the given request and filename.
|
||||||
|
// The location is the direct GCS link if the option is set,
|
||||||
|
// otherwise we serve the file through parse-server.
|
||||||
|
getFileLocation(config, filename) {
|
||||||
|
if (this._directAccess) {
|
||||||
|
return `https://${this._bucket}.storage.googleapis.com/${this._bucketPrefix + filename}`;
|
||||||
|
}
|
||||||
|
return (config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GCSAdapter;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
AdaptableController.js
|
AdaptableController.js
|
||||||
|
|
||||||
AdaptableController is the base class for all controllers
|
AdaptableController is the base class for all controllers
|
||||||
that support adapter,
|
that support adapter,
|
||||||
The super class takes care of creating the right instance for the adapter
|
The super class takes care of creating the right instance for the adapter
|
||||||
based on the parameters passed
|
based on the parameters passed
|
||||||
|
|
||||||
@@ -28,30 +28,30 @@ export class AdaptableController {
|
|||||||
this.validateAdapter(adapter);
|
this.validateAdapter(adapter);
|
||||||
this[_adapter] = adapter;
|
this[_adapter] = adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
get adapter() {
|
get adapter() {
|
||||||
return this[_adapter];
|
return this[_adapter];
|
||||||
}
|
}
|
||||||
|
|
||||||
get config() {
|
get config() {
|
||||||
return new Config(this.appId);
|
return new Config(this.appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedAdapterType() {
|
expectedAdapterType() {
|
||||||
throw new Error("Subclasses should implement expectedAdapterType()");
|
throw new Error("Subclasses should implement expectedAdapterType()");
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAdapter(adapter) {
|
validateAdapter(adapter) {
|
||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
throw new Error(this.constructor.name+" requires an adapter");
|
throw new Error(this.constructor.name+" requires an adapter");
|
||||||
}
|
}
|
||||||
|
|
||||||
let Type = this.expectedAdapterType();
|
let Type = this.expectedAdapterType();
|
||||||
// Allow skipping for testing
|
// Allow skipping for testing
|
||||||
if (!Type) {
|
if (!Type) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Makes sure the prototype matches
|
// Makes sure the prototype matches
|
||||||
let mismatches = Object.getOwnPropertyNames(Type.prototype).reduce( (obj, key) => {
|
let mismatches = Object.getOwnPropertyNames(Type.prototype).reduce( (obj, key) => {
|
||||||
const adapterType = typeof adapter[key];
|
const adapterType = typeof adapter[key];
|
||||||
@@ -64,7 +64,7 @@ export class AdaptableController {
|
|||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
if (Object.keys(mismatches).length > 0) {
|
if (Object.keys(mismatches).length > 0) {
|
||||||
throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches);
|
throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ export class FilesController extends AdaptableController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createFile(config, filename, data, contentType) {
|
createFile(config, filename, data, contentType) {
|
||||||
|
|
||||||
let extname = path.extname(filename);
|
let extname = path.extname(filename);
|
||||||
|
|
||||||
const hasExtension = extname.length > 0;
|
const hasExtension = extname.length > 0;
|
||||||
|
|
||||||
if (!hasExtension && contentType && mime.extension(contentType)) {
|
if (!hasExtension && contentType && mime.extension(contentType)) {
|
||||||
filename = filename + '.' + mime.extension(contentType);
|
filename = filename + '.' + mime.extension(contentType);
|
||||||
} else if (hasExtension && !contentType) {
|
} else if (hasExtension && !contentType) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
|||||||
import { FilesController } from './Controllers/FilesController';
|
import { FilesController } from './Controllers/FilesController';
|
||||||
import { FilesRouter } from './Routers/FilesRouter';
|
import { FilesRouter } from './Routers/FilesRouter';
|
||||||
import { FunctionsRouter } from './Routers/FunctionsRouter';
|
import { FunctionsRouter } from './Routers/FunctionsRouter';
|
||||||
|
import { GCSAdapter } from './Adapters/Files/GCSAdapter';
|
||||||
import { GlobalConfigRouter } from './Routers/GlobalConfigRouter';
|
import { GlobalConfigRouter } from './Routers/GlobalConfigRouter';
|
||||||
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
|
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
|
||||||
import { HooksController } from './Controllers/HooksController';
|
import { HooksController } from './Controllers/HooksController';
|
||||||
@@ -259,4 +260,5 @@ function addParseCloud() {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
ParseServer: ParseServer,
|
ParseServer: ParseServer,
|
||||||
S3Adapter: S3Adapter,
|
S3Adapter: S3Adapter,
|
||||||
|
GCSAdapter: GCSAdapter
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user