├── .travis.yml ├── gulpfile.js ├── lib ├── checkFolder.js ├── fileinfo.js ├── configs.js └── transport │ ├── local.js │ └── aws.js ├── .gitignore ├── specs └── fileupload-spec.js ├── index.js ├── package.json └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.12 4 | - 0.10 5 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | jshint = require('gulp-jshint'), 3 | jshintStylish = require('jshint-stylish'), 4 | pkg = require('./package.json'); 5 | 6 | gulp.task('lint',function(){ 7 | 8 | return gulp.src(['index.js','lib/**/*.js']) 9 | .pipe(jshint()) 10 | .pipe(jshint.reporter(jshintStylish)); 11 | 12 | }); 13 | 14 | gulp.task('watch', function() { 15 | gulp.watch(['lib/**/*.js','index.js'],['lint']); 16 | }); 17 | 18 | 19 | gulp.task('default', ['lint','watch']); 20 | -------------------------------------------------------------------------------- /lib/checkFolder.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var mkdirp = require('mkdirp'); 3 | /** 4 | * check if folder exists, otherwise create it 5 | */ 6 | module.exports = function checkExists(dir) { 7 | fs.exists(dir, function(exists) { 8 | if (!exists) { 9 | mkdirp(dir, function(err) { 10 | if (err) console.error(err); 11 | else console.log('The uploads folder was not present, we have created it for you [' + dir + ']'); 12 | }); 13 | //throw new Error(dir + ' does not exists. Please create the folder'); 14 | } 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | tmp 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # Deployed apps should consider commenting this line out: 25 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 26 | node_modules 27 | -------------------------------------------------------------------------------- /lib/fileinfo.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | // Since Node 0.8, .existsSync() moved from path to fs: 8 | var _existsSync = fs.existsSync || path.existsSync; 9 | 10 | var getFileKey = function(filePath) { 11 | 12 | return path.basename(filePath); 13 | 14 | }; 15 | var udf; 16 | 17 | function FileInfo(file, opts, fields) { 18 | this.name = file.name; 19 | this.size = file.size; 20 | this.type = file.type; 21 | this.modified = file.lastMod; 22 | this.deleteType = 'DELETE'; 23 | this.options = opts; 24 | this.key = getFileKey(file.path); 25 | this.versions = {}; 26 | this.proccessed = false; 27 | this.width = udf; 28 | this.height = udf; 29 | this.fields = fields; 30 | if (opts.saveFile) { 31 | this.safeName(); 32 | } 33 | } 34 | FileInfo.prototype.update = function(file) { 35 | this.size = file.size; 36 | }; 37 | FileInfo.prototype.safeName = function() { 38 | var nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/; 39 | 40 | function nameCountFunc(s, index, ext) { 41 | return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || ''); 42 | } 43 | 44 | // Prevent directory traversal and creating hidden system files: 45 | this.name = path.basename(this.name).replace(/^\.+/, ''); 46 | // Prevent overwriting existing files: 47 | while (_existsSync(this.options.uploadDir + '/' + this.name)) { 48 | this.name = this.name.replace(nameCountRegexp, nameCountFunc); 49 | } 50 | }; 51 | 52 | FileInfo.prototype.initUrls = function() { 53 | 54 | var that = this; 55 | var baseUrl = (that.options.useSSL ? 'https:' : 'http:') + '//' + that.options.host + that.options.uploadUrl; 56 | if (this.error) return; 57 | if (!this.awsFile) { 58 | that.url = baseUrl + encodeURIComponent(that.name); 59 | that.deleteUrl = baseUrl + encodeURIComponent(that.name); 60 | if (!that.hasVersionImages()) return; 61 | Object.keys(that.options.imageVersions).forEach(function(version) { 62 | if (_existsSync(that.options.uploadDir + '/' + version + '/' + that.name)) { 63 | that[version + 'Url'] = baseUrl + version + '/' + encodeURIComponent(that.name); 64 | } else { 65 | // create version failed, use the default url as version url 66 | that[version + 'Url'] = that.url; 67 | } 68 | }); 69 | return; 70 | } 71 | 72 | that.url = that.awsFile.url; 73 | that.deleteUrl = that.options.uploadUrl + that.url.split('/')[that.url.split('/').length - 1].split('?')[0]; 74 | if (!that.hasVersionImages()) return; 75 | Object.keys(that.options.imageVersions).forEach(function(version) { 76 | that[version + 'Url'] = that.url; 77 | }); 78 | 79 | }; 80 | 81 | FileInfo.prototype.validate = function() { 82 | if (this.options.minFileSize && this.options.minFileSize > this.size) { 83 | this.error = 'File is too small'; 84 | } else if (this.options.maxFileSize && this.options.maxFileSize < this.size) { 85 | this.error = 'File is too big'; 86 | } else if (!this.options.acceptFileTypes.test(this.name)) { 87 | this.error = 'Filetype not allowed'; 88 | } 89 | return !this.error; 90 | }; 91 | FileInfo.prototype.hasVersionImages = function() { 92 | return (this.options.copyImgAsThumb && this.options.imageTypes.test(this.url)); 93 | }; 94 | 95 | module.exports = FileInfo; 96 | 97 | module.exports.getFileKey = getFileKey; 98 | -------------------------------------------------------------------------------- /specs/fileupload-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | describe('FileInfo package', function() { 3 | var FileInfo = require('../lib/fileinfo.js'); 4 | // TODO - FileInfo default constructor or mock parameters 5 | 6 | it('should provide a safe name for new files'); 7 | 8 | it('should generate URLs for the files'); 9 | 10 | it('should check against certain rules'); 11 | 12 | it('should check or create folders'); 13 | }); 14 | 15 | describe('AWS transport package', function() { 16 | var uploadFileAWS = require('../lib/transport/aws.js'); 17 | }); 18 | 19 | describe('Uploader configuration', function() { 20 | var options; 21 | var uploader; 22 | 23 | beforeEach(function() { 24 | // TODO - Create a mock object for the filesystem 25 | uploader = require('../index'); 26 | }); 27 | 28 | it('can require configs without error',function(){ 29 | var configs = require('../lib/configs.js'); 30 | expect(configs).not.toBe(null); 31 | }); 32 | 33 | it('should have default config values', function() { 34 | options = {}; 35 | expect(uploader(options).config).toBeDefined(); 36 | }); 37 | 38 | it('should support the local filesystem', function() { 39 | options = { 40 | tmpDir: 'tmp/foo', 41 | uploadDir: 'tmp/bar' 42 | }; 43 | expect(uploader(options).config.tmpDir).toEqual('tmp/foo'); 44 | expect(uploader(options).config.uploadDir).toEqual('tmp/bar'); 45 | }); 46 | 47 | it('should support Amazon Simple Storage Service', function() { 48 | var awsConfig = { 49 | type: 'aws', 50 | aws: { 51 | accessKeyId: 'sesame', 52 | secretAccessKey: 'open', 53 | region: 'us-west-2', 54 | bucketName: 'ali-baba', 55 | acl: 'private' 56 | } 57 | }; 58 | options = { 59 | storage: awsConfig 60 | }; 61 | expect(uploader(options).config.storage).toEqual(awsConfig); 62 | }); 63 | 64 | it('should support thumbnails generation', function() { 65 | var thumbsConfig = { 66 | maxWidth: 200, 67 | maxHeight: 'auto', 68 | large: { 69 | width: 600, 70 | height: 600 71 | }, 72 | medium: { 73 | width: 300, 74 | height: 300 75 | }, 76 | small: { 77 | width: 150, 78 | height: 150 79 | } 80 | } 81 | options = { 82 | imageVersions: thumbsConfig 83 | }; 84 | var obj = uploader(options); 85 | 86 | expect(obj.config.imageVersions.thumbnail.width).toEqual(thumbsConfig.maxWidth); 87 | expect(obj.config.imageVersions.thumbnail.height).toEqual(thumbsConfig.maxHeight); 88 | expect(obj.config.imageVersions.large).toEqual(thumbsConfig.large); 89 | expect(obj.config.imageVersions.medium).toEqual(thumbsConfig.medium); 90 | expect(obj.config.imageVersions.small).toEqual(thumbsConfig.small); 91 | }); 92 | 93 | it('should allow disabling thumbnails', function() { 94 | options = { 95 | copyImgAsThumb: true 96 | }; 97 | expect(uploader(options).config.copyImgAsThumb).toBe(true); 98 | }); 99 | 100 | it('should support SSL', function() { 101 | options = { 102 | useSSL: true 103 | }; 104 | expect(uploader(options).config.useSSL).toBe(true); 105 | }); 106 | }); 107 | 108 | describe('Uploader REST services', function() { 109 | var options = {}; 110 | var uploader = require('../index')(options); 111 | 112 | it('should provide a GET method', function() { 113 | expect(uploader.get).toBeDefined(); 114 | expect(uploader.get.length).toEqual(3); 115 | }); 116 | 117 | it('should provide a POST method', function() { 118 | expect(uploader.post).toBeDefined(); 119 | expect(uploader.post.length).toEqual(3); 120 | }); 121 | 122 | it('should provide a DELETE method', function() { 123 | expect(uploader.delete).toBeDefined(); 124 | expect(uploader.delete.length).toEqual(3); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | var FileInfo = require('./lib/fileinfo.js'); 5 | var configs = require('./lib/configs.js'); 6 | var formidable = require('formidable'); 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | 10 | module.exports = uploadService; 11 | 12 | function uploadService(opts) { 13 | var options = configs.apply(opts); 14 | var transporter = options.storage.type === 'local' ? require('./lib/transport/local.js') : require('./lib/transport/aws.js'); 15 | 16 | transporter = transporter(options); 17 | 18 | var fileUploader = {}; 19 | 20 | fileUploader.config = options; 21 | 22 | function setNoCacheHeaders(res) { 23 | res.setHeader('Pragma', 'no-cache'); 24 | res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); 25 | res.setHeader('Content-Disposition', 'inline; filename="files.json"'); 26 | } 27 | 28 | fileUploader.get = function(req, res, callback) { 29 | this.config.host = req.headers.host; 30 | setNoCacheHeaders(res); 31 | transporter.get(callback); 32 | }; 33 | 34 | fileUploader.post = function(req, res, callback) { 35 | setNoCacheHeaders(res); 36 | var form = new formidable.IncomingForm(); 37 | var tmpFiles = []; 38 | var files = []; 39 | var map = {}; 40 | var fields = {}; 41 | var redirect; 42 | 43 | this.config.host = req.headers.host; 44 | 45 | var configs = this.config; 46 | 47 | req.body = req.body || {}; 48 | 49 | function finish(error, fileInfo) { 50 | 51 | if (error) return callback(error, { 52 | files: files 53 | }, redirect); 54 | 55 | if (!fileInfo) return callback(null, { 56 | files: files 57 | }, redirect); 58 | 59 | var allFilesProccessed = true; 60 | 61 | files.forEach(function(file, idx) { 62 | allFilesProccessed = allFilesProccessed && file.proccessed; 63 | }); 64 | 65 | if (allFilesProccessed) { 66 | callback(null, { 67 | files: files 68 | }, redirect); 69 | } 70 | } 71 | 72 | form.uploadDir = configs.tmpDir; 73 | 74 | form.on('fileBegin', function(name, file) { 75 | tmpFiles.push(file.path); 76 | // fix #41 77 | configs.saveFile = true; 78 | var fileInfo = new FileInfo(file, configs, fields); 79 | map[fileInfo.key] = fileInfo; 80 | files.push(fileInfo); 81 | }).on('field', function(name, value) { 82 | fields[name] = value; 83 | if (name === 'redirect') { 84 | redirect = value; 85 | } 86 | }).on('file', function(name, file) { 87 | var fileInfo = map[FileInfo.getFileKey(file.path)]; 88 | fileInfo.update(file); 89 | if (!fileInfo.validate()) { 90 | finish(fileInfo.error); 91 | fs.unlink(file.path); 92 | return; 93 | } 94 | 95 | transporter.post(fileInfo, file, finish); 96 | 97 | }).on('aborted', function() { 98 | finish('aborted'); 99 | tmpFiles.forEach(function(file) { 100 | fs.unlink(file); 101 | }); 102 | }).on('error', function(e) { 103 | console.log('form.error', e); 104 | finish(e); 105 | }).on('progress', function(bytesReceived) { 106 | if (bytesReceived > configs.maxPostSize) { 107 | req.connection.destroy(); 108 | } 109 | }).on('end', function() { 110 | //if (configs.storage.type == 'local') { 111 | // finish(); 112 | //} 113 | }).parse(req); 114 | }; 115 | 116 | fileUploader.delete = function(req, res, callback) { 117 | transporter.delete(req, res, callback); 118 | }; 119 | 120 | return fileUploader; 121 | } 122 | -------------------------------------------------------------------------------- /lib/configs.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | var checkExists = require('./checkFolder.js'); 3 | 4 | var options = { 5 | tmpDir: __dirname + '/tmp', 6 | uploadDir: __dirname + '/public/files', 7 | uploadUrl: '/files/', 8 | maxPostSize: 11000000000, // 11 GB 9 | minFileSize: 1, 10 | maxFileSize: 10000000000, // 10 GB 11 | acceptFileTypes: /.+/i, 12 | copyImgAsThumb: true, 13 | useSSL: false, 14 | UUIDRegex: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/, 15 | // Files not matched by this regular expression force a download dialog, 16 | // to prevent executing any scripts in the context of the service domain: 17 | inlineFileTypes: /\.(gif|jpe?g|png)/i, 18 | imageTypes: /\.(gif|jpe?g|png)/i, 19 | imageVersions: { 20 | 'thumbnail': { 21 | width: 99, 22 | height: 'auto' 23 | } 24 | }, 25 | accessControl: { 26 | allowOrigin: '*', 27 | allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE', 28 | allowHeaders: 'Content-Type, Content-Range, Content-Disposition' 29 | }, 30 | storage: { 31 | type: 'local', 32 | aws: { 33 | accessKeyId: null, 34 | secretAccessKey: null, 35 | region: null, 36 | bucketName: null, 37 | acl: 'public-read' 38 | } 39 | }, 40 | /** 41 | * apply custom options to the default options 42 | */ 43 | apply:function(opts){ 44 | 45 | opts = opts || {}; 46 | 47 | for(var c in opts ){ 48 | if( this.specialApply(c,opts[c]) ) continue; 49 | this[c] = opts[c]; 50 | } 51 | 52 | this.validate(); 53 | 54 | return this; 55 | 56 | }, 57 | 58 | specialApply:function(key,opt){ 59 | //special properties that cann't apply simple copy 60 | var specialProperties = { 61 | 'imageVersions':true, 62 | 'accessControl':true, 63 | 'storage':true 64 | }; 65 | 66 | if(!specialProperties[key]) return false; 67 | 68 | switch(key){ 69 | case 'imageVersions': 70 | this.imageVersions.thumbnail.width = opt.maxWidth || this.imageVersions.thumbnail.width; 71 | this.imageVersions.thumbnail.height = opt.maxHeight || this.imageVersions.thumbnail.height; 72 | Object.keys(opt).forEach(function(version) { 73 | if (version != 'maxHeight' && version != 'maxWidth') { 74 | options.imageVersions[version] = opt[version]; 75 | } 76 | }); 77 | 78 | break; 79 | 80 | case 'accessControl': 81 | for(var c in opt){ 82 | this.accessControl[c] = opt[c]; 83 | } 84 | break; 85 | 86 | case 'storage': 87 | this.storage.type = opt.type || this.storage.type; 88 | if(opt.aws){ 89 | for(var c1 in opt.aws){ 90 | this.storage.aws[c1] = opt.aws[c1]; 91 | } 92 | } 93 | 94 | break; 95 | 96 | }//switch 97 | 98 | return true; 99 | }, 100 | 101 | validate:function(){ 102 | if ( this.storage.type === 'local' ) { 103 | checkExists(options.tmpDir); 104 | checkExists(options.uploadDir); 105 | if (options.copyImgAsThumb) { 106 | Object.keys(options.imageVersions).forEach(function(version) { 107 | checkExists(options.uploadDir + '/' + version); 108 | }); 109 | } 110 | } 111 | 112 | if( this.storage.type === 'aws') { 113 | 114 | if (!this.storage.aws.accessKeyId || !this.storage.aws.secretAccessKey || !this.storage.aws.bucketName) { 115 | throw new Error('Please enter valid AWS S3 details'); 116 | } 117 | } 118 | 119 | } 120 | }; 121 | 122 | /* 123 | * default configurations 124 | */ 125 | module.exports = options; 126 | -------------------------------------------------------------------------------- /lib/transport/local.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | var fs = require('fs'); 3 | var FileInfo = require('../fileinfo.js'); 4 | var lwip = require('lwip'); 5 | var path = require('path'); 6 | var async = require('async'); 7 | 8 | module.exports = function(opts) { 9 | 10 | var api = { 11 | options: opts, 12 | /** 13 | * get files 14 | */ 15 | get: function(callback) { 16 | var files = [], 17 | options = this.options; 18 | // fix #41 19 | options.saveFile = false; 20 | fs.readdir(options.uploadDir, function(err, list) { 21 | list.forEach(function(name) { 22 | var stats = fs.statSync(options.uploadDir + '/' + name); 23 | if (stats.isFile() && name[0] !== '.') { 24 | var fileInfo = new FileInfo({ 25 | name: name, 26 | size: stats.size, 27 | lastMod: stats.mtime 28 | }, options); 29 | fileInfo.initUrls(); 30 | files.push(fileInfo); 31 | } 32 | }); 33 | callback(null, { 34 | files: files 35 | }); 36 | }); 37 | }, 38 | proccessVersionFile: function(versionObj, cbk) { 39 | 40 | var options = api.options; 41 | var retVal = versionObj; 42 | 43 | lwip.open(options.uploadDir + '/' + versionObj.fileInfo.name, function(error, image) { 44 | 45 | if (error) return cbk(error, versionObj.version); 46 | 47 | //update pics width and height 48 | if (!retVal.fileInfo.width) { 49 | retVal.fileInfo.width = image.width() || 50; //incase we don't get a valid width 50 | retVal.fileInfo.height = image.height() || retVal.fileInfo.width; 51 | } 52 | 53 | var opts0 = options.imageVersions[versionObj.version]; 54 | if (opts0.height == 'auto') { 55 | 56 | retVal.width = opts0.width; 57 | retVal.height = (opts0.width / retVal.fileInfo.width) * retVal.fileInfo.height; 58 | image.batch().resize(opts0.width, retVal.height).writeFile(options.uploadDir + '/' + versionObj.version + '/' + versionObj.fileInfo.name, function(err) { 59 | if (err) { 60 | cbk(err, retVal); 61 | return; 62 | } 63 | cbk(null, retVal); 64 | }); 65 | return; 66 | } 67 | retVal.width = opts0.width; 68 | retVal.height = opts0.height; 69 | image.batch().resize(opts0.width, opts0.height).writeFile(options.uploadDir + '/' + versionObj.version + '/' + versionObj.fileInfo.name, function(err) { 70 | if (err) { 71 | return cbk(err, retVal); 72 | } 73 | cbk(null, retVal); 74 | }); 75 | 76 | }); 77 | }, 78 | post: function(fileInfo, file, finish) { 79 | 80 | var me = this, 81 | options = this.options, 82 | versionFuncs = []; 83 | 84 | 85 | fs.renameSync(file.path, options.uploadDir + '/' + fileInfo.name); 86 | 87 | if ((!options.copyImgAsThumb) || (!options.imageTypes.test(fileInfo.name))) { 88 | fileInfo.initUrls(); 89 | fileInfo.proccessed = true; 90 | return finish(null, fileInfo); 91 | } 92 | 93 | 94 | Object.keys(options.imageVersions).forEach(function(version) { 95 | 96 | versionFuncs.push({ 97 | version: version, 98 | fileInfo: fileInfo 99 | }); 100 | 101 | }); 102 | 103 | 104 | async.map(versionFuncs, me.proccessVersionFile, function(err, results) { 105 | 106 | results.forEach(function(v, i) { 107 | fileInfo.versions[v.version] = { 108 | err: v.err, 109 | width: v.width, 110 | height: v.height 111 | }; 112 | }); 113 | fileInfo.initUrls(); 114 | fileInfo.proccessed = true; 115 | finish(err, fileInfo); 116 | 117 | }); 118 | 119 | 120 | }, 121 | delete: function(req, res, callback) { 122 | var options = this.options; 123 | var fileName = ''; 124 | if (req.url.slice(0, options.uploadUrl.length) === options.uploadUrl) { 125 | fileName = path.basename(decodeURIComponent(req.url)); 126 | if (fileName[0] !== '.') { 127 | fs.unlink(options.uploadDir + '/' + fileName, function(ex) { 128 | Object.keys(options.imageVersions).forEach(function(version) { 129 | // TODO - Missing callback 130 | fs.unlink(options.uploadDir + '/' + version + '/' + fileName); 131 | }); 132 | callback(null, { 133 | success: true 134 | }); 135 | }); 136 | return; 137 | } 138 | } 139 | callback(new Error('File name invalid:' + fileName), null); 140 | } 141 | }; 142 | 143 | return api; 144 | 145 | }; 146 | -------------------------------------------------------------------------------- /lib/transport/aws.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | var AWS = require('aws-sdk'); 6 | var FileInfo = require('../fileinfo.js'); 7 | 8 | /** 9 | * AWS transport 10 | * 11 | * @param {Object} opts 12 | * @param {Object} opts.storage 13 | * @param {Object} opts.storage.aws 14 | * @param {string} opts.storage.aws.accessKeyId 15 | * @param {string} opts.storage.aws.secretAccessKey 16 | * @param {string} opts.storage.aws.region 17 | * @param {string} opts.storage.aws.bucketName 18 | * @param {string} opts.storage.aws.acl 19 | * @param {string} [opts.storage.aws.cacheControl] - Sets the S3 CacheControl 20 | * param. 21 | * @param {Number} [opts.storage.aws.expiresInMilliseconds] - Sets the S3 22 | * Expires param with expiresInMilliseconds from the current time 23 | * @param {boolean} [opts.storage.aws.getSignedUrl=true] - If set to true, the 24 | * upload callback will pass a signed URL of the file, that will expire in 25 | * signedUrlExpiresSeconds if set (default 900s = 15m). If set to false, the 26 | * callback will pass the actual URL. More info about the signed URL here: 27 | * http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property 28 | * @param {boolean} [opts.storage.aws.signedUrlExpiresSeconds=900] - For use 29 | * with getSignedUrl=true. 30 | * @param {string} [opts.storage.aws.path] - Path on bucket to store uploads 31 | * 32 | * @example 33 | * awsTransport({ 34 | * storage: { 35 | * type: 'aws', 36 | * aws: { 37 | * accessKeyId: '...', 38 | * secretAccessKey: '...', 39 | * region: 'us-west-2', 40 | * bucketName: '...', 41 | * acl: 'public-read', 42 | * cacheControl: 'max-age=630720000, public', 43 | * expiresInMilliseconds: 63072000000, 44 | * getSignedUrl: false, 45 | * path: 'uploads/' 46 | * } 47 | * } 48 | * }); 49 | */ 50 | module.exports = function (opts){ 51 | 52 | var configs = opts.storage.aws; 53 | 54 | // init aws 55 | AWS.config.update({ 56 | accessKeyId: configs.accessKeyId, 57 | secretAccessKey: configs.secretAccessKey 58 | }); 59 | if (configs.region) AWS.config.region = configs.region; 60 | 61 | var api = { 62 | s3:new AWS.S3({computeChecksums:true}), 63 | configs:configs, 64 | options:opts, 65 | upload:function(fileName,filePath,callback){ 66 | uploadFile(this.s3,fileName,filePath,this.configs,callback); 67 | }, 68 | /** 69 | * get files 70 | */ 71 | get:function(callback){ 72 | var params = { 73 | Bucket: api.configs.bucketName // required 74 | //Delimiter: 'STRING_VALUE', 75 | //EncodingType: 'url', 76 | //Marker: 'STRING_VALUE', 77 | //MaxKeys: 0, 78 | //Prefix: 'STRING_VALUE', 79 | }; 80 | var files = []; 81 | var options = this.options; 82 | api.s3.listObjects(params, function(err, data) { 83 | if (err) { 84 | console.log(err, err.stack); 85 | return callback(err); 86 | } 87 | data.Contents.forEach(function(o) { 88 | var sss = { 89 | url: (options.useSSL ? 'https:' : 'http:') + '//s3.amazonaws.com/' + configs.bucketName + '/' + o.Key 90 | }; 91 | var fileInfo = new FileInfo({ 92 | name: options.UUIDRegex.test(o.Key) ? o.Key.split('__')[1] : o.Key, 93 | size: o.Size, 94 | awsFile:sss 95 | }, options); 96 | fileInfo.initUrls(); 97 | files.push(fileInfo); 98 | 99 | }); 100 | callback(null,{files: files}); 101 | }); 102 | }, 103 | post:function(fileInfo,file,finish){ 104 | 105 | this.upload(fileInfo.name, file.path, function(error,awsFile) { 106 | if(!error){ 107 | fileInfo.awsFile = awsFile; 108 | fileInfo.proccessed = true; 109 | fileInfo.initUrls(); 110 | } 111 | finish(error,fileInfo); 112 | }); 113 | }, 114 | delete:function(req,res,callback){ 115 | var options = api.options; 116 | var params = { 117 | Bucket: options.storage.aws.bucketName, // required 118 | Key: decodeURIComponent(req.url.split('/')[req.url.split('/').length - 1]) // required 119 | }; 120 | console.log(params); 121 | this.s3.deleteObject(params, function(err, data) { 122 | if (err) { 123 | console.log(err, err.stack); 124 | return callback(err); 125 | } 126 | 127 | console.log(data); // successful response 128 | callback(null,data); 129 | }); 130 | 131 | } 132 | }; 133 | 134 | return api; 135 | 136 | }; 137 | 138 | // AWS Random UUID 139 | /* https://gist.github.com/jed/982883#file-index-js */ 140 | function b(a) { 141 | return a ? (a ^ Math.random() * 16 >> a / 4).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, b); 142 | } 143 | 144 | function getContentTypeByFile(fileName) { 145 | var rc = 'application/octet-stream'; 146 | var fn = fileName.toLowerCase(); 147 | 148 | if (fn.indexOf('.html') >= 0) rc = 'text/html'; 149 | else if (fn.indexOf('.css') >= 0) rc = 'text/css'; 150 | else if (fn.indexOf('.json') >= 0) rc = 'application/json'; 151 | else if (fn.indexOf('.js') >= 0) rc = 'application/x-javascript'; 152 | else if (fn.indexOf('.png') >= 0) rc = 'image/png'; 153 | else if (fn.indexOf('.jpg') >= 0) rc = 'image/jpg'; 154 | 155 | return rc; 156 | } 157 | 158 | function uploadFile(s3, fileName, filePath, opts, callback) { 159 | var fileBuffer = fs.readFileSync(filePath); 160 | var metaData = getContentTypeByFile(fileName); 161 | var remoteFilename = b() + '__' + fileName; 162 | var params = { 163 | ACL: opts.acl, 164 | Bucket: opts.bucketName, 165 | Key: (opts.path || '') + remoteFilename, 166 | Body: fileBuffer, 167 | ContentType: metaData 168 | }; 169 | 170 | // consider setting params.CacheControl by default to 'max-age=630720000, public' 171 | if (typeof opts.cacheControl !== 'undefined') { 172 | params.CacheControl = opts.cacheControl; 173 | } 174 | 175 | // consider setting params.Expires by default to new Date(Date.now() + 63072000000) 176 | if (typeof opts.expiresInMilliseconds !== 'undefined') { 177 | params.Expires = new Date(Date.now() + opts.expiresInMilliseconds); 178 | } 179 | 180 | s3.putObject(params, function(error) { 181 | var url; 182 | 183 | if (typeof opts.getSignedUrl === 'undefined' || opts.getSignedUrl === true) { 184 | 185 | // getSignedUrl documentation 186 | // http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property 187 | url = s3.getSignedUrl('getObject', { 188 | Bucket: opts.bucketName, 189 | Key: remoteFilename, 190 | Expires: opts.signedUrlExpires || 900 191 | }); 192 | } else { 193 | url = s3.endpoint.href + opts.bucketName + '/' + opts.path + remoteFilename; 194 | } 195 | 196 | callback(error,{ url: url}); 197 | }); 198 | } 199 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-file-upload-expressjs", 3 | "version": "0.4.5", 4 | "description": "jQuery File Upload using Expressjs : 'borrowed' from Blueimp jQuery File Upload developed by Sebastian Tschan", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jasmine-node specs/" 8 | }, 9 | "dependencies": { 10 | "async": "^0.9.0", 11 | "aws-sdk": "^2.1.20", 12 | "formidable": "^1.0.17", 13 | "lwip": "~0.0.8", 14 | "mkdirp": "0.5.0" 15 | }, 16 | "devDependencies": { 17 | "gulp": "^3.8.11", 18 | "gulp-jshint": "^1.9.4", 19 | "jasmine-node": "1.14.2", 20 | "jshint-stylish": "^1.0.1" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/arvindr21/blueimp-file-upload-expressjs.git" 25 | }, 26 | "keywords": [ 27 | "jQuery", 28 | "file", 29 | "upload", 30 | "expressjs" 31 | ], 32 | "author": { 33 | "name": "Arvind Ravulavaru" 34 | }, 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/arvindr21/blueimp-file-upload-expressjs/issues" 38 | }, 39 | "homepage": "https://github.com/arvindr21/blueimp-file-upload-expressjs", 40 | "gitHead": "eb370db8c5c79475a56d25972dc33e2172b0a4ef", 41 | "readme": "# Blueimp file upload for Express js\n\n[![Build Status](https://travis-ci.org/arvindr21/blueimp-file-upload-expressjs.svg?branch=master)](https://travis-ci.org/arvindr21/blueimp-file-upload-expressjs) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/arvindr21/blueimp-file-upload-expressjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n\n[![NPM](https://nodei.co/npm/blueimp-file-upload-expressjs.png?downloads=true)](https://nodei.co/npm/blueimp-file-upload-expressjs/)\n\nA simple express module for integrating the *[jQuery File Upload](http://blueimp.github.io/jQuery-File-Upload/)* frontend plugin.\n\n[Fullstack Demo](http://expressjs-fileupload.cloudno.de/) | [Tutorial on my blog](http://thejackalofjavascript.com/uploading-files-made-fun)\n\n## History\nThe code was forked from a sample backend code from the [plugin's repo](https://github.com/blueimp/jQuery-File-Upload/tree/master/server/node). Adaptations were made to show how to use this plugin with the popular *[Express](http://expressjs.com/)* *Node.js* framework.\n\nAlthough this code was initially meant for educational purposes, enhancements were made. Users can additionally:\n\n* choose the destination filesystem, local or cloud-based *Amazon S3*,\n* create thumbnail without heavy external dependencies using [lwip](https://www.npmjs.com/package/lwip),\n* setup server-side rules by [configuration](#Configuration),\n* modify the code against a [test harness](#Tests). \n\n## Installation\n\nSetup an *Express* project and install the package.\n\n```js\n$ npm install blueimp-file-upload-expressjs\n```\n\nBeginners can follow the [tutorial](http://thejackalofjavascript.com/uploading-files-made-fun) for detailed instructions.\n\n## Tests\n\nUnit tests can be run with *Jasmine* using `npm test` or this command:\n```js\n$ jasmine-node specs/\n```\n\nManual end to end tests can be done with [this full project](https://github.com/arvindr21/expressjs-fileupload). Change the `require()` path of [`uploadManager.js`](https://github.com/arvindr21/expressjs-fileupload/blob/master/routes/uploadManager.js#L29) to link it this cloned repository.\n\n## Configuration\n```js\noptions = {\n tmpDir: __dirname + '/tmp', // tmp dir to upload files to\n uploadDir: __dirname + '/public/files', // actual location of the file\n uploadUrl: '/files/', // end point for delete route \n maxPostSize: 11000000000, // 11 GB\n minFileSize: 1,\n maxFileSize: 10000000000, // 10 GB\n acceptFileTypes: /.+/i,\n inlineFileTypes: /\\.(gif|jpe?g|png)/i,\n imageTypes: /\\.(gif|jpe?g|png)/i,\n copyImgAsThumb : true, // required\n imageVersions :{\n maxWidth : 200,\n maxHeight : 200\n },\n accessControl: {\n allowOrigin: '*',\n allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE',\n allowHeaders: 'Content-Type, Content-Range, Content-Disposition'\n },\n storage : {\n type : 'local', // local or aws\n aws : {\n accessKeyId : 'xxxxxxxxxxxxxxxxx', // required if aws\n secretAccessKey : 'xxxxxxxxxxxxxxxxxxxxxxx', // required if aws\n region : 'us-west-2', //make sure you know the region, else leave this option out\n bucketName : 'xxxxxxxxx' // required if aws\n }\n }\n};\n\n```\n### Usage with options \n(*refer tutorial*)\n```js\n// config the uploader\nvar options = {\n tmpDir: __dirname + '/../public/uploaded/tmp',\n uploadDir: __dirname + '/../public/uploaded/files',\n uploadUrl: '/uploaded/files/',\n maxPostSize: 11000000000, // 11 GB\n minFileSize: 1,\n maxFileSize: 10000000000, // 10 GB\n acceptFileTypes: /.+/i,\n // Files not matched by this regular expression force a download dialog,\n // to prevent executing any scripts in the context of the service domain:\n inlineFileTypes: /\\.(gif|jpe?g|png)/i,\n imageTypes: /\\.(gif|jpe?g|png)/i,\n copyImgAsThumb : true, // required\n imageVersions :{\n maxWidth : 200,\n maxHeight : 200\n },\n accessControl: {\n allowOrigin: '*',\n allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE',\n allowHeaders: 'Content-Type, Content-Range, Content-Disposition'\n },\n storage : {\n type : 'aws',\n aws : {\n accessKeyId : 'xxxxxxxxxxxxxxxxx',\n secretAccessKey : 'xxxxxxxxxxxxxxxxx',\n region : 'us-east-1',//make sure you know the region, else leave this option out\n bucketName : 'xxxxxxxxxxxxxxxxx'\n }\n }\n};\n\n// init the uploader\nvar uploader = require('blueimp-file-upload-expressjs')(options);\n\n\nmodule.exports = function (router) {\n router.get('/upload', function(req, res) {\n uploader.get(req, res, function (err,obj) {\n if(!err){\n res.send(JSON.stringify(obj));\n }\n });\n \n });\n\n router.post('/upload', function(req, res) {\n uploader.post(req, res, function (error,obj, redirect) {\n if(!error)\n {\n res.send(JSON.stringify(obj)); \n }\n });\n \n });\n \n // the path SHOULD match options.uploadUrl\n router.delete('/uploaded/files/:name', function(req, res) {\n uploader.delete(req, res, function (err,obj) {\n res.Json({error:err}); \n });\n \n });\n return router;\n}\n```\n### SSL Support\n\nSet the `useSSL` option to `true` to use the package with an [HTTPS server](http://expressjs.com/4x/api.html#app.listen).\n```js\nvar express = require('express')\nvar fs = require('fs')\nvar https = require('https');\n\nvar app = express()\n\n// config the uploader\nvar options = {\n ...\n useSSL: true\n ...\n};\n\n// init the uploader\nvar uploader = require('blueimp-file-upload-expressjs')(options);\n\napp.get('/upload', function(req, res) {\n uploader.get(req, res, function (err,obj) {\n if(!err)\n res.send(JSON.stringify(obj)); \n})\n .post('/upload', // ...\n .delete('/uploaded/files/:name', // ...\n\n// create the HTTPS server\nvar app_key = fs.readFileSync('key.pem');\nvar app_cert = fs.readFileSync('cert.pem');\n\nhttps.createServer({key: app_key, cert: app_cert}, app).listen(443);\n\n```\n\n### Multiple thumbnails\n\nTo generate multiple thumbnails while uploading\n\n```js\nvar options = {\n tmpDir: __dirname + '/../public/uploaded/tmp',\n uploadDir: __dirname + '/../public/uploaded/files',\n uploadUrl: '/uploaded/files/',\n copyImgAsThumb: true, // required\n imageVersions: {\n maxWidth: 200,\n maxHeight: 200\n },\n storage: {\n type: 'local'\n }\n};\n```\n`copyImgAsThumb` needs to be set to true. `imageVersions`, `maxWidth` and `maxHeight` will by default create a `thumbnail` folder and place the specified width/height thumbnail in it.\n \nOptionally, you can omit the `maxHeight`. In this case, it will be resize proportionally to the specified width. \n\n```js\nimageVersions: {\n maxWidth: 200\n },\n```\n\nalso\n\n```js\nimageVersions: {\n maxWidth: 200,\n maxHeight : 'auto'\n },\n```\nPS : `auto` value works only with height.\n\nYou can also specify multiple thumbnail generations like \n\n```js\nvar options = {\n tmpDir: __dirname + '/../public/uploaded/tmp',\n uploadDir: __dirname + '/../public/uploaded/files',\n uploadUrl: '/uploaded/files/',\n copyImgAsThumb: true,\n imageVersions: {\n maxWidth: 200,\n maxHeight: 'auto',\n \"large\" : {\n width : 600,\n height : 600\n },\n \"medium\" : {\n width : 300,\n height : 300\n },\n \"small\" : {\n width : 150,\n height : 150\n }\n },\n storage: {\n type: 'local'\n }\n};\n```\n\n## Contributions\n\nChanges and improvements are welcome! Feel free to fork and open a pull request.\n\n### To Do\n* Make [Configuration](#Configuration) documentation clearer and shorter,\n* Refactor code to build tests and provide generic transports as in `winston`, \n* Write end to end tests with [WebdriverIO](http://webdriver.io/),\n* Provide a basic image processing pipeline (resizing, croping, filter effects),\n* Provide access to other cloud-based services like *Microsoft Azure*.\n\n### Done\n\n* ~~Fix Thumbnail creation when uploading images with a more 'feasible' approach~~,\n* ~~Amazon S3 integration~~,\n* ~~SSL Support~~.\n\n***\n## License\n\n*blueimp-file-upload-expressjs* is licensed under the [MIT licence](http://opensource.org/licenses/MIT).\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n", 42 | "readmeFilename": "README.md", 43 | "_id": "blueimp-file-upload-expressjs@1.0.0", 44 | "_shasum": "7f70770aad4683883dcf170c744a89cd7809aa47", 45 | "_from": "../../../../../../../var/folders/n0/znrvzzk10tbd6hy4cdvnpj5h0000gn/T/npm-1132-d71a7db7/1426387788006-0.8811725666746497/eb370db8c5c79475a56d25972dc33e2172b0a4ef", 46 | "_resolved": "git+https://github.com/mamboer/blueimp-file-upload-expressjs#eb370db8c5c79475a56d25972dc33e2172b0a4ef" 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blueimp file upload for Express js 2 | 3 | [![Build Status](https://travis-ci.org/arvindr21/blueimp-file-upload-expressjs.svg?branch=master)](https://travis-ci.org/arvindr21/blueimp-file-upload-expressjs) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/arvindr21/blueimp-file-upload-expressjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | [![NPM](https://nodei.co/npm/blueimp-file-upload-expressjs.png?downloads=true)](https://nodei.co/npm/blueimp-file-upload-expressjs/) 6 | 7 | ## * * This project is no longer under active maintenance, if anyone is interested to manage this, please reach out to me. * * 8 | 9 | A simple express module for integrating the *[jQuery File Upload](http://blueimp.github.io/jQuery-File-Upload/)* frontend plugin. 10 | 11 | [Fullstack Demo](http://expressjs-fileupload.cloudno.de/) | [Tutorial on my blog](http://thejackalofjavascript.com/uploading-files-made-fun) 12 | 13 | > v 0.4.0 Released! 14 | 15 | ## Contents 16 | 17 | * [Main features in v0.4.0](#main-features-in-v040) 18 | * [Notices on upgrading v0.3.x to v0.4.0](#notices-on-upgrading-v03x-to-v040) 19 | * [History](#history) 20 | * [Installation](#installation) 21 | * [Configuration](#configuration) 22 | * [Usage with options](#usage-with-options) 23 | * [SSL Support](#ssl-support) 24 | * [Multiple thumbnails](#multiple-thumbnails) 25 | * [Get Form Fields along with Upload](#get-form-fields-along-with-upload) 26 | * [Tests](#tests) 27 | * [Contributions](#contributions) 28 | 29 | 30 | ### Main features in v0.4.0 31 | 32 | 1. Upgrade lwip to v0.0.6 33 | > It means that we can upload and proccess GIF images now. 34 | 35 | 2. More NodeJs friendly callback API 36 | 37 | > Using function(err,data) instead of function(data,err) 38 | 39 | ### Notices on upgrading v0.3.x to v0.4.0 40 | 41 | In order to make v0.4.0 to work properly, we need to change the uploader instance's callback function correspondingly as mentioned above. 42 | 43 | From v0.3.x style, 44 | 45 | ``` 46 | uploader.get(req, res, function (obj) { 47 | res.send(JSON.stringify(obj)); 48 | }); 49 | ``` 50 | 51 | To v0.4.0 style, 52 | 53 | ``` 54 | uploader.get(req, res, function (err,obj) { 55 | if(!err){ 56 | res.send(JSON.stringify(obj)); 57 | } 58 | }); 59 | 60 | ``` 61 | 62 | Similar rule that applies to the callback functions of `uploader.post` and `uploader.delete`. 63 | 64 | ## History 65 | The code was forked from a sample backend code from the [plugin's repo](https://github.com/blueimp/jQuery-File-Upload/tree/master/server/node). Adaptations were made to show how to use this plugin with the popular *[Express](http://expressjs.com/)* *Node.js* framework. 66 | 67 | Although this code was initially meant for educational purposes, enhancements were made. Users can additionally: 68 | 69 | * upgrade lwip to version 0.0.6 to support gif images (New at v0.4.0) 70 | * choose the destination filesystem, local or cloud-based *Amazon S3*, 71 | * create thumbnail without heavy external dependencies using [lwip](https://www.npmjs.com/package/lwip), 72 | * setup server-side rules by [configuration](#Configuration), 73 | * modify the code against a [test harness](#Tests). 74 | 75 | ## Installation 76 | 77 | Setup an *Express* project and install the package. 78 | 79 | ```js 80 | $ npm install blueimp-file-upload-expressjs 81 | ``` 82 | 83 | Beginners can follow the [tutorial](http://thejackalofjavascript.com/uploading-files-made-fun) for detailed instructions. 84 | 85 | 86 | ## Configuration 87 | ```js 88 | options = { 89 | tmpDir: __dirname + '/tmp', // tmp dir to upload files to 90 | uploadDir: __dirname + '/public/files', // actual location of the file 91 | uploadUrl: '/files/', // end point for delete route 92 | maxPostSize: 11000000000, // 11 GB 93 | minFileSize: 1, 94 | maxFileSize: 10000000000, // 10 GB 95 | acceptFileTypes: /.+/i, 96 | inlineFileTypes: /\.(gif|jpe?g|png)/i, 97 | imageTypes: /\.(gif|jpe?g|png)/i, 98 | copyImgAsThumb : true, // required 99 | imageVersions :{ 100 | maxWidth : 200, 101 | maxHeight : 200 102 | }, 103 | accessControl: { 104 | allowOrigin: '*', 105 | allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE', 106 | allowHeaders: 'Content-Type, Content-Range, Content-Disposition' 107 | }, 108 | storage : { 109 | type : 'local', // local or aws 110 | aws : { 111 | accessKeyId : 'xxxxxxxxxxxxxxxxx', // required if aws 112 | secretAccessKey : 'xxxxxxxxxxxxxxxxxxxxxxx', // required if aws 113 | region : 'us-west-2', //make sure you know the region, else leave this option out 114 | bucketName : 'xxxxxxxxx' // required if aws 115 | } 116 | } 117 | }; 118 | 119 | ``` 120 | ### Usage with options 121 | (*refer tutorial*) 122 | ```js 123 | // config the uploader 124 | var options = { 125 | tmpDir: __dirname + '/../public/uploaded/tmp', 126 | uploadDir: __dirname + '/../public/uploaded/files', 127 | uploadUrl: '/uploaded/files/', 128 | maxPostSize: 11000000000, // 11 GB 129 | minFileSize: 1, 130 | maxFileSize: 10000000000, // 10 GB 131 | acceptFileTypes: /.+/i, 132 | // Files not matched by this regular expression force a download dialog, 133 | // to prevent executing any scripts in the context of the service domain: 134 | inlineFileTypes: /\.(gif|jpe?g|png)/i, 135 | imageTypes: /\.(gif|jpe?g|png)/i, 136 | copyImgAsThumb : true, // required 137 | imageVersions :{ 138 | maxWidth : 200, 139 | maxHeight : 200 140 | }, 141 | accessControl: { 142 | allowOrigin: '*', 143 | allowMethods: 'OPTIONS, HEAD, GET, POST, PUT, DELETE', 144 | allowHeaders: 'Content-Type, Content-Range, Content-Disposition' 145 | }, 146 | storage : { 147 | type : 'aws', 148 | aws : { 149 | accessKeyId : 'xxxxxxxxxxxxxxxxx', 150 | secretAccessKey : 'xxxxxxxxxxxxxxxxx', 151 | region : 'us-east-1',//make sure you know the region, else leave this option out 152 | bucketName : 'xxxxxxxxxxxxxxxxx' 153 | } 154 | } 155 | }; 156 | 157 | // init the uploader 158 | var uploader = require('blueimp-file-upload-expressjs')(options); 159 | 160 | 161 | module.exports = function (router) { 162 | router.get('/upload', function(req, res) { 163 | uploader.get(req, res, function (err,obj) { 164 | if(!err){ 165 | res.send(JSON.stringify(obj)); 166 | } 167 | }); 168 | 169 | }); 170 | 171 | router.post('/upload', function(req, res) { 172 | uploader.post(req, res, function (error,obj, redirect) { 173 | if(!error) 174 | { 175 | res.send(JSON.stringify(obj)); 176 | } 177 | }); 178 | 179 | }); 180 | 181 | // the path SHOULD match options.uploadUrl 182 | router.delete('/uploaded/files/:name', function(req, res) { 183 | uploader.delete(req, res, function (err,obj) { 184 | res.Json({error:err}); 185 | }); 186 | 187 | }); 188 | return router; 189 | } 190 | ``` 191 | ### SSL Support 192 | 193 | Set the `useSSL` option to `true` to use the package with an [HTTPS server](http://expressjs.com/4x/api.html#app.listen). 194 | ```js 195 | var express = require('express') 196 | var fs = require('fs') 197 | var https = require('https'); 198 | 199 | var app = express() 200 | 201 | // config the uploader 202 | var options = { 203 | ... 204 | useSSL: true 205 | ... 206 | }; 207 | 208 | // init the uploader 209 | var uploader = require('blueimp-file-upload-expressjs')(options); 210 | 211 | app.get('/upload', function(req, res) { 212 | uploader.get(req, res, function (err,obj) { 213 | if(!err) 214 | res.send(JSON.stringify(obj)); 215 | }) 216 | .post('/upload', // ... 217 | .delete('/uploaded/files/:name', // ... 218 | 219 | // create the HTTPS server 220 | var app_key = fs.readFileSync('key.pem'); 221 | var app_cert = fs.readFileSync('cert.pem'); 222 | 223 | https.createServer({key: app_key, cert: app_cert}, app).listen(443); 224 | 225 | ``` 226 | 227 | ### Multiple thumbnails 228 | 229 | To generate multiple thumbnails while uploading 230 | 231 | ```js 232 | var options = { 233 | tmpDir: __dirname + '/../public/uploaded/tmp', 234 | uploadDir: __dirname + '/../public/uploaded/files', 235 | uploadUrl: '/uploaded/files/', 236 | copyImgAsThumb: true, // required 237 | imageVersions: { 238 | maxWidth: 200, 239 | maxHeight: 200 240 | }, 241 | storage: { 242 | type: 'local' 243 | } 244 | }; 245 | ``` 246 | `copyImgAsThumb` needs to be set to true. `imageVersions`, `maxWidth` and `maxHeight` will by default create a `thumbnail` folder and place the specified width/height thumbnail in it. 247 | 248 | Optionally, you can omit the `maxHeight`. In this case, it will be resize proportionally to the specified width. 249 | 250 | ```js 251 | imageVersions: { 252 | maxWidth: 200 253 | }, 254 | ``` 255 | 256 | also 257 | 258 | ```js 259 | imageVersions: { 260 | maxWidth: 200, 261 | maxHeight : 'auto' 262 | }, 263 | ``` 264 | PS : `auto` value works only with height. 265 | 266 | You can also specify multiple thumbnail generations like 267 | 268 | ```js 269 | var options = { 270 | tmpDir: __dirname + '/../public/uploaded/tmp', 271 | uploadDir: __dirname + '/../public/uploaded/files', 272 | uploadUrl: '/uploaded/files/', 273 | copyImgAsThumb: true, 274 | imageVersions: { 275 | maxWidth: 200, 276 | maxHeight: 'auto', 277 | "large" : { 278 | width : 600, 279 | height : 600 280 | }, 281 | "medium" : { 282 | width : 300, 283 | height : 300 284 | }, 285 | "small" : { 286 | width : 150, 287 | height : 150 288 | } 289 | }, 290 | storage: { 291 | type: 'local' 292 | } 293 | }; 294 | ``` 295 | 296 | ## Get Form Fields along with Upload 297 | > Form fields will come as a part of a JSON object of fileInfo(like title/description). If not specified then will return empty object. [[PR42](https://github.com/arvindr21/blueimp-file-upload-expressjs/pull/42)] 298 | 299 | Refer to : [How to submit additional form data](https://github.com/blueimp/jQuery-File-Upload/wiki/How-to-submit-additional-form-data) to send additional form data from the client. 300 | 301 | ## Tests 302 | 303 | Unit tests can be run with *Jasmine* using `npm test` or this command: 304 | ```js 305 | $ jasmine-node specs/ 306 | ``` 307 | 308 | Manual end to end tests can be done with [this full project](https://github.com/arvindr21/expressjs-fileupload). Change the `require()` path of [`uploadManager.js`](https://github.com/arvindr21/expressjs-fileupload/blob/master/routes/uploadManager.js#L29) to link it this cloned repository. 309 | 310 | ## Contributions 311 | 312 | Changes and improvements are welcome! Feel free to fork and open a pull request. 313 | 314 | ### To Do 315 | * Make [Configuration](#configuration) documentation clearer and shorter, 316 | * Refactor code to build tests and provide generic transports as in `winston`, 317 | * Write end to end tests with [WebdriverIO](http://webdriver.io/), 318 | * Provide a basic image processing pipeline (resizing, croping, filter effects), 319 | * Fix AWS thubnail issue, 320 | * Provide access to other cloud-based services like *Microsoft Azure*. 321 | 322 | ### Done 323 | 324 | * ~~Fix Thumbnail creation when uploading images with a more 'feasible' approach~~, 325 | * ~~Amazon S3 integration~~, 326 | * ~~SSL Support~~. 327 | 328 | *** 329 | ## License 330 | 331 | *blueimp-file-upload-expressjs* is licensed under the [MIT licence](http://opensource.org/licenses/MIT). 332 | 333 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 334 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 335 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 336 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 337 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 338 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 339 | THE SOFTWARE. 340 | --------------------------------------------------------------------------------