From eaf190219b71d39c198d1ac279cb34a8babf0977 Mon Sep 17 00:00:00 2001 From: Michel Hua Date: Mon, 29 Dec 2014 04:05:49 +0100 Subject: [PATCH] Broke dependencies into /lib and added unit tests --- .gitignore | 1 + README.md | 71 +++++++--- index.js | 293 +++++++++++++-------------------------- lib/fileinfo.js | 89 ++++++++++++ lib/transport/aws.js | 49 +++++++ package.json | 2 +- specs/fileupload-spec.js | 99 +++++++++++-- 7 files changed, 374 insertions(+), 230 deletions(-) create mode 100644 lib/fileinfo.js create mode 100644 lib/transport/aws.js diff --git a/.gitignore b/.gitignore index da23d0d..60a395b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ pids # Directory for instrumented libs generated by jscoverage/JSCover lib-cov +tmp # Coverage directory used by tools like istanbul coverage diff --git a/README.md b/README.md index 25efc44..50999ca 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,43 @@ # Blueimp file upload for Express js -[![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) + +[![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) [![NPM](https://nodei.co/npm/blueimp-file-upload-expressjs.png?downloads=true)](https://nodei.co/npm/blueimp-file-upload-expressjs/) -[![Build Status](https://travis-ci.org/arvindr21/blueimp-file-upload-expressjs.svg?branch=master)](https://travis-ci.org/arvindr21/blueimp-file-upload-expressjs) +A simple express module for integrating the *[jQuery File Upload](http://blueimp.github.io/jQuery-File-Upload/)* frontend plugin. + +[Fullstack Demo](http://expressjs-fileupload.cloudno.de/) | [Tutorial on my blog](http://thejackalofjavascript.com/uploading-files-made-fun) -* A simple express module for integrating jQuery File Upload. -* The code is borrowed from [here](https://github.com/blueimp/jQuery-File-Upload/tree/master/server/node) and made compatible with Expressjs -* [Demo](http://expressjs-fileupload.cloudno.de/) -* [Tutorial](http://thejackalofjavascript.com/uploading-files-made-fun) +## History +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. +Although this code was initially meant for educational purposes, enhancements were made. Users can additionally: -## Features -* Upload to server -* Upload to AWS -* Client available [here](http://blueimp.github.io/jQuery-File-Upload/) +* choose the destination filesystem, local or cloud-based *Amazon S3*, +* create thumbnail without heavy external dependencies using [lwip](https://www.npmjs.com/package/lwip), +* setup server-side rules by [configuration](#Configuration), +* modify the code against a [test harness](#Tests). ## Installation + +Setup an *Express* project and install the package. + ```js $ npm install blueimp-file-upload-expressjs ``` - -## Options + +Beginners can follow the [tutorial](http://thejackalofjavascript.com/uploading-files-made-fun) for detailed instructions. + +## Tests + +Unit tests can be run with *Jasmine* using `npm test` or this command: +```js +$ jasmine-node specs/ +``` + +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. + +## Configuration ```js options = { tmpDir: __dirname + '/tmp', // tmp dir to upload files to @@ -55,7 +71,7 @@ options = { }; ``` -## Usage with options +### Usage with options (*refer tutorial*) ```js // config the uploader @@ -124,7 +140,7 @@ module.exports = function (router) { return router; } ``` -## SSL Support +### SSL Support Set the `useSSL` option to `true` to use the package with an [HTTPS server](http://expressjs.com/4x/api.html#app.listen). ```js @@ -159,7 +175,7 @@ https.createServer({key: app_key, cert: app_cert}, app).listen(443); ``` -## Multiple thumbnails +### Multiple thumbnails To generate multiple thumbnails while uploading @@ -228,19 +244,28 @@ var options = { }; ``` -## Todo +## Contributions -* Refactor code to build tests -* ~~Fix Thumbnail creation when uploading images with a more 'feasible' appraoch~~ -* Add reizing, croping and other filter effects -* ~~Amazon S3 Intergartion~~ -* Azure Integration -* ~~SSL SUpport~~ +Changes and improvements are welcome! Feel free to fork and open a pull request. + +### To Do +* Make [Configuration](#Configuration) documentation clearer and shorter, +* Refactor code to build tests and provide generic transports as in `winston`, +* Write end to end tests with [WebdriverIO](http://webdriver.io/), +* Provide a basic image processing pipeline (resizing, croping, filter effects), +* Provide access to other cloud-based services like *Microsoft Azure*. + +### Done + +* ~~Fix Thumbnail creation when uploading images with a more 'feasible' approach~~, +* ~~Amazon S3 integration~~, +* ~~SSL Support~~. *** ## License -### MIT +*blueimp-file-upload-expressjs* is licensed under the [MIT licence](http://opensource.org/licenses/MIT). + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/index.js b/index.js index e0e0d90..49276ca 100644 --- a/index.js +++ b/index.js @@ -1,52 +1,58 @@ 'use strict'; -module.exports = function(opts) { - var path = require('path'), - fs = require('fs'), - lwip = require('lwip'), - _existsSync = fs.existsSync || path.existsSync, - mkdirp = require('mkdirp'), - AWS = require('aws-sdk'), - formidable = require('formidable'), - nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/, - s3, - options = { - tmpDir: opts.tmpDir || __dirname + '/tmp', - uploadDir: opts.uploadDir || __dirname + '/public/files', - uploadUrl: opts.uploadUrl || '/files/', - maxPostSize: opts.maxPostSize || 11000000000, // 11 GB - minFileSize: opts.minFileSize || 1, - maxFileSize: opts.maxFileSize || 10000000000, // 10 GB - acceptFileTypes: opts.acceptFileTypes || /.+/i, - copyImgAsThumb: opts.copyImgAsThumb && true, - useSSL: opts.useSSL || false, - UUIDRegex: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/, - // Files not matched by this regular expression force a download dialog, - // to prevent executing any scripts in the context of the service domain: - inlineFileTypes: opts.inlineFileTypes || /\.(gif|jpe?g|png)/i, - imageTypes: opts.imageTypes || /\.(gif|jpe?g|png)/i, - imageVersions: { - 'thumbnail': { - width: (opts.imageVersions && opts.imageVersions.maxWidth) ? opts.imageVersions.maxWidth : 99, - height: (opts.imageVersions && opts.imageVersions.maxHeight) ? opts.imageVersions.maxHeight : 'auto' - } - }, - accessControl: { - allowOrigin: (opts.accessControl && opts.accessControl.allowOrigin) ? opts.accessControl.allowOrigin : '*', - allowMethods: (opts.accessControl && opts.accessControl.allowMethods) ? opts.accessControl.allowMethods : 'OPTIONS, HEAD, GET, POST, PUT, DELETE', - allowHeaders: (opts.accessControl && opts.accessControl.allowHeaders) ? opts.accessControl.allowHeaders : 'Content-Type, Content-Range, Content-Disposition' - }, - storage: { - type: (opts.storage && opts.storage.type) ? opts.storage.type : 'local', - aws: { - accessKeyId: (opts.storage && opts.storage.aws && opts.storage.aws.accessKeyId) ? opts.storage.aws.accessKeyId : null, - secretAccessKey: (opts.storage && opts.storage.aws && opts.storage.aws.secretAccessKey) ? opts.storage.aws.secretAccessKey : null, - region: (opts.storage && opts.storage.aws && opts.storage.aws.region) ? opts.storage.aws.region : null, - bucketName: (opts.storage && opts.storage.aws && opts.storage.aws.bucketName) ? opts.storage.aws.bucketName : null, - acl: (opts.storage && opts.storage.aws && opts.storage.aws.acl) ? opts.storage.aws.acl : 'public-read' - } +var FileInfo = require('./lib/fileinfo.js'); +var checkExists = FileInfo.checkFolder; + +// TODO - Refactor these 3 guys to aws.js Transport +var AWS = require('aws-sdk'); +var uploadFileAWS = require('./lib/transport/aws.js'); +var s3; + +// TODO - Provide a locale filesystem Transport + +var fs = require('fs'); +var path = require('path'); + +module.exports = uploadService; + +function applyConfig(opts) { + var options = { + tmpDir: opts.tmpDir || __dirname + '/tmp', + uploadDir: opts.uploadDir || __dirname + '/public/files', + uploadUrl: opts.uploadUrl || '/files/', + maxPostSize: opts.maxPostSize || 11000000000, // 11 GB + minFileSize: opts.minFileSize || 1, + maxFileSize: opts.maxFileSize || 10000000000, // 10 GB + acceptFileTypes: opts.acceptFileTypes || /.+/i, + copyImgAsThumb: opts.copyImgAsThumb && true, + useSSL: opts.useSSL || false, + UUIDRegex: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/, + // Files not matched by this regular expression force a download dialog, + // to prevent executing any scripts in the context of the service domain: + inlineFileTypes: opts.inlineFileTypes || /\.(gif|jpe?g|png)/i, + imageTypes: opts.imageTypes || /\.(gif|jpe?g|png)/i, + imageVersions: { + 'thumbnail': { + width: (opts.imageVersions && opts.imageVersions.maxWidth) ? opts.imageVersions.maxWidth : 99, + height: (opts.imageVersions && opts.imageVersions.maxHeight) ? opts.imageVersions.maxHeight : 'auto' } - }; + }, + accessControl: { + allowOrigin: (opts.accessControl && opts.accessControl.allowOrigin) ? opts.accessControl.allowOrigin : '*', + allowMethods: (opts.accessControl && opts.accessControl.allowMethods) ? opts.accessControl.allowMethods : 'OPTIONS, HEAD, GET, POST, PUT, DELETE', + allowHeaders: (opts.accessControl && opts.accessControl.allowHeaders) ? opts.accessControl.allowHeaders : 'Content-Type, Content-Range, Content-Disposition' + }, + storage: { + type: (opts.storage && opts.storage.type) ? opts.storage.type : 'local', + aws: { + accessKeyId: (opts.storage && opts.storage.aws && opts.storage.aws.accessKeyId) ? opts.storage.aws.accessKeyId : null, + secretAccessKey: (opts.storage && opts.storage.aws && opts.storage.aws.secretAccessKey) ? opts.storage.aws.secretAccessKey : null, + region: (opts.storage && opts.storage.aws && opts.storage.aws.region) ? opts.storage.aws.region : null, + bucketName: (opts.storage && opts.storage.aws && opts.storage.aws.bucketName) ? opts.storage.aws.bucketName : null, + acl: (opts.storage && opts.storage.aws && opts.storage.aws.acl) ? opts.storage.aws.acl : 'public-read' + } + } + }; if (opts.imageVersions) { Object.keys(opts.imageVersions).forEach(function(version) { @@ -56,7 +62,6 @@ module.exports = function(opts) { }); } - if (options.storage.type === 'local') { checkExists(options.tmpDir); checkExists(options.uploadDir); @@ -81,133 +86,23 @@ module.exports = function(opts) { } } - // AWS Random UUID - /* https://gist.github.com/jed/982883#file-index-js */ - function b(a) { - return a ? (a ^ Math.random() * 16 >> a / 4).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, b); - } - - - // check if upload folders exists - function checkExists(dir) { - fs.exists(dir, function(exists) { - if (!exists) { - mkdirp(dir, function(err) { - if (err) console.error(err); - else console.log('The uploads folder was not present, we have created it for you [' + dir + ']'); - }); - //throw new Error(dir + ' does not exists. Please create the folder'); - } - }); - } - - function getContentTypeByFile(fileName) { - var rc = 'application/octet-stream'; - var fn = fileName.toLowerCase(); - - if (fn.indexOf('.html') >= 0) rc = 'text/html'; - else if (fn.indexOf('.css') >= 0) rc = 'text/css'; - else if (fn.indexOf('.json') >= 0) rc = 'application/json'; - else if (fn.indexOf('.js') >= 0) rc = 'application/x-javascript'; - else if (fn.indexOf('.png') >= 0) rc = 'image/png'; - else if (fn.indexOf('.jpg') >= 0) rc = 'image/jpg'; - - return rc; - } - - function uploadFile(remoteFilename, fileName, callback) { - var fileBuffer = fs.readFileSync(fileName); - var metaData = getContentTypeByFile(fileName); - - - s3.putObject({ - ACL: options.storage.aws.acl, - Bucket: options.storage.aws.bucketName, - Key: remoteFilename, - Body: fileBuffer, - ContentType: metaData - }, function(error, response) { - var params = { - Bucket: options.storage.aws.bucketName, - Key: remoteFilename - }; - var url = s3.getSignedUrl('getObject', params); - callback({ - url: url - },error); - }); - } - - // This function is never used - var utf8encode = function(str) { - return unescape(encodeURIComponent(str)); - }; - - var nameCountFunc = function(s, index, ext) { - return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || ''); - }; - var FileInfo = function(file) { - this.name = file.name; - this.size = file.size; - this.type = file.type; - this.modified = file.lastMod; - this.deleteType = 'DELETE'; - }; + return options; +} - FileInfo.prototype.safeName = function() { - // Prevent directory traversal and creating hidden system files: - this.name = path.basename(this.name).replace(/^\.+/, ''); - // Prevent overwriting existing files: - while (_existsSync(options.uploadDir + '/' + this.name)) { - this.name = this.name.replace(nameCountRegexp, nameCountFunc); - } - }; +function uploadService(opts) { + var lwip = require('lwip'); + var formidable = require('formidable'); - FileInfo.prototype.initUrls = function(req, sss) { - if (!this.error) { - var that = this; - if (!sss) { - var baseUrl = (options.useSSL ? 'https:' : 'http:') + - '//' + req.headers.host + options.uploadUrl; - that.url = baseUrl + encodeURIComponent(that.name); - that.deleteUrl = baseUrl + encodeURIComponent(that.name); - Object.keys(options.imageVersions).forEach(function(version) { - if (_existsSync( - options.uploadDir + '/' + version + '/' + that.name - )) { - that[version + 'Url'] = baseUrl + version + '/' + - encodeURIComponent(that.name); - } - }); - } else { - that.url = sss.url; - that.deleteUrl = options.uploadUrl + sss.url.split('/')[sss.url.split('/').length - 1].split('?')[0]; - if (options.imageTypes.test(sss.url)) { - Object.keys(options.imageVersions).forEach(function(version) { - that[version + 'Url'] = sss.url; - }); - } - } - } - }; + var fileUploader = {}; - FileInfo.prototype.validate = function() { - if (options.minFileSize && options.minFileSize > this.size) { - this.error = 'File is too small'; - } else if (options.maxFileSize && options.maxFileSize < this.size) { - this.error = 'File is too big'; - } else if (!options.acceptFileTypes.test(this.name)) { - this.error = 'Filetype not allowed'; - } - return !this.error; - }; + fileUploader.config = applyConfig(opts); + var options = fileUploader.config; - var setNoCacheHeaders = function(res) { + function setNoCacheHeaders(res) { res.setHeader('Pragma', 'no-cache'); res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); res.setHeader('Content-Disposition', 'inline; filename="files.json"'); - }; - var fileUploader = {}; + } fileUploader.get = function(req, res, callback) { setNoCacheHeaders(res); @@ -215,14 +110,13 @@ module.exports = function(opts) { if (options.storage.type == 'local') { fs.readdir(options.uploadDir, function(err, list) { list.forEach(function(name) { - var stats = fs.statSync(options.uploadDir + '/' + name), - fileInfo; + var stats = fs.statSync(options.uploadDir + '/' + name); if (stats.isFile() && name[0] !== '.') { - fileInfo = new FileInfo({ + var fileInfo = new FileInfo({ name: name, size: stats.size, lastMod: stats.mtime - }); + }, options); fileInfo.initUrls(req); files.push(fileInfo); } @@ -248,7 +142,7 @@ module.exports = function(opts) { var fileInfo = new FileInfo({ name: options.UUIDRegex.test(o.Key) ? o.Key.split('__')[1] : o.Key, size: o.Size - }); + }, options); var sss = { url: (options.useSSL ? 'https:' : 'http:') + '//s3.amazonaws.com/' + options.storage.aws.bucketName + '/' + o.Key }; @@ -263,33 +157,32 @@ module.exports = function(opts) { } }; - fileUploader.post = function(req, res, callback) { setNoCacheHeaders(res); - var form = new formidable.IncomingForm(), - tmpFiles = [], - files = [], - map = {}, - counter = 1, - redirect, - finish = function(sss, error) { - counter -= 1; - if (!counter) { - files.forEach(function(fileInfo) { - fileInfo.initUrls(req, sss); - }); - callback({ - files: files - }, redirect, error); - } - }; - + var form = new formidable.IncomingForm(); + var tmpFiles = []; + var files = []; + var map = {}; + var counter = 1; + var redirect; + + function finish(sss, error) { + counter -= 1; + if (!counter) { + files.forEach(function(fileInfo) { + fileInfo.initUrls(req, sss); + }); + callback({ + files: files + }, redirect, error); + } + } form.uploadDir = options.tmpDir; form.on('fileBegin', function(name, file) { tmpFiles.push(file.path); - var fileInfo = new FileInfo(file, req, true); + var fileInfo = new FileInfo(file, options, req, true); fileInfo.safeName(); map[path.basename(file.path)] = fileInfo; files.push(fileInfo); @@ -301,6 +194,7 @@ module.exports = function(opts) { var fileInfo = map[path.basename(file.path)]; fileInfo.size = file.size; if (!fileInfo.validate()) { + // TODO - Missing callback fs.unlink(file.path); return; } @@ -333,12 +227,18 @@ module.exports = function(opts) { }); } } else if (options.storage.type == 'aws') { - uploadFile((b() + '__' + fileInfo.name), file.path, function(sss, error) { + var awsOpts = { + ACL: options.storage.aws.acl, + Bucket: options.storage.aws.bucketName + }; + + uploadFileAWS(s3, fileInfo.name, file.path, awsOpts, function(sss, error) { finish(sss, error); }); } }).on('aborted', function() { tmpFiles.forEach(function(file) { + // TODO - Missing callback fs.unlink(file); }); }).on('error', function(e) { @@ -363,9 +263,8 @@ module.exports = function(opts) { if (fileName[0] !== '.') { fs.unlink(options.uploadDir + '/' + fileName, function(ex) { Object.keys(options.imageVersions).forEach(function(version) { - fs.unlink(options.uploadDir + '/' + version + '/' + fileName, function(err) { - //if (err) throw err; - }); + // TODO - Missing callback + fs.unlink(options.uploadDir + '/' + version + '/' + fileName); }); callback({ success: !ex @@ -394,4 +293,4 @@ module.exports = function(opts) { }; return fileUploader; -}; +} diff --git a/lib/fileinfo.js b/lib/fileinfo.js new file mode 100644 index 0000000..28989fb --- /dev/null +++ b/lib/fileinfo.js @@ -0,0 +1,89 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); + +var mkdirp = require('mkdirp'); + +// Since Node 0.8, .existsSync() moved from path to fs: +var _existsSync = fs.existsSync || path.existsSync; + +module.exports = FileInfo; + +module.exports.checkFolder = checkExists; + +function FileInfo(file, opts) { + this.name = file.name; + this.size = file.size; + this.type = file.type; + this.modified = file.lastMod; + this.deleteType = 'DELETE'; + this.options = opts; +} + +FileInfo.prototype.safeName = function() { + var nameCountRegexp = /(?:(?: \(([\d]+)\))?(\.[^.]+))?$/; + + function nameCountFunc(s, index, ext) { + return ' (' + ((parseInt(index, 10) || 0) + 1) + ')' + (ext || ''); + } + + // Prevent directory traversal and creating hidden system files: + this.name = path.basename(this.name).replace(/^\.+/, ''); + // Prevent overwriting existing files: + while (_existsSync(this.options.uploadDir + '/' + this.name)) { + this.name = this.name.replace(nameCountRegexp, nameCountFunc); + } +}; + +FileInfo.prototype.initUrls = function(req, sss) { + if (!this.error) { + var that = this; + if (!sss) { + var baseUrl = (that.options.useSSL ? 'https:' : 'http:') + + '//' + req.headers.host + that.options.uploadUrl; + that.url = baseUrl + encodeURIComponent(that.name); + that.deleteUrl = baseUrl + encodeURIComponent(that.name); + Object.keys(that.options.imageVersions).forEach(function(version) { + if (_existsSync( + that.options.uploadDir + '/' + version + '/' + that.name + )) { + that[version + 'Url'] = baseUrl + version + '/' + + encodeURIComponent(that.name); + } + }); + } else { + that.url = sss.url; + that.deleteUrl = that.options.uploadUrl + sss.url.split('/')[sss.url.split('/').length - 1].split('?')[0]; + if (that.options.imageTypes.test(sss.url)) { + Object.keys(that.options.imageVersions).forEach(function(version) { + that[version + 'Url'] = sss.url; + }); + } + } + } +}; + +FileInfo.prototype.validate = function() { + if (this.options.minFileSize && this.options.minFileSize > this.size) { + this.error = 'File is too small'; + } else if (this.options.maxFileSize && this.options.maxFileSize < this.size) { + this.error = 'File is too big'; + } else if (!this.options.acceptFileTypes.test(this.name)) { + this.error = 'Filetype not allowed'; + } + return !this.error; +}; + +// check if folder exists, otherwise create it +function checkExists(dir) { + fs.exists(dir, function(exists) { + if (!exists) { + mkdirp(dir, function(err) { + if (err) console.error(err); + else console.log('The uploads folder was not present, we have created it for you [' + dir + ']'); + }); + //throw new Error(dir + ' does not exists. Please create the folder'); + } + }); +} diff --git a/lib/transport/aws.js b/lib/transport/aws.js new file mode 100644 index 0000000..a171d04 --- /dev/null +++ b/lib/transport/aws.js @@ -0,0 +1,49 @@ +'use strict'; + +var fs = require('fs'); +module.exports = uploadFile; + +// AWS Random UUID +/* https://gist.github.com/jed/982883#file-index-js */ +function b(a) { + return a ? (a ^ Math.random() * 16 >> a / 4).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, b); +} + +function getContentTypeByFile(fileName) { + var rc = 'application/octet-stream'; + var fn = fileName.toLowerCase(); + + if (fn.indexOf('.html') >= 0) rc = 'text/html'; + else if (fn.indexOf('.css') >= 0) rc = 'text/css'; + else if (fn.indexOf('.json') >= 0) rc = 'application/json'; + else if (fn.indexOf('.js') >= 0) rc = 'application/x-javascript'; + else if (fn.indexOf('.png') >= 0) rc = 'image/png'; + else if (fn.indexOf('.jpg') >= 0) rc = 'image/jpg'; + + return rc; +} + +function uploadFile(s3, fileName, filePath, opts, callback) { + var fileBuffer = fs.readFileSync(filePath); + var metaData = getContentTypeByFile(filePath); + var remoteFilename = b() + '__' + fileName; + + s3.putObject({ + ACL: opts.ACL, + Bucket: opts.Bucket, + Key: remoteFilename, + Body: fileBuffer, + ContentType: metaData + }, function(error) { + var params = { + Bucket: opts.Bucket, + Key: remoteFilename + }; + + var url = s3.getSignedUrl('getObject', params); + + callback({ + url: url + }, error); + }); +} \ No newline at end of file diff --git a/package.json b/package.json index 47c6552..ed2915f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blueimp-file-upload-expressjs", - "version": "0.3.2", + "version": "0.4.0", "description": "jQuery File Upload using Expressjs : 'borrowed' from Blueimp jQuery File Upload developed by Sebastian Tschan", "main": "index.js", "scripts": { diff --git a/specs/fileupload-spec.js b/specs/fileupload-spec.js index cce8f2c..cbb5e36 100644 --- a/specs/fileupload-spec.js +++ b/specs/fileupload-spec.js @@ -1,23 +1,104 @@ 'use strict'; +describe('FileInfo package', function() { + var FileInfo = require('../lib/fileinfo.js'); + // TODO - FileInfo default constructor or mock parameters + + it('should provide a safe name for new files'); + + it('should generate URLs for the files'); + + it('should check against certain rules'); + + it('should check or create folders'); +}); + +describe('AWS transport package', function() { + var uploadFileAWS = require('../lib/transport/aws.js'); +}); + describe('Uploader configuration', function() { var options; var uploader; + beforeEach(function() { + // TODO - Create a mock object for the filesystem + uploader = require('../index'); + }); + it('should have default config values', function() { options = {}; - uploader = require('../index')(options); - expect(uploader).toBeDefined(); + expect(uploader(options).config).toBeDefined(); }); - // TODO : Refactor code to be able to test the Specs below - it('should support local filesystem'); + it('should support the local filesystem', function() { + options = { + tmpDir: 'tmp/foo', + uploadDir: 'tmp/bar' + }; + expect(uploader(options).config.tmpDir).toEqual('tmp/foo'); + expect(uploader(options).config.uploadDir).toEqual('tmp/bar'); + }); - it('should support Amazon Simple Storage Service'); + it('should support Amazon Simple Storage Service', function() { + var awsConfig = { + type: 'aws', + aws: { + accessKeyId: 'sesame', + secretAccessKey: 'open', + region: 'us-west-2', + bucketName: 'ali-baba', + acl: 'private' + } + }; + options = { + storage: awsConfig + }; + expect(uploader(options).config.storage).toEqual(awsConfig); + }); - it('should support thumbnails generation'); + it('should support thumbnails generation', function() { + var thumbsConfig = { + maxWidth: 200, + maxHeight: 'auto', + large: { + width: 600, + height: 600 + }, + medium: { + width: 300, + height: 300 + }, + small: { + width: 150, + height: 150 + } + } + options = { + imageVersions: thumbsConfig + }; + var obj = uploader(options); + + expect(obj.config.imageVersions.thumbnail.width).toEqual(thumbsConfig.maxWidth); + expect(obj.config.imageVersions.thumbnail.height).toEqual(thumbsConfig.maxHeight); + expect(obj.config.imageVersions.large).toEqual(thumbsConfig.large); + expect(obj.config.imageVersions.medium).toEqual(thumbsConfig.medium); + expect(obj.config.imageVersions.small).toEqual(thumbsConfig.small); + }); - it('should support SSL'); + it('should allow disabling thumbnails', function() { + options = { + copyImgAsThumb: true + }; + expect(uploader(options).config.copyImgAsThumb).toBe(true); + }); + + it('should support SSL', function() { + options = { + useSSL: true + }; + expect(uploader(options).config.useSSL).toBe(true); + }); }); describe('Uploader REST services', function() { @@ -26,7 +107,7 @@ describe('Uploader REST services', function() { it('should provide a GET method', function() { expect(uploader.get).toBeDefined(); - expect(uploader.post.length).toEqual(3); + expect(uploader.get.length).toEqual(3); }); it('should provide a POST method', function() { @@ -38,4 +119,4 @@ describe('Uploader REST services', function() { expect(uploader.delete).toBeDefined(); expect(uploader.delete.length).toEqual(3); }); -}); \ No newline at end of file +});