/**
* @fileOverview Provides core promisified helper functions for using minio bucket from Node API
*
* @author Darshit Vora
* @author Manjesh Vinayaka
* @author Yatish Balaji
* @author Anish Lushte
* @author Yogesh Chaudhary
*
* @requires NPM:fs-promise
* @requires NPM:minio
* @requires NPM:bluebird
*/
const fsp = require('fs-promise');
const minio = require('minio');
const Bluebird = require('bluebird');
/**
* Minio Helper
* Initialize Node Minio Object
* @example
* const NodeMinio = require('node-minio');
*
* const config = {
* minio: {
* endPoint: "192.168.23.100",
* accessKey: "SN8JBGY43WPMFT0R56fsdfdsf",
* secretKey: "fdfdfdfd+3R9e/x4V0F6Xpjtfdsfd",
* secure: false,
* port: 8000,
* },
* errorFileUrl: "/sample/errorfile.pdf",
* bucket: "sample"
* }
const Minio = new NodeMinio(config);
* @class
* @constructor
* @param {object} config
* @param {string} bucket
*/
const NodeMinio = function (config) {
this.config = config;
this.bucket = config.bucket;
this.Minio = new minio.Client(this.config.minio);
Bluebird.promisifyAll(Object.getPrototypeOf(this.Minio));
return this.Minio;
};
function qualifyBucket(bucketName) {
let bucket = bucketName;
if (typeof bucket === 'string' && bucket[0] === '/') {
bucket = bucket.slice(1);
}
return bucket.toLowerCase();
}
/**
* Takes and object with 3 or 4 attributes {object, base64String, bucket, buffer}.
* Uploads file in specified minio bucket. This function is internally used by
* base64Upload function
* @example
* const NodeMinio = require('node-minio');
*
* const config = {
* minio: {
endPoint: process.env.MINIO_ENDPOINT,
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY,
secure: false,
port: 8000,
},
errorFileUrl: "/sample/errorfile.pdf"
}
*
* const Minio = new NodeMinio(config);
*
* async function uploadFile(file) {
* const { base64: base64String, filename } = file;
* const extension = filename.split('.').pop();
* const fileName = moment().format('YYYY_MM_DD_hh_mm_ss_a');
* const object = `sampleFile/${fileName}.${extension}`;
* const buffer = Buffer.from(base64String, 'base64');
*
* await Minio.bufferUpload({ object, base64String, buffer });
* return object;
* }
* @memberof NodeMinio
* @param {object} minioObject Contains an object with filepath, bucket and base64string
*
* @returns {object} returns promise object
*/
NodeMinio.prototype.bufferUpload = (minioObject) => {
const minObj = minioObject;
minObj.bucket = minObj.bucket || this.bucket; // Bucket name always in lowercaseObj
return this.Minio.putObjectAsync(
minObj.bucket, minObj.object,
minObj.buffer, 'application/octet-stream',
);
};
/**
* Takes and object with 2 attributes {object, base64String}.
* Asynchronously uploads file on specified path and returns promise
* @example
* const NodeMinio = require('node-minio');
*
* const config = {
* minio: {
endPoint: process.env.MINIO_ENDPOINT,
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY,
secure: false,
port: 8000,
},
errorFileUrl: "/sample/errorfile.pdf"
}
*
* const Minio = new NodeMinio(config);
*
* async function uploadFile(file) {
* const { base64: base64String, filename } = file;
* const extension = filename.split('.').pop();
* const fileName = moment().format('YYYY_MM_DD_hh_mm_ss_a');
* const object = `sampleFile/${fileName}.${extension}`;
*
* await Minio.base64Upload({ object, base64String });
* return object;
* }
* @memberof NodeMinio
* @param {object} minioObject Contains an object with filepath, bucket and base64string
*
* @returns {object} returns promise object
*/
NodeMinio.prototype.base64Upload = (minioObject) => {
const minObj = minioObject;
minObj.buffer = Buffer.from(minioObject.base64String, 'base64');
return this.Minio.bufferUpload(minObj);
};
/**
* Takes and object with 2 attributes {object, bucket, expires}.
* Generates dynamic Minio URL for accessing file with expiry
* @example
*
* const NodeMinio = require('node-minio');
*
* const config = {
* minio: {
endPoint: process.env.MINIO_ENDPOINT,
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY,
secure: false,
port: 8000,
},
errorFileUrl: "/sample/errorfile.pdf"
}
*
* const Minio = new NodeMinio(config);
*
* async function viewFile(fileName) {
* const filePath= 'sampleFile/${fileName}.pdf'
*
* const url = await minio
* .viewLink({
* object: filePath,
* }, false);
* return res.json(url);
* }
* @memberof NodeMinio
* @param {object} minioObject Contains an object with filepath, bucket, expiry time
* @param {boolean} FSCompat Removes forward slash (/) from file path
*
* @returns {object} returns promise object
*/
NodeMinio.prototype.viewLink = (minioObject, FSCompat = false) => {
const minObj = minioObject;
minObj.bucket = minObj.bucket || this.bucket; // Bucket name always in lowercaseObj
minObj.expires = minObj.expires || 24 * 60 * 60; // Expired in one day
if (!minObj.object) {
console.error('Minio: View File not found', minObj);
return Promise.resolve(this.config.errorFileUrl);
}
return this.Minio.statObjectAsync(
minObj.bucket,
FSCompat
? qualifyBucket(minObj.object)
: minObj.object,
)
.then(() => this.Minio
.presignedGetObjectAsync(
minObj.bucket,
FSCompat
? qualifyBucket(minObj.object)
: minObj.object, minObj.expires,
))
.catch((err) => {
console.error('Minio: View File not found', minObj, err);
return this.config.errorFileUrl;
});
};
/**
* Takes an object with 1 attributes {object}.
* Lists all the directories in current folder
* @example
*
* const Minio = require('node-minio');
*
* async function listFilesInDirectory() {
*
* const baseUrl = "/sampleFile";
* let fileList = await Minio.listDirectoryObjects({ object: baseUrl });
* return fileList;
* }
* @memberof NodeMinio
* @param {object} minioObject Contains an object with filepath, bucket, expiry time
* @param {boolean} FSCompat Removes forward slash (/) from file path
*
* @returns {object} returns promise object
*/
NodeMinio.prototype.listDirectoryObjects = async (minioObject) => {
const minObj = minioObject;
minObj.bucket = minObj.bucket || this.bucket; // Bucket name always in lowercaseObj
if (!minObj.object) {
console.error('Minio: View Folder not found', minObj);
return Promise.resolve(this.config.errorFileUrl);
}
return new Promise((res, rej) => {
const objectsStream = this.Minio.listObjects(minObj.bucket, qualifyBucket(minObj.object), false);
const data = [];
objectsStream.on('data', (obj) => {
data.push(obj);
});
objectsStream.on('error', (e) => {
rej(e);
});
objectsStream.on('end', () => {
res(data);
});
});
};
/**
* Takes an object with 2 attributes {object, name}.
* Create a downloadable link for file
* @example
*
* const Minio = require('node-minio');
*
* async function downloadFile(name) {
* const file = {
* object: 'sampleFiles/',
* name: `${name}_${
* moment().format('YYYY-DD-MM')}.${ext}`,
* };
*
* return Minio.downloadLinkBase(file)
.then(downloadLink => res.redirect(downloadLink));
}
* @memberof NodeMinio
* @param {object} minioObject Contains an object with bucket, filepath
* @returns {url} returns url
*/
NodeMinio.prototype.downloadLinkBase = (minioObject) => {
if (!minioObject.name) return console.error('File Name required for download');
const minObj = minioObject;
minObj.bucket = minObj.bucket || this.bucket; // Bucket name always in lowercaseObj
minObj.expires = minObj.expires || 24 * 60 * 60; // Expired in one day
minObj.headers = {
'response-content-disposition':
`attachment; filename="${minObj.name.replace(/[^a-zA-Z0-9-_.]/g, '')}"`,
};
return this.Minio.presignedGetObjectAsync(
minObj.bucket.toLowerCase(), minObj.object,
minObj.expires, minObj.headers,
);
};
/**
* Takes an object with 2 attributes {object, name}.
* Create a downloadable link for file
* @example
*
* const Minio = require('node-minio');
*
* async function downloadFile(name) {
* const file = {
* object: 'sampleFiles/',
* name: `${name}_${
* moment().format('YYYY-DD-MM')}.${ext}`,
* };
*
* return Minio.downloadLink(file)
.then(downloadLink => res.redirect(downloadLink));
}
* @memberof NodeMinio
* @param {object} minioObject Contains an object with bucket, filepath
* @returns {url} returns url
*/
NodeMinio.prototype.downloadLink = (minioObject, qualify = false) => {
const minObj = minioObject;
minObj.bucket = minObj.bucket || this.bucket; // Bucket name always in lowercase
return this.Minio
.statObjectAsync(minObj.bucket, qualify
? qualifyBucket(minObj.object)
: minObj.object)
.then(() => this.Minio.downloadLinkBase(minObj))
.catch((err) => {
console.error('Minio: File not found', minObj, err);
return this.config.errorFileUrl;
});
};
/**
* Takes and object with 2 attributes {object}.
* Used to retry alternative file to be downloaded. Here in tha example below we dont get original file than we search for -rst file and try download it
* @example
*
* const Minio = require('node-minio');
*
* async function retryFileFromMinio(fileName) {
* const retryObject = fileName.path.toLowerCase();
* const name = `${fileName}.pdf`.replace(/ /g, '_');
*
* const url = await Minio
* .retryDownloadLink({
* name,
* retryObject,
* object: retryObject.replace(/\.pdf$/g, '-rst.pdf'),
* });
* }
* @memberof NodeMinio
* @param {object} minioObject Contains an object with filepath
* *
* @returns {object} returns download link
*/
NodeMinio.prototype.retryDownloadLink = (minioObject) => {
const minObj = minioObject;
minObj.bucket = minObj.bucket || this.bucket; // Bucket name always in lowercase
return this.Minio.statObjectAsync(minObj.bucket, qualifyBucket(minObj.object))
.then(() => this.Minio.downloadLinkBase(minObj))
.catch((e) => {
console.error('Minio: retry', minObj, e);
return this.Minio.statObjectAsync(minObj.bucket, qualifyBucket(minObj.retryObject))
.then(() => {
minObj.object = minObj.retryObject;
return this.Minio.downloadLink(minObj);
})
.catch((err) => {
logger.error('Minio: File not found', minObj, err);
return this.config.errorFileUrl;
});
});
};
/**
* Takes and object with 2 attributes {object, bucket, expires}.
* Generates and returns presigned URL for HTTP PUT operations.
* Clients may point to this URL to upload objects directly to a bucket even if it is private.
* This presigned URL can have an associated expiration time in seconds after which the URL is no longer valid.
* The default value is 7 days.
* @example
* https://docs.min.io/docs/upload-files-from-browser-using-pre-signed-urls.html
* https://docs.min.io/docs/javascript-client-api-reference.html#presignedPutObject
* const Minio = require('node-minio');
*
* async function getUploadLink(req,res) {
* const url = await Minio
* .uploadLink({});
* return res.json(url);
* }
* @memberof NodeMinio
* @param {object} minioObject Contains an object with option bucket name and expiry
*
* @returns {object} returns url which can be used to upload file
*/
NodeMinio.prototype.uploadLink = (minioObject) => {
const minObj = minioObject;
minObj.bucket = minObj.bucket || this.bucket; // Bucket name always in lowercaseObj
minObj.expires = minObj.expires || 24 * 60 * 60; // Expired in one day
return this.Minio.presignedPutObjectAsync(minObj.bucket, qualifyBucket(minObj.object), minObj.expires);
};
/**
* Uploads base64 file from temp path(specified path) to bucket
* @example
*
* const Minio = require('node-minio');
*
* async function uploadFromTemp(req, res, file) {
* const extension = (file.name || file.filename).split('.').pop().toLowerCase();
* minioObject = {
* base64String: file.base64,
* temp: file.path,
* object: `sample/${Id}/${Id
* }_${moment().format('DD-MM-YYYY_hh-mm-ss-a')}.${extension}`,
* };
* const file = await Minio
* .uploadTemp(minioObject);
* return res.json(file);
* }
* @memberof NodeMinio
* @param {object} minioObject Contains an object with option bucket
*
* @returns {object} returns promise
*/
NodeMinio.prototype.uploadTemp = (minioObject) => {
const minObj = minioObject;
const fileStream = fsp.createReadStream(minioObject.temp);
return fsp.stat(minioObject.temp).then(stats => this.Minio
.putObjectAsync(this.bucket, minObj.object, fileStream, stats.size, 'application/octet-stream'));
};
/**
* Takes and object with 2 attributes {object, bucket, expires}.
* Uploads multiple base64 files. Used in sync with Multer
*
*/
NodeMinio.prototype.base64UploadMulti = minioObjects => Promise.all(minioObjects.map(m => this.Minio.base64Upload(m)));
/**
* Takes and object with 2 attributes {object, bucket, expires}.
* Copies file from one bucket to another
*/
NodeMinio.prototype.customCopyObject = (minioObject) => {
const conds = new minio.CopyConditions();
const bucket = minioObject.bucket || this.bucket;
this.Minio.copyObject(
bucket,
minioObject.destObj,
`/${bucket}/${minioObject.srcObj}`,
conds,
(e, data) => {
if (e) {
console.error(e);
return e;
}
return data;
},
);
};
/**
* Takes and object with 2 attributes {object, bucket, expires}.
* Gets object from minio as a stream
*
*/
NodeMinio.prototype.getFileStream = minioObject => new Promise(((resolve, reject) => {
this.Minio.getObject(
minioObject.bucket || this.bucket, minioObject.object,
(err, stream) => {
if (err) return reject(err);
return resolve(stream);
},
);
}));
module.exports = NodeMinio;