UNPKG

multer-cloud-storage

Version:
193 lines (159 loc) 5.8 kB
import multer = require('multer'); import { Bucket, CreateWriteStreamOptions, PredefinedAcl, Storage, StorageOptions } from '@google-cloud/storage'; import { v4 as uuid } from 'uuid'; import { encode } from 'urlencode'; import { Request } from 'express'; type GoogleCloudBlobFileReference = { destination?: string, filename: string } export type MulterGoogleCloudStorageOptions = { acl?: PredefinedAcl; bucket?: string; contentType?: ContentTypeFunction | string; destination?: any; filename?: any; filenameEncoding?: boolean, hideFilename?: boolean; uniformBucketLevelAccess?: boolean; }; export default class MulterGoogleCloudStorage implements multer.StorageEngine { private gcsBucket: Bucket; private gcsStorage: Storage; private options: StorageOptions & MulterGoogleCloudStorageOptions; //private blobFile: {destination?: string, filename: string} = { destination: '', filename: '' }; getFilename( req, file, cb ) { if(typeof file.originalname === 'string') cb( null, file.originalname ); else cb( null, `${uuid()}` ); } getDestination( req, file, cb ) { cb( null, '' ); } getContentType( req, file ) { if(typeof file.mimetype === 'string') return file.mimetype; else return undefined; } private getBlobFileReference( req, file ): GoogleCloudBlobFileReference | false { const blobFile: GoogleCloudBlobFileReference = { destination: '', filename: '', }; this.getDestination(req, file, (err, destination) => { if (err) { return false; } var escDestination = ''; escDestination += destination .replace(/^\.+/g, '') .replace(/^\/+|\/+$/g, ''); if (escDestination !== '') { escDestination = escDestination + '/'; } blobFile.destination = escDestination; }); this.getFilename(req, file, (err, filename) => { if (err) { return false; } if (this.options.filenameEncoding) { blobFile.filename = encode(filename .replace(/^\.+/g, '') .replace(/^\/+/g, '') .replace(/\r|\n/g, '_') ); } else { blobFile.filename = filename; } }); return blobFile; } constructor(opts?: StorageOptions & MulterGoogleCloudStorageOptions) { opts = opts || {}; typeof opts.destination === 'string' ? this.getDestination = function (req, file, cb) { cb(null, opts.destination) } : this.getDestination = opts.destination || this.getDestination; if (opts.hideFilename) { this.getFilename = function (req, file, cb) { cb(null, `${uuid()}`) }; this.getContentType = function (req, file) { return undefined }; } else { typeof opts.filename === 'string' ? this.getFilename = function (req, file, cb) { cb(null, opts.filename) } : this.getFilename = opts.filename || this.getFilename; typeof opts.contentType === 'string' ? this.getContentType = function (req, file) { return opts.contentType } : this.getContentType = opts.contentType || this.getContentType; } const bucket = opts.bucket || process.env.GCS_BUCKET || null; const projectId = opts.projectId || process.env.GCLOUD_PROJECT || null; const keyFilename = opts.keyFilename || process.env.GCS_KEYFILE || null; if (!bucket) { throw new Error('You have to specify bucket for Google Cloud Storage to work.'); } if (!projectId) { throw new Error('You have to specify project id for Google Cloud Storage to work.'); } /* * If credentials and keyfile are not defined, Google Storage should appropriately be able to locate the * default credentials for the environment see: https://cloud.google.com/docs/authentication/application-default-credentials#search_order */ this.gcsStorage = new Storage({ ...opts, projectId, keyFilename, }); this.gcsBucket = this.gcsStorage.bucket(bucket); this.options = opts; } _handleFile = (req, file, cb) => { const blobFile = this.getBlobFileReference( req, file ); if(blobFile !== false) { var blobName = blobFile.destination + blobFile.filename; var blob = this.gcsBucket.file(blobName); const streamOpts: CreateWriteStreamOptions = {}; if (!this.options.uniformBucketLevelAccess) { streamOpts.predefinedAcl = this.options.acl || 'private' } const contentType = this.getContentType(req, file); if (contentType) { streamOpts.metadata = {contentType}; } const blobStream = blob.createWriteStream(streamOpts); file.stream.pipe(blobStream) .on('error', (err) => cb(err)) .on('finish', (file) => { const name = blob.metadata.name; const filename = name.substr(name.lastIndexOf('/')+1); cb(null, { bucket: blob.metadata.bucket, destination: blobFile.destination, filename, path: `${blobFile.destination}${filename}`, contentType: blob.metadata.contentType, size: blob.metadata.size, uri: `gs://${blob.metadata.bucket}/${blobFile.destination}${filename}`, linkUrl: `${this.gcsStorage.apiEndpoint}/${blob.metadata.bucket}/${blobFile.destination}${filename}`, selfLink: blob.metadata.selfLink, //metadata: blob.metadata }) }); } } _removeFile = (req, file, cb) => { const blobFile = this.getBlobFileReference( req, file ); if(blobFile !== false) { var blobName = blobFile.destination + blobFile.filename; var blob = this.gcsBucket.file(blobName); blob.delete(); cb(); } }; } export function storageEngine(opts?: StorageOptions & MulterGoogleCloudStorageOptions) { return new MulterGoogleCloudStorage(opts); } export type ContentTypeFunction = (req: Request, file: Express.Multer.File) => string | undefined;