2016-02-27 16:33:34 -08:00
|
|
|
// GCSAdapter
|
|
|
|
|
// Store Parse Files in Google Cloud Storage: https://cloud.google.com/storage
|
2016-03-03 22:36:25 -08:00
|
|
|
import { storage } from 'gcloud';
|
2016-02-27 16:33:34 -08:00
|
|
|
import { FilesAdapter } from './FilesAdapter';
|
2016-03-03 22:36:25 -08:00
|
|
|
import requiredParameter from '../../requiredParameter';
|
2016-02-27 16:33:34 -08:00
|
|
|
|
|
|
|
|
export class GCSAdapter extends FilesAdapter {
|
|
|
|
|
// GCS Project ID and the name of a corresponding Keyfile are required.
|
2016-03-03 22:36:25 -08:00
|
|
|
// Unlike the S3 adapter, you must create a new Cloud Storage bucket, as this is not created automatically.
|
2016-02-27 16:33:34 -08:00
|
|
|
// See https://googlecloudplatform.github.io/gcloud-node/#/docs/master/guides/authentication
|
|
|
|
|
// for more details.
|
|
|
|
|
constructor(
|
2016-03-03 22:36:25 -08:00
|
|
|
projectId = requiredParameter('GCSAdapter requires a GCP Project ID'),
|
|
|
|
|
keyFilename = requiredParameter('GCSAdapter requires a GCP keyfile'),
|
|
|
|
|
bucket = requiredParameter('GCSAdapter requires a GCS bucket name'),
|
2016-02-27 16:33:34 -08:00
|
|
|
{ bucketPrefix = '',
|
|
|
|
|
directAccess = false } = {}
|
|
|
|
|
) {
|
|
|
|
|
super();
|
|
|
|
|
|
|
|
|
|
this._bucket = bucket;
|
|
|
|
|
this._bucketPrefix = bucketPrefix;
|
|
|
|
|
this._directAccess = directAccess;
|
|
|
|
|
|
2016-03-03 22:36:25 -08:00
|
|
|
let options = {
|
2016-02-27 16:33:34 -08:00
|
|
|
projectId: projectId,
|
|
|
|
|
keyFilename: keyFilename
|
|
|
|
|
};
|
|
|
|
|
|
2016-03-03 22:36:25 -08:00
|
|
|
this._gcsClient = new storage(options);
|
2016-02-27 16:33:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For a given config object, filename, and data, store a file in GCS.
|
|
|
|
|
// Resolves the promise or fails with an error.
|
2016-03-03 22:36:25 -08:00
|
|
|
createFile(config, filename, data, contentType) {
|
|
|
|
|
let params = {
|
|
|
|
|
contentType: contentType || 'application/octet-stream'
|
|
|
|
|
};
|
|
|
|
|
|
2016-02-27 16:33:34 -08:00
|
|
|
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.
|
2016-03-03 22:36:25 -08:00
|
|
|
var uploadStream = file.createWriteStream(params);
|
2016-02-27 16:33:34 -08:00
|
|
|
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();
|
|
|
|
|
});
|
2016-02-27 21:27:32 -08:00
|
|
|
} else {
|
|
|
|
|
resolve();
|
2016-02-27 16:33:34 -08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
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) => {
|
2016-03-03 22:36:25 -08:00
|
|
|
console.log("delete: ", filename, err, res);
|
2016-02-27 16:33:34 -08:00
|
|
|
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);
|
2016-03-03 22:36:25 -08:00
|
|
|
// Check for existence, since gcloud-node seemed to be caching the result
|
|
|
|
|
file.exists((err, exists) => {
|
|
|
|
|
if (exists) {
|
|
|
|
|
file.download((err, data) => {
|
|
|
|
|
console.log("get: ", filename, err, data);
|
|
|
|
|
if (err !== null) {
|
|
|
|
|
return reject(err);
|
|
|
|
|
}
|
|
|
|
|
return resolve(data);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
reject(err);
|
2016-02-27 16:33:34 -08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|