index.js
/* eslint-disable func-names */
'use strict';
const { Storage } = require('@google-cloud/storage');
const Promise = require('bluebird');
const { PassThrough } = require('stream');
const defaultOptions = require('./config/default');
const sharpOptions = require('./lib/get-sharp-options');
const getDestination = require('./lib/get-destination');
const getFilename = require('./lib/get-filename');
const transformer = require('./lib/transformer');
const getFormat = require('./lib/get-format');
const logger = require('./lib/logger');
class MulterSharp {
constructor(options) {
/* eslint-disable no-param-reassign, no-underscore-dangle */
options.bucket = options.bucket || process.env.GCS_BUCKET || null;
options.projectId = options.projectId || process.env.GC_PROJECT || null;
options.keyFilename = options.keyFilename || process.env.GCS_KEYFILE || null;
this._check(options);
this.options = Object.assign({}, MulterSharp.defaultOptions, options || {});
this.getFilename = this.options.filename || getFilename;
this.sharpOptions = sharpOptions(this.options);
if (typeof options.destination === 'string') {
this.getDestination = ($0, $1, cb) => cb(null, options.destination);
} else {
this.getDestination = options.destination || getDestination;
}
this.gcStorage = new Storage({
projectId: options.projectId,
keyFilename: options.keyFilename
});
this.gcsBucket = this.gcStorage.bucket(options.bucket);
}
_check(options) {
if (!options.bucket) {
throw new Error(
'You have to specify bucket for Google Cloud Storage to work.'
);
}
if (!options.projectId) {
throw new Error(
'You have to specify project id for Google Cloud Storage to work.'
);
}
}
_eachUpload(file, filename, gcName, fileOptions, stream) {
return (size) => {
const filenameWithSuffix = `${filename}-${size.suffix}`;
const gcNameBySuffix = `${gcName}-${size.suffix}`;
const gcFile = this.gcsBucket.file(gcNameBySuffix);
const streamCopy = new PassThrough();
const resizerStream = transformer(this.sharpOptions, size);
const writableStream = gcFile.createWriteStream(fileOptions);
stream.pipe(streamCopy);
return new Promise((resolve, reject) => {
streamCopy.pipe(resizerStream).pipe(writableStream);
resizerStream.on('info', logger);
resizerStream.on('error', function (transformErr) {
resizerStream.unpipe(writableStream);
this.end();
reject(transformErr);
});
writableStream.on('error', function (gcErr) {
this.end();
reject(gcErr);
});
writableStream.on('finish', () => {
const uri = encodeURI(
`https://storage.googleapis.com/${
this.options.bucket
}/${gcNameBySuffix}`
);
resolve({
mimetype: getFormat(this.sharpOptions.toFormat) || file.mimetype,
path: uri,
filename: filenameWithSuffix,
suffix: size.suffix
});
});
});
};
}
_handleFile(req, file, cb) {
this.getDestination(req, file, (destErr, destination) => {
if (destErr) cb(destErr);
this.getFilename(req, file, (fileErr, filename) => {
if (fileErr) cb(fileErr);
const fileOptions = {
predefinedAcl: this.options.acl,
metadata: Object.assign(this.options.metadata, {
contentType: getFormat(this.sharpOptions.toFormat) || file.mimetype
}),
gzip: this.options.gzip
};
const gcName = typeof destination === 'string' && destination.length > 0
? `${destination}/${filename}`
: filename;
const gcFile = this.gcsBucket.file(gcName);
const { stream } = file;
const resizerStream = transformer(this.sharpOptions, this.options.size);
const writableStream = gcFile.createWriteStream(fileOptions);
if (
Array.isArray(this.options.sizes)
&& this.options.sizes.length > 0
) {
this._uploadWithMultipleSize(
this.options.sizes,
file,
filename,
gcName,
fileOptions,
stream,
cb
);
} else {
this._uploadOne(
stream,
file,
filename,
gcName,
resizerStream,
writableStream,
cb
);
}
});
});
}
_removeFile(req, file, cb) {
this.getDestination(req, file, (destErr, destination) => {
if (destErr) cb(destErr);
const gcName = typeof destination === 'string' && destination.length > 0
? `${destination}/${file.filename}`
: file.filename;
const gcFile = this.gcsBucket.file(gcName);
gcFile.delete(cb);
});
}
_uploadOne(
stream,
file,
filename,
gcName,
resizerStream,
writableStream,
cb
) {
stream.pipe(resizerStream).pipe(writableStream);
resizerStream
.on('info', logger)
.on('error', function (transformErr) {
resizerStream.unpipe(writableStream);
cb(transformErr);
this.end();
});
writableStream
.on('error', function (gcErr) {
cb(gcErr);
this.end();
})
.on('finish', () => {
const uri = encodeURI(
`https://storage.googleapis.com/${this.options.bucket}/${gcName}`
);
return cb(null, {
mimetype: getFormat(this.sharpOptions.toFormat) || file.mimetype,
path: uri,
filename
});
});
}
_uploadWithMultipleSize(
sizes,
file,
filename,
gcName,
fileOptions,
stream,
cb
) {
Promise.map(
sizes,
this._eachUpload(file, filename, gcName, fileOptions, stream)
)
.then((results) => {
// All resolve, do something
const mapArrayToObject = results.reduce((acc, curr) => {
acc[curr.suffix] = {};
acc[curr.suffix].path = curr.path;
acc[curr.suffix].filename = curr.filename;
return acc;
}, {});
cb(null, mapArrayToObject);
return null;
})
.catch(cb);
}
}
MulterSharp.defaultOptions = defaultOptions;
module.exports = (options) => new MulterSharp(options);