lib/storage/put.js
'use strict';
const Vinyl = require('vinyl');
const vfs = require('vinyl-fs');
const sfa = require('stream-from-array');
const map = require('map-stream');
const uuid = require('uuid');
const config = require('config');
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
let adapter = '';
const builtInProps = ['uuid', '_uuid', 'field', '_field', 'type', '_type', 'rel', '_rel'];
try {
// Need to have the require here so it can be dynamic
adapter = require(`vinyl-${config.storage.type}`); // eslint-disable-line global-require
}
catch (e) {
console.error(`Could not find Vinyl Adapter 'vinyl-${config.storage.type}'. Falling back to 'vinyl-fs'.`); // eslint-disable-line no-console
adapter = vfs;
}
/*
* Punchcard File
*
* Custom Vinyl Type
*
* Adds UUID (uuid), field the file came from (field), file MIME type (type), and relative path from pluggable storage root (rel) to standard Vinyl object.
*
* READ - uuid
* READ/WRITE - field, type, rel
*/
class PunchcardFile extends Vinyl {
constructor(options) {
super(options);
this._uuid = uuid.v4();
this._field = '';
this._type = '';
this._rel = '';
}
get uuid() {
return this._uuid;
}
get field() {
return this._field;
}
set field(field) {
if (field) {
this._field = field;
}
}
get type() {
return this._type;
}
set type(type) {
if (type) {
this._type = type;
}
}
get rel() {
return this._rel;
}
set rel(pth) {
if (pth) {
this._rel = pth;
}
}
static isCustomProp(name) {
return super.isCustomProp(name) && builtInProps.indexOf(name) === -1;
}
}
/*
* File DJ
*
* Turns a file in to a PunchcardFile object
*
* @param {object} file - A File object as received from a multi-part form
*
* @returns {object} - A Promise that resolves to a new PunchcardFile, with UUID, Type, Path, and Rel available
*/
const fileDJ = file => {
return new Promise((res, rej) => {
fs.readFile(file.path, (err, data) => {
if (err) {
rej(err);
}
const f = new PunchcardFile({
cwd: '/',
base: '/',
path: `/${file.originalname}`,
contents: data,
});
f.field = file.fieldname;
f.type = file.mimetype;
f.path = `/${f.uuid}${path.extname(file.originalname)}`;
f.rel = `/${f.uuid}${path.extname(file.originalname)}`;
f.original = file.originalname;
res(f);
});
});
};
/**
* Get the storable value for a file
*
* @param {object} file - Vinyl-compatible PunchcardFile object
*
* @returns {object} - contains data to be stored about this file
*/
const value = file => {
const storable = {};
storable.type = _.get(file, 'type', '');
storable.original = _.get(file, 'original', '');
storable.relative = _.get(file, 'rel', '');
return storable;
};
/*
* Punchcard Stream
*
* Takes an array of Vinyl-compatible PunchcardFile objects, streams them through the chosen adapter, collects the results, and wraps the whole thing in a promise.
*
* @param {array} items - Array of PunchcardFile objects
*
* @returns {object} - Keys are file fields, values are the result of the stream transformation
*/
const punchcardStream = items => {
return new Promise((res, rej) => {
const files = {};
sfa.obj(items)
.pipe(adapter.dest(config.storage.dest, config.storage.settings))
.pipe(map((file, cb) => {
files[file.field] = value(file);
cb(null, file);
}))
.on('error', e => {
rej(e);
})
.on('end', () => {
res(files);
});
});
};
/*
* File Put
*
* @param {array} files - An array of file objects as received from a multi-part form
*
* @returns {object} - Results from Punchcard Stream
*/
const put = files => {
const streams = files.map(fileDJ);
return Promise.all(streams).then(items => {
return punchcardStream(items);
});
};
module.exports = put;