├── .gitignore ├── README.md ├── app.js ├── bin └── www ├── helpers └── AvatarStorage.js ├── package-lock.json ├── package.json ├── public └── stylesheets │ └── style.css ├── routes ├── index.js └── users.js └── views ├── error.ejs └── index.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # advanced-multer-node-sourcecode 2 | 3 | **You can checkout the complete tutorial on Scotch: [Advanced Photo Upload in Node](https://scotch.io/tutorials/advanced-photo-upload-in-node).** 4 | 5 | Source code for tutorial on advanced photo upload in Node using Multer and Jimp. In order to run the demo on your local machine and experiment with the source code, do the following: 6 | 7 | - Clone the repository into a new directory on your machine 8 | 9 | - Run the following command from the new directory to install the dependencies: 10 | 11 | ```sh 12 | npm install 13 | ``` 14 | 15 | - Create a `.env` file in the root of the new directory with the following content: 16 | 17 | ```ini 18 | 19 | # Avatar Upload Variables 20 | 21 | AVATAR_FIELD=avatar 22 | AVATAR_BASE_URL=/uploads/avatars 23 | AVATAR_STORAGE=public/uploads/avatars 24 | 25 | ``` 26 | 27 | - The app will run on port **3000** by default. If you would prefer another port, you can specify it in the `bin/www` file. Look for the following line in the file and replace `'3000'` with your desired port. 28 | 29 | ```js 30 | var port = normalizePort(process.env.PORT || '3000'); 31 | ``` 32 | 33 | - Finally, start the demo app by running the following command: 34 | 35 | ```sh 36 | npm start 37 | ``` 38 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var dotenv = require('dotenv').config(); 8 | 9 | var index = require('./routes/index'); 10 | var users = require('./routes/users'); 11 | 12 | var app = express(); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'ejs'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | app.use('/', index); 27 | app.use('/users', users); 28 | 29 | // catch 404 and forward to error handler 30 | app.use(function(req, res, next) { 31 | var err = new Error('Not Found'); 32 | err.status = 404; 33 | next(err); 34 | }); 35 | 36 | // error handler 37 | app.use(function(err, req, res, next) { 38 | // set locals, only providing error in development 39 | res.locals.message = err.message; 40 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 41 | 42 | // render the error page 43 | res.status(err.status || 500); 44 | res.render('error'); 45 | }); 46 | 47 | module.exports = app; 48 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('photo-uploader-app:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /helpers/AvatarStorage.js: -------------------------------------------------------------------------------- 1 | // Load dependencies 2 | var _ = require('lodash'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Jimp = require('jimp'); 6 | var crypto = require('crypto'); 7 | var mkdirp = require('mkdirp'); 8 | var concat = require('concat-stream'); 9 | var streamifier = require('streamifier'); 10 | 11 | // Configure UPLOAD_PATH 12 | // process.env.AVATAR_STORAGE contains uploads/avatars 13 | var UPLOAD_PATH = path.resolve(__dirname, '..', process.env.AVATAR_STORAGE); 14 | 15 | // create a multer storage engine 16 | var AvatarStorage = function(options) { 17 | 18 | // this serves as a constructor 19 | function AvatarStorage(opts) { 20 | 21 | var baseUrl = process.env.AVATAR_BASE_URL; 22 | 23 | var allowedStorageSystems = ['local']; 24 | var allowedOutputFormats = ['jpg', 'png']; 25 | 26 | // fallback for the options 27 | var defaultOptions = { 28 | storage: 'local', 29 | output: 'png', 30 | greyscale: false, 31 | quality: 70, 32 | square: true, 33 | threshold: 500, 34 | responsive: false, 35 | }; 36 | 37 | // extend default options with passed options 38 | var options = (opts && _.isObject(opts)) ? _.pick(opts, _.keys(defaultOptions)) : {}; 39 | options = _.extend(defaultOptions, options); 40 | 41 | // check the options for correct values and use fallback value where necessary 42 | this.options = _.forIn(options, function(value, key, object) { 43 | 44 | switch (key) { 45 | 46 | case 'square': 47 | case 'greyscale': 48 | case 'responsive': 49 | object[key] = _.isBoolean(value) ? value : defaultOptions[key]; 50 | break; 51 | 52 | case 'storage': 53 | value = String(value).toLowerCase(); 54 | object[key] = _.includes(allowedStorageSystems, value) ? value : defaultOptions[key]; 55 | break; 56 | 57 | case 'output': 58 | value = String(value).toLowerCase(); 59 | object[key] = _.includes(allowedOutputFormats, value) ? value : defaultOptions[key]; 60 | break; 61 | 62 | case 'quality': 63 | value = _.isFinite(value) ? value : Number(value); 64 | object[key] = (value && value >= 0 && value <= 100) ? value : defaultOptions[key]; 65 | break; 66 | 67 | case 'threshold': 68 | value = _.isFinite(value) ? value : Number(value); 69 | object[key] = (value && value >= 0) ? value : defaultOptions[key]; 70 | break; 71 | 72 | } 73 | 74 | }); 75 | 76 | // set the upload path 77 | this.uploadPath = this.options.responsive ? path.join(UPLOAD_PATH, 'responsive') : UPLOAD_PATH; 78 | 79 | // set the upload base url 80 | this.uploadBaseUrl = this.options.responsive ? path.join(baseUrl, 'responsive') : baseUrl; 81 | 82 | if (this.options.storage == 'local') { 83 | // if upload path does not exist, create the upload path structure 84 | !fs.existsSync(this.uploadPath) && mkdirp.sync(this.uploadPath); 85 | } 86 | 87 | } 88 | 89 | // this generates a random cryptographic filename 90 | AvatarStorage.prototype._generateRandomFilename = function() { 91 | // create pseudo random bytes 92 | var bytes = crypto.pseudoRandomBytes(32); 93 | 94 | // create the md5 hash of the random bytes 95 | var checksum = crypto.createHash('MD5').update(bytes).digest('hex'); 96 | 97 | // return as filename the hash with the output extension 98 | return checksum + '.' + this.options.output; 99 | }; 100 | 101 | // this creates a Writable stream for a filepath 102 | AvatarStorage.prototype._createOutputStream = function(filepath, cb) { 103 | 104 | // create a reference for this to use in local functions 105 | var that = this; 106 | 107 | // create a writable stream from the filepath 108 | var output = fs.createWriteStream(filepath); 109 | 110 | // set callback fn as handler for the error event 111 | output.on('error', cb); 112 | 113 | // set handler for the finish event 114 | output.on('finish', function() { 115 | cb(null, { 116 | destination: that.uploadPath, 117 | baseUrl: that.uploadBaseUrl, 118 | filename: path.basename(filepath), 119 | storage: that.options.storage 120 | }); 121 | }); 122 | 123 | // return the output stream 124 | return output; 125 | }; 126 | 127 | // this processes the Jimp image buffer 128 | AvatarStorage.prototype._processImage = function(image, cb) { 129 | 130 | // create a reference for this to use in local functions 131 | var that = this; 132 | 133 | var batch = []; 134 | 135 | // the responsive sizes 136 | var sizes = ['lg', 'md', 'sm']; 137 | 138 | var filename = this._generateRandomFilename(); 139 | 140 | var mime = Jimp.MIME_PNG; 141 | 142 | // create a clone of the Jimp image 143 | var clone = image.clone(); 144 | 145 | // fetch the Jimp image dimensions 146 | var width = clone.bitmap.width; 147 | var height = clone.bitmap.height; 148 | var square = Math.min(width, height); 149 | var threshold = this.options.threshold; 150 | 151 | // resolve the Jimp output mime type 152 | switch (this.options.output) { 153 | case 'jpg': 154 | mime = Jimp.MIME_JPEG; 155 | break; 156 | case 'png': 157 | default: 158 | mime = Jimp.MIME_PNG; 159 | break; 160 | } 161 | 162 | // auto scale the image dimensions to fit the threshold requirement 163 | if (threshold && square > threshold) { 164 | clone = (square == width) ? clone.resize(threshold, Jimp.AUTO) : clone.resize(Jimp.AUTO, threshold); 165 | } 166 | 167 | // crop the image to a square if enabled 168 | if (this.options.square) { 169 | 170 | if (threshold) { 171 | square = Math.min(square, threshold); 172 | } 173 | 174 | // fetch the new image dimensions and crop 175 | clone = clone.crop((clone.bitmap.width - square) / 2, (clone.bitmap.height - square) / 2, square, square); 176 | } 177 | 178 | // convert the image to greyscale if enabled 179 | if (this.options.greyscale) { 180 | clone = clone.greyscale(); 181 | } 182 | 183 | // set the image output quality 184 | clone = clone.quality(this.options.quality); 185 | 186 | if (this.options.responsive) { 187 | 188 | // map through the responsive sizes and push them to the batch 189 | batch = _.map(sizes, function(size) { 190 | 191 | var outputStream; 192 | 193 | var image = null; 194 | var filepath = filename.split('.'); 195 | 196 | // create the complete filepath and create a writable stream for it 197 | filepath = filepath[0] + '_' + size + '.' + filepath[1]; 198 | filepath = path.join(that.uploadPath, filepath); 199 | outputStream = that._createOutputStream(filepath, cb); 200 | 201 | // scale the image based on the size 202 | switch (size) { 203 | case 'sm': 204 | image = clone.clone().scale(0.3); 205 | break; 206 | case 'md': 207 | image = clone.clone().scale(0.7); 208 | break; 209 | case 'lg': 210 | image = clone.clone(); 211 | break; 212 | } 213 | 214 | // return an object of the stream and the Jimp image 215 | return { 216 | stream: outputStream, 217 | image: image 218 | }; 219 | }); 220 | 221 | } else { 222 | 223 | // push an object of the writable stream and Jimp image to the batch 224 | batch.push({ 225 | stream: that._createOutputStream(path.join(that.uploadPath, filename), cb), 226 | image: clone 227 | }); 228 | 229 | } 230 | 231 | // process the batch sequence 232 | _.each(batch, function(current) { 233 | // get the buffer of the Jimp image using the output mime type 234 | current.image.getBuffer(mime, function(err, buffer) { 235 | if (that.options.storage == 'local') { 236 | // create a read stream from the buffer and pipe it to the output stream 237 | streamifier.createReadStream(buffer).pipe(current.stream); 238 | } 239 | }); 240 | }); 241 | 242 | }; 243 | 244 | // multer requires this for handling the uploaded file 245 | AvatarStorage.prototype._handleFile = function(req, file, cb) { 246 | 247 | // create a reference for this to use in local functions 248 | var that = this; 249 | 250 | // create a writable stream using concat-stream that will 251 | // concatenate all the buffers written to it and pass the 252 | // complete buffer to a callback fn 253 | var fileManipulate = concat(function(imageData) { 254 | 255 | // read the image buffer with Jimp 256 | // it returns a promise 257 | Jimp.read(imageData) 258 | .then(function(image) { 259 | // process the Jimp image buffer 260 | that._processImage(image, cb); 261 | }) 262 | .catch(cb); 263 | }); 264 | 265 | // write the uploaded file buffer to the fileManipulate stream 266 | file.stream.pipe(fileManipulate); 267 | 268 | }; 269 | 270 | // multer requires this for destroying file 271 | AvatarStorage.prototype._removeFile = function(req, file, cb) { 272 | 273 | var matches, pathsplit; 274 | var filename = file.filename; 275 | var _path = path.join(this.uploadPath, filename); 276 | var paths = []; 277 | 278 | // delete the file properties 279 | delete file.filename; 280 | delete file.destination; 281 | delete file.baseUrl; 282 | delete file.storage; 283 | 284 | // create paths for responsive images 285 | if (this.options.responsive) { 286 | pathsplit = _path.split('/'); 287 | matches = pathsplit.pop().match(/^(.+?)_.+?\.(.+)$/i); 288 | 289 | if (matches) { 290 | paths = _.map(['lg', 'md', 'sm'], function(size) { 291 | return pathsplit.join('/') + '/' + (matches[1] + '_' + size + '.' + matches[2]); 292 | }); 293 | } 294 | } else { 295 | paths = [_path]; 296 | } 297 | 298 | // delete the files from the filesystem 299 | _.each(paths, function(_path) { 300 | fs.unlink(_path, cb); 301 | }); 302 | 303 | }; 304 | 305 | // create a new instance with the passed options and return it 306 | return new AvatarStorage(options); 307 | 308 | }; 309 | 310 | // export the storage engine 311 | module.exports = AvatarStorage; 312 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "photo-uploader-app", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "accepts": { 7 | "version": "1.3.4", 8 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", 9 | "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=" 10 | }, 11 | "ajv": { 12 | "version": "5.4.0", 13 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.4.0.tgz", 14 | "integrity": "sha1-MtHPCNvIDEMvQm8S4QslEfa0ZHQ=" 15 | }, 16 | "append-field": { 17 | "version": "0.1.0", 18 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-0.1.0.tgz", 19 | "integrity": "sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo=" 20 | }, 21 | "array-flatten": { 22 | "version": "1.1.1", 23 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 24 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 25 | }, 26 | "asn1": { 27 | "version": "0.2.3", 28 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 29 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 30 | }, 31 | "assert-plus": { 32 | "version": "1.0.0", 33 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 34 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 35 | }, 36 | "asynckit": { 37 | "version": "0.4.0", 38 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 39 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 40 | }, 41 | "aws-sign2": { 42 | "version": "0.7.0", 43 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 44 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 45 | }, 46 | "aws4": { 47 | "version": "1.6.0", 48 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 49 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 50 | }, 51 | "basic-auth": { 52 | "version": "1.1.0", 53 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", 54 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" 55 | }, 56 | "bcrypt-pbkdf": { 57 | "version": "1.0.1", 58 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 59 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 60 | "optional": true 61 | }, 62 | "bignumber.js": { 63 | "version": "2.4.0", 64 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", 65 | "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" 66 | }, 67 | "bmp-js": { 68 | "version": "0.0.3", 69 | "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.0.3.tgz", 70 | "integrity": "sha1-ZBE+nHzxICs3btYHvzBibr5XsYo=" 71 | }, 72 | "body-parser": { 73 | "version": "1.17.2", 74 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 75 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", 76 | "dependencies": { 77 | "debug": { 78 | "version": "2.6.7", 79 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 80 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 81 | } 82 | } 83 | }, 84 | "boom": { 85 | "version": "4.3.1", 86 | "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", 87 | "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=" 88 | }, 89 | "buffer-equal": { 90 | "version": "0.0.1", 91 | "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", 92 | "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" 93 | }, 94 | "busboy": { 95 | "version": "0.2.14", 96 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", 97 | "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", 98 | "dependencies": { 99 | "isarray": { 100 | "version": "0.0.1", 101 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 102 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 103 | }, 104 | "readable-stream": { 105 | "version": "1.1.14", 106 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 107 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" 108 | }, 109 | "string_decoder": { 110 | "version": "0.10.31", 111 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 112 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 113 | } 114 | } 115 | }, 116 | "bytes": { 117 | "version": "2.4.0", 118 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 119 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 120 | }, 121 | "caseless": { 122 | "version": "0.12.0", 123 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 124 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 125 | }, 126 | "co": { 127 | "version": "4.6.0", 128 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 129 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 130 | }, 131 | "combined-stream": { 132 | "version": "1.0.5", 133 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 134 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" 135 | }, 136 | "concat-stream": { 137 | "version": "1.6.0", 138 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", 139 | "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=" 140 | }, 141 | "content-disposition": { 142 | "version": "0.5.2", 143 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 144 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 145 | }, 146 | "content-type": { 147 | "version": "1.0.4", 148 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 149 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 150 | }, 151 | "cookie": { 152 | "version": "0.3.1", 153 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 154 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 155 | }, 156 | "cookie-parser": { 157 | "version": "1.4.3", 158 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", 159 | "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=" 160 | }, 161 | "cookie-signature": { 162 | "version": "1.0.6", 163 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 164 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 165 | }, 166 | "core-util-is": { 167 | "version": "1.0.2", 168 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 169 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 170 | }, 171 | "cryptiles": { 172 | "version": "3.1.2", 173 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", 174 | "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", 175 | "dependencies": { 176 | "boom": { 177 | "version": "5.2.0", 178 | "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", 179 | "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==" 180 | } 181 | } 182 | }, 183 | "dashdash": { 184 | "version": "1.14.1", 185 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 186 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=" 187 | }, 188 | "debug": { 189 | "version": "2.6.9", 190 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 191 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==" 192 | }, 193 | "delayed-stream": { 194 | "version": "1.0.0", 195 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 196 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 197 | }, 198 | "depd": { 199 | "version": "1.1.1", 200 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 201 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 202 | }, 203 | "destroy": { 204 | "version": "1.0.4", 205 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 206 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 207 | }, 208 | "dicer": { 209 | "version": "0.2.5", 210 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", 211 | "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", 212 | "dependencies": { 213 | "isarray": { 214 | "version": "0.0.1", 215 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 216 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 217 | }, 218 | "readable-stream": { 219 | "version": "1.1.14", 220 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 221 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=" 222 | }, 223 | "string_decoder": { 224 | "version": "0.10.31", 225 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 226 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 227 | } 228 | } 229 | }, 230 | "dom-walk": { 231 | "version": "0.1.1", 232 | "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", 233 | "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" 234 | }, 235 | "dotenv": { 236 | "version": "4.0.0", 237 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", 238 | "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=" 239 | }, 240 | "ecc-jsbn": { 241 | "version": "0.1.1", 242 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 243 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 244 | "optional": true 245 | }, 246 | "ee-first": { 247 | "version": "1.1.1", 248 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 249 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 250 | }, 251 | "ejs": { 252 | "version": "2.5.7", 253 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", 254 | "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=" 255 | }, 256 | "encodeurl": { 257 | "version": "1.0.1", 258 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 259 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 260 | }, 261 | "es6-promise": { 262 | "version": "3.3.1", 263 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", 264 | "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=" 265 | }, 266 | "escape-html": { 267 | "version": "1.0.3", 268 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 269 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 270 | }, 271 | "etag": { 272 | "version": "1.8.1", 273 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 274 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 275 | }, 276 | "exif-parser": { 277 | "version": "0.1.12", 278 | "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", 279 | "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=" 280 | }, 281 | "express": { 282 | "version": "4.15.5", 283 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.5.tgz", 284 | "integrity": "sha1-ZwI1ypWYiQpa6BcLg9tyK4Qu2Sc=", 285 | "dependencies": { 286 | "qs": { 287 | "version": "6.5.0", 288 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz", 289 | "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==" 290 | }, 291 | "statuses": { 292 | "version": "1.3.1", 293 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 294 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 295 | } 296 | } 297 | }, 298 | "extend": { 299 | "version": "3.0.1", 300 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 301 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 302 | }, 303 | "extsprintf": { 304 | "version": "1.3.0", 305 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 306 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 307 | }, 308 | "fast-deep-equal": { 309 | "version": "1.0.0", 310 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", 311 | "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" 312 | }, 313 | "fast-json-stable-stringify": { 314 | "version": "2.0.0", 315 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 316 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 317 | }, 318 | "file-type": { 319 | "version": "3.9.0", 320 | "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", 321 | "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" 322 | }, 323 | "finalhandler": { 324 | "version": "1.0.6", 325 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", 326 | "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", 327 | "dependencies": { 328 | "statuses": { 329 | "version": "1.3.1", 330 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 331 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 332 | } 333 | } 334 | }, 335 | "for-each": { 336 | "version": "0.3.2", 337 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", 338 | "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=" 339 | }, 340 | "forever-agent": { 341 | "version": "0.6.1", 342 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 343 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 344 | }, 345 | "form-data": { 346 | "version": "2.3.1", 347 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", 348 | "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=" 349 | }, 350 | "forwarded": { 351 | "version": "0.1.2", 352 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 353 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 354 | }, 355 | "fresh": { 356 | "version": "0.5.2", 357 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 358 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 359 | }, 360 | "getpass": { 361 | "version": "0.1.7", 362 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 363 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=" 364 | }, 365 | "global": { 366 | "version": "4.3.2", 367 | "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", 368 | "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=" 369 | }, 370 | "har-schema": { 371 | "version": "2.0.0", 372 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 373 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 374 | }, 375 | "har-validator": { 376 | "version": "5.0.3", 377 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 378 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=" 379 | }, 380 | "hawk": { 381 | "version": "6.0.2", 382 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", 383 | "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==" 384 | }, 385 | "hoek": { 386 | "version": "4.2.0", 387 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", 388 | "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" 389 | }, 390 | "http-errors": { 391 | "version": "1.6.2", 392 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 393 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=" 394 | }, 395 | "http-signature": { 396 | "version": "1.2.0", 397 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 398 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=" 399 | }, 400 | "iconv-lite": { 401 | "version": "0.4.15", 402 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 403 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 404 | }, 405 | "inherits": { 406 | "version": "2.0.3", 407 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 408 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 409 | }, 410 | "ip-regex": { 411 | "version": "1.0.3", 412 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-1.0.3.tgz", 413 | "integrity": "sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0=" 414 | }, 415 | "ipaddr.js": { 416 | "version": "1.4.0", 417 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", 418 | "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" 419 | }, 420 | "is-function": { 421 | "version": "1.0.1", 422 | "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", 423 | "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" 424 | }, 425 | "is-typedarray": { 426 | "version": "1.0.0", 427 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 428 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 429 | }, 430 | "isarray": { 431 | "version": "1.0.0", 432 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 433 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 434 | }, 435 | "isstream": { 436 | "version": "0.1.2", 437 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 438 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 439 | }, 440 | "jimp": { 441 | "version": "0.2.28", 442 | "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.2.28.tgz", 443 | "integrity": "sha1-3VKak3GQ9ClXp5N9Gsw6d2KZbqI=" 444 | }, 445 | "jpeg-js": { 446 | "version": "0.2.0", 447 | "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz", 448 | "integrity": "sha1-U+RI7J0mPmgyZkZ+lELSxaLvVII=" 449 | }, 450 | "jsbn": { 451 | "version": "0.1.1", 452 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 453 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 454 | "optional": true 455 | }, 456 | "json-schema": { 457 | "version": "0.2.3", 458 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 459 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 460 | }, 461 | "json-schema-traverse": { 462 | "version": "0.3.1", 463 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 464 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 465 | }, 466 | "json-stringify-safe": { 467 | "version": "5.0.1", 468 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 469 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 470 | }, 471 | "jsprim": { 472 | "version": "1.4.1", 473 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 474 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=" 475 | }, 476 | "load-bmfont": { 477 | "version": "1.3.0", 478 | "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.3.0.tgz", 479 | "integrity": "sha1-u358cQ3mvK/LE8s7jIHgwBMey8k=" 480 | }, 481 | "lodash": { 482 | "version": "4.17.4", 483 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 484 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 485 | }, 486 | "media-typer": { 487 | "version": "0.3.0", 488 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 489 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 490 | }, 491 | "merge-descriptors": { 492 | "version": "1.0.1", 493 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 494 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 495 | }, 496 | "methods": { 497 | "version": "1.1.2", 498 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 499 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 500 | }, 501 | "mime": { 502 | "version": "1.3.4", 503 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 504 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 505 | }, 506 | "mime-db": { 507 | "version": "1.30.0", 508 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", 509 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 510 | }, 511 | "mime-types": { 512 | "version": "2.1.17", 513 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 514 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=" 515 | }, 516 | "min-document": { 517 | "version": "2.19.0", 518 | "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", 519 | "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=" 520 | }, 521 | "minimist": { 522 | "version": "0.0.8", 523 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 524 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 525 | }, 526 | "mkdirp": { 527 | "version": "0.5.1", 528 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 529 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=" 530 | }, 531 | "morgan": { 532 | "version": "1.8.2", 533 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.2.tgz", 534 | "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc=", 535 | "dependencies": { 536 | "debug": { 537 | "version": "2.6.8", 538 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 539 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=" 540 | } 541 | } 542 | }, 543 | "ms": { 544 | "version": "2.0.0", 545 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 546 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 547 | }, 548 | "multer": { 549 | "version": "1.3.0", 550 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz", 551 | "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=" 552 | }, 553 | "negotiator": { 554 | "version": "0.6.1", 555 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 556 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 557 | }, 558 | "oauth-sign": { 559 | "version": "0.8.2", 560 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 561 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 562 | }, 563 | "object-assign": { 564 | "version": "3.0.0", 565 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", 566 | "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" 567 | }, 568 | "on-finished": { 569 | "version": "2.3.0", 570 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 571 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 572 | }, 573 | "on-headers": { 574 | "version": "1.0.1", 575 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", 576 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" 577 | }, 578 | "parse-bmfont-ascii": { 579 | "version": "1.0.6", 580 | "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", 581 | "integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU=" 582 | }, 583 | "parse-bmfont-binary": { 584 | "version": "1.0.6", 585 | "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", 586 | "integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY=" 587 | }, 588 | "parse-bmfont-xml": { 589 | "version": "1.1.3", 590 | "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.3.tgz", 591 | "integrity": "sha1-1rZqNxr9OcUAfZ8O6yYqTyzOe3w=" 592 | }, 593 | "parse-headers": { 594 | "version": "2.0.1", 595 | "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz", 596 | "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=" 597 | }, 598 | "parseurl": { 599 | "version": "1.3.2", 600 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 601 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 602 | }, 603 | "path-to-regexp": { 604 | "version": "0.1.7", 605 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 606 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 607 | }, 608 | "performance-now": { 609 | "version": "2.1.0", 610 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 611 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 612 | }, 613 | "pixelmatch": { 614 | "version": "4.0.2", 615 | "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", 616 | "integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=" 617 | }, 618 | "pngjs": { 619 | "version": "3.3.1", 620 | "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.1.tgz", 621 | "integrity": "sha512-ggXCTsqHRIsGMkHlCEhbHhUmNTA2r1lpkE0NL4Q9S8spkXbm4vE9TVmPso2AGYn90Gltdz8W5CyzhcIGg2Gejg==" 622 | }, 623 | "process": { 624 | "version": "0.5.2", 625 | "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", 626 | "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" 627 | }, 628 | "process-nextick-args": { 629 | "version": "1.0.7", 630 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 631 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" 632 | }, 633 | "proxy-addr": { 634 | "version": "1.1.5", 635 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", 636 | "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=" 637 | }, 638 | "qs": { 639 | "version": "6.4.0", 640 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 641 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 642 | }, 643 | "range-parser": { 644 | "version": "1.2.0", 645 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 646 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 647 | }, 648 | "raw-body": { 649 | "version": "2.2.0", 650 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 651 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" 652 | }, 653 | "read-chunk": { 654 | "version": "1.0.1", 655 | "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz", 656 | "integrity": "sha1-X2jKswfmY/GZk1J9m1icrORmEZQ=" 657 | }, 658 | "readable-stream": { 659 | "version": "2.3.3", 660 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 661 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==" 662 | }, 663 | "request": { 664 | "version": "2.83.0", 665 | "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", 666 | "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", 667 | "dependencies": { 668 | "qs": { 669 | "version": "6.5.1", 670 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 671 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 672 | } 673 | } 674 | }, 675 | "safe-buffer": { 676 | "version": "5.1.1", 677 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 678 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 679 | }, 680 | "sax": { 681 | "version": "1.2.1", 682 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 683 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 684 | }, 685 | "send": { 686 | "version": "0.15.6", 687 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.6.tgz", 688 | "integrity": "sha1-IPI6nJJbdiq4JwX+L52yUqzkfjQ=", 689 | "dependencies": { 690 | "statuses": { 691 | "version": "1.3.1", 692 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 693 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 694 | } 695 | } 696 | }, 697 | "serve-favicon": { 698 | "version": "2.4.5", 699 | "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.4.5.tgz", 700 | "integrity": "sha512-s7F8h2NrslMkG50KxvlGdj+ApSwaLex0vexuJ9iFf3GLTIp1ph/l1qZvRe9T9TJEYZgmq72ZwJ2VYiAEtChknw==" 701 | }, 702 | "serve-static": { 703 | "version": "1.12.6", 704 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.6.tgz", 705 | "integrity": "sha1-uXN3P2NEmTTaVOW+ul4x2fQhFXc=" 706 | }, 707 | "setprototypeof": { 708 | "version": "1.0.3", 709 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 710 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 711 | }, 712 | "sntp": { 713 | "version": "2.1.0", 714 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", 715 | "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==" 716 | }, 717 | "sshpk": { 718 | "version": "1.13.1", 719 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", 720 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=" 721 | }, 722 | "statuses": { 723 | "version": "1.4.0", 724 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 725 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 726 | }, 727 | "stream-to": { 728 | "version": "0.2.2", 729 | "resolved": "https://registry.npmjs.org/stream-to/-/stream-to-0.2.2.tgz", 730 | "integrity": "sha1-hDBgmNhf25kLn6MAsbPM9V6O8B0=" 731 | }, 732 | "stream-to-buffer": { 733 | "version": "0.1.0", 734 | "resolved": "https://registry.npmjs.org/stream-to-buffer/-/stream-to-buffer-0.1.0.tgz", 735 | "integrity": "sha1-JnmdkDqyAlyb1VCsRxcbAPjdgKk=" 736 | }, 737 | "streamifier": { 738 | "version": "0.1.1", 739 | "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", 740 | "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=" 741 | }, 742 | "streamsearch": { 743 | "version": "0.1.2", 744 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", 745 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" 746 | }, 747 | "string_decoder": { 748 | "version": "1.0.3", 749 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 750 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==" 751 | }, 752 | "stringstream": { 753 | "version": "0.0.5", 754 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", 755 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" 756 | }, 757 | "tinycolor2": { 758 | "version": "1.4.1", 759 | "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", 760 | "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" 761 | }, 762 | "tough-cookie": { 763 | "version": "2.3.3", 764 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", 765 | "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", 766 | "dependencies": { 767 | "punycode": { 768 | "version": "1.4.1", 769 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 770 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 771 | } 772 | } 773 | }, 774 | "trim": { 775 | "version": "0.0.1", 776 | "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", 777 | "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" 778 | }, 779 | "tunnel-agent": { 780 | "version": "0.6.0", 781 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 782 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" 783 | }, 784 | "tweetnacl": { 785 | "version": "0.14.5", 786 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 787 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 788 | "optional": true 789 | }, 790 | "type-is": { 791 | "version": "1.6.15", 792 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 793 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 794 | }, 795 | "typedarray": { 796 | "version": "0.0.6", 797 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 798 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 799 | }, 800 | "unpipe": { 801 | "version": "1.0.0", 802 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 803 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 804 | }, 805 | "url-regex": { 806 | "version": "3.2.0", 807 | "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz", 808 | "integrity": "sha1-260eDJ4p4QXdCx8J9oYvf9tIJyQ=" 809 | }, 810 | "util-deprecate": { 811 | "version": "1.0.2", 812 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 813 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 814 | }, 815 | "utils-merge": { 816 | "version": "1.0.0", 817 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 818 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 819 | }, 820 | "uuid": { 821 | "version": "3.1.0", 822 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 823 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 824 | }, 825 | "vary": { 826 | "version": "1.1.2", 827 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 828 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 829 | }, 830 | "verror": { 831 | "version": "1.10.0", 832 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 833 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=" 834 | }, 835 | "xhr": { 836 | "version": "2.4.0", 837 | "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.0.tgz", 838 | "integrity": "sha1-4W5mpF+GmGHu76tBbV7/ci3ECZM=" 839 | }, 840 | "xml-parse-from-string": { 841 | "version": "1.0.1", 842 | "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", 843 | "integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig=" 844 | }, 845 | "xml2js": { 846 | "version": "0.4.17", 847 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", 848 | "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=" 849 | }, 850 | "xmlbuilder": { 851 | "version": "4.2.1", 852 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", 853 | "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=" 854 | }, 855 | "xtend": { 856 | "version": "4.0.1", 857 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 858 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 859 | } 860 | } 861 | } 862 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "photo-uploader-app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.17.1", 10 | "concat-stream": "^1.6.0", 11 | "cookie-parser": "~1.4.3", 12 | "debug": "~2.6.3", 13 | "dotenv": "^4.0.0", 14 | "ejs": "~2.5.6", 15 | "express": "~4.15.2", 16 | "jimp": "^0.2.28", 17 | "mkdirp": "^0.5.1", 18 | "morgan": "~1.8.1", 19 | "multer": "^1.3.0", 20 | "serve-favicon": "~2.4.2", 21 | "streamifier": "^0.1.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | // import multer and the AvatarStorage engine 5 | var _ = require('lodash'); 6 | var path = require('path'); 7 | var multer = require('multer'); 8 | var AvatarStorage = require('../helpers/AvatarStorage'); 9 | 10 | // setup a new instance of the AvatarStorage engine 11 | var storage = AvatarStorage({ 12 | square: true, 13 | responsive: true, 14 | greyscale: true, 15 | quality: 90 16 | }); 17 | 18 | var limits = { 19 | files: 1, // allow only 1 file per request 20 | fileSize: 1024 * 1024, // 1 MB (max file size) 21 | }; 22 | 23 | var fileFilter = function(req, file, cb) { 24 | // supported image file mimetypes 25 | var allowedMimes = ['image/jpeg', 'image/pjpeg', 'image/png', 'image/gif']; 26 | 27 | if (_.includes(allowedMimes, file.mimetype)) { 28 | // allow supported image files 29 | cb(null, true); 30 | } else { 31 | // throw error for invalid files 32 | cb(new Error('Invalid file type. Only jpg, png and gif image files are allowed.')); 33 | } 34 | }; 35 | 36 | // setup multer 37 | var upload = multer({ 38 | storage: storage, 39 | limits: limits, 40 | fileFilter: fileFilter 41 | }); 42 | 43 | /* GET home page. */ 44 | router.get('/', function(req, res, next) { 45 | res.render('index', { title: 'Upload Avatar', avatar_field: process.env.AVATAR_FIELD }); 46 | }); 47 | 48 | router.post('/upload', upload.single(process.env.AVATAR_FIELD), function(req, res, next) { 49 | 50 | var files; 51 | var file = req.file.filename; 52 | var matches = file.match(/^(.+?)_.+?\.(.+)$/i); 53 | 54 | if (matches) { 55 | files = _.map(['lg', 'md', 'sm'], function(size) { 56 | return matches[1] + '_' + size + '.' + matches[2]; 57 | }); 58 | } else { 59 | files = [file]; 60 | } 61 | 62 | files = _.map(files, function(file) { 63 | var port = req.app.get('port'); 64 | var base = req.protocol + '://' + req.hostname + (port ? ':' + port : ''); 65 | var url = path.join(req.file.baseUrl, file).replace(/[\\\/]+/g, '/').replace(/^[\/]+/g, ''); 66 | 67 | return (req.file.storage == 'local' ? base : '') + '/' + url; 68 | }); 69 | 70 | res.json({ 71 | images: files 72 | }); 73 | 74 | }); 75 | 76 | module.exports = router; 77 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |
<%= error.stack %>4 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |