landmark-serve
Version:
Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose
375 lines (289 loc) • 7.98 kB
JavaScript
/*!
* Module dependencies.
*/
var _ = require('underscore'),
moment = require('moment'),
landmark = require('../../'),
async = require('async'),
util = require('util'),
knox = require('knox'),
// s3 = require('s3'),
utils = require('landmark-utils'),
super_ = require('../field');
/**
* S3File FieldType Constructor
* @extends Field
* @api public
*/
function s3file(list, path, options) {
this._underscoreMethods = ['format', 'uploadFile'];
// event queues
this._pre = {
upload: []
};
// TODO: implement filtering, usage disabled for now
options.nofilter = true;
// TODO: implement initial form, usage disabled for now
if (options.initial) {
throw new Error('Invalid Configuration\n\n' +
'S3File fields (' + list.key + '.' + path + ') do not currently support being used as initial fields.\n');
}
s3file.super_.call(this, list, path, options);
// validate s3 config (has to happen after super_.call)
if (!this.s3config) {
throw new Error('Invalid Configuration\n\n' +
'S3File fields (' + list.key + '.' + path + ') require the "s3 config" option to be set.\n\n' +
'See http://getlandmarkproject.com/docs/cms/configuration/#services-amazons3 for more information.\n');
}
// Could be more pre- hooks, just upload for now
if (options.pre && options.pre.upload) {
this._pre.upload = this._pre.upload.concat(options.pre.upload);
}
}
/*!
* Inherit from Field
*/
util.inherits(s3file, super_);
/**
* Exposes the custom or landmark s3 config settings
*/
Object.defineProperty(s3file.prototype, 's3config', { get: function() {
return this.options.s3config || landmark.get('s3 config');
}});
/**
* Allows you to add pre middleware after the field has been initialised
*
* @api public
*/
s3file.prototype.pre = function(event, fn) {
if (!this._pre[event]) {
throw new Error('S3File (' + this.list.key + '.' + this.path + ') error: s3field.pre()\n\n' +
'Event ' + event + ' is not supported.\n');
}
this._pre[event].push(fn);
return this;
};
/**
* Registers the field on the List's Mongoose Schema.
*
* @api public
*/
s3file.prototype.addToSchema = function() {
var field = this,
schema = this.list.schema;
var paths = this.paths = {
// fields
filename: this._path.append('.filename'),
path: this._path.append('.path'),
size: this._path.append('.size'),
filetype: this._path.append('.filetype'),
url: this._path.append('.url'),
// virtuals
exists: this._path.append('.exists'),
upload: this._path.append('_upload'),
action: this._path.append('_action')
};
var schemaPaths = this._path.addTo({}, {
filename: String,
path: String,
size: Number,
filetype: String,
url: String
});
schema.add(schemaPaths);
var exists = function(item) {
return (item.get(paths.url) ? true : false);
};
// The .exists virtual indicates whether a file is stored
schema.virtual(paths.exists).get(function() {
return schemaMethods.exists.apply(this);
});
var reset = function(item) {
item.set(field.path, {
filename: '',
path: '',
size: 0,
filetype: '',
url: ''
});
};
var schemaMethods = {
exists: function() {
return exists(this);
},
/**
* Resets the value of the field
*
* @api public
*/
reset: function() {
reset(this);
},
/**
* Deletes the file from S3File and resets the field
*
* @api public
*/
delete: function() {
try {
var client = knox.createClient(field.s3config);
client.deleteFile(this.get(paths.path) + this.get(paths.filename), function(err, res){ res ? res.resume() : false; });
} catch(e) {}
reset(this);
}
};
_.each(schemaMethods, function(fn, key) {
field.underscoreMethod(key, fn);
});
// expose a method on the field to call schema methods
this.apply = function(item, method) {
return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
};
this.bindUnderscoreMethods();
};
/**
* Formats the field value
*
* @api public
*/
s3file.prototype.format = function(item) {
return item.get(this.paths.url);
};
/**
* Detects whether the field has been modified
*
* @api public
*/
s3file.prototype.isModified = function(item) {
return item.isModified(this.paths.url);
};
/**
* Validates that a value for this field has been provided in a data object
*
* @api public
*/
s3file.prototype.validateInput = function(data) {
// TODO - how should file field input be validated?
return true;
};
/**
* Updates the value for this field in the item from a data object
*
* @api public
*/
s3file.prototype.updateItem = function(item, data) {
// TODO - direct updating of data (not via upload)
};
/**
* Uploads the file for this field
*
* @api public
*/
s3file.prototype.uploadFile = function(item, file, update, callback) {
var field = this,
path = field.options.s3path ? field.options.s3path + '/' : '',
prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '',
name = prefix + file.name;
if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){
return callback(new Error('Unsupported File Type: '+file.type));
}
if ('function' == typeof update) {
callback = update;
update = false;
}
var doUpload = function() {
knox.createClient(field.s3config).putFile(file.path, path + name, {
'Content-Type': file.type,
'x-amz-acl': 'public-read'
}, function(err, res) {
if (res) res.resume();
if (err) return callback(err);
var protocol = (field.s3config.protocol && field.s3config.protocol + ':') || '',
url = res.req.url.replace(/^https?:/i, protocol);
var fileData = {
filename: name,
path: path,
size: file.size,
filetype: file.type,
url: url
};
if (update) {
item.set(field.path, fileData);
}
callback(null, fileData);
});
};
async.eachSeries(this._pre.upload, function(fn, next) {
fn(item, file, next);
}, function(err) {
if (err) return callback(err);
doUpload();
});
// Alternative method via S3 module, which provides helpful events for uploading files, leaving for future reference
/*
var client = s3.createClient(landmark.get('s3 config'));
var headers = {
'Content-Type': file.type,
'x-amz-acl': 'public-read'
};
var uploader = client.upload(file.path, file.name, headers);
uploader.on('error', function(res) {
console.log('Error uploading Amazon S3 file:', res.stack);
callback(res.stack);
});
uploader.on('progress', function(amountDone, amountTotal) {
console.log('Amazon S3 file progress: ' + amountDone + ' of ' + amountTotal);
});
uploader.on('end', function(url) {
item.set(field.path, {
filename: file.name,
size: file.size,
filetype: file.type,
url: url
});
callback();
});
*/
};
/**
* Returns a callback that handles a standard form submission for the field
*
* Expected form parts are
* - `field.paths.action` in `req.body` (`clear` or `delete`)
* - `field.paths.upload` in `req.files` (uploads the file to s3file)
*
* @api public
*/
s3file.prototype.getRequestHandler = function(item, req, paths, callback) {
var field = this;
if (utils.isFunction(paths)) {
callback = paths;
paths = field.paths;
} else if (!paths) {
paths = field.paths;
}
callback = callback || function() {};
return function() {
if (req.body) {
var action = req.body[paths.action];
if (/^(delete|reset)$/.test(action))
field.apply(item, action);
}
if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {
return field.uploadFile(item, req.files[paths.upload], true, callback);
}
return callback();
};
};
/**
* Immediately handles a standard form submission for the field (see `getRequestHandler()`)
*
* @api public
*/
s3file.prototype.handleRequest = function(item, req, paths, callback) {
this.getRequestHandler(item, req, paths, callback)();
};
/*!
* Export class
*/
exports = module.exports = s3file;