├── .gitignore ├── views ├── error.hbs ├── layout.hbs └── index.hbs ├── public └── stylesheets │ └── style.css ├── test └── test.js ├── config.js ├── functions └── shorten.js ├── package.json ├── readme.md ├── routes ├── index.js └── add.js ├── app.js ├── bin └── www └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /views/error.hbs: -------------------------------------------------------------------------------- 1 |

{{message}}

2 |

{{error.status}}

3 |
{{error.stack}}
4 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px Helvetica, "Lucida Grande", Arial, sans-serif; 4 | } 5 | 6 | .header { 7 | text-align: center; 8 | } 9 | a { 10 | color: #00B7FF; 11 | } 12 | -------------------------------------------------------------------------------- /views/layout.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | URL Shortener Microservice 6 | 7 | 8 | 9 | 10 | {{{body}}} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var expect = require("chai").expect; 2 | var shorten = require("../functions/shorten"); 3 | 4 | // test the function that generate the short Url from a counter 5 | describe('shorten', () => { 6 | it('should return a string', () => { 7 | expect(shorten(10)).to.be.a("string"); 8 | }); 9 | 10 | it('shoud convert 20 to k', () => { 11 | expect(shorten(20)).to.equal('k'); 12 | }); 13 | 14 | it('should convert 1000 to g8', () => { 15 | expect(shorten(1000)).to.equal("g8"); 16 | }); 17 | }); -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | let NODE_ENV = process.env.NODE_ENV.trim(), 4 | DB_URI = "", 5 | APP_URI = "", 6 | PORT = ""; 7 | 8 | if (NODE_ENV == "development") { 9 | require("dotenv").config(); 10 | DB_URI = process.env.DB_URI_DEV; 11 | APP_URI = process.env.APP_URI_DEV; 12 | PORT = process.env.PORT_DEV; 13 | 14 | } else if (NODE_ENV == "production") { 15 | DB_URI = process.env.DB_URI; 16 | APP_URI = process.env.APP_URI; 17 | PORT = process.env.PORT; 18 | } 19 | 20 | module.exports = { 21 | NODE_ENV, 22 | DB_URI, 23 | APP_URI, 24 | PORT 25 | } -------------------------------------------------------------------------------- /functions/shorten.js: -------------------------------------------------------------------------------- 1 | function shorten(counter) { 2 | 3 | let alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 4 | let shortUrl = []; 5 | let quotient = Math.floor(counter / alphabet.length); 6 | 7 | while (quotient > 1) { 8 | shortUrl.push(alphabet[quotient]); 9 | counter = counter - (quotient * alphabet.length); 10 | quotient = Math.floor(counter / alphabet.length); 11 | } 12 | 13 | shortUrl.push(alphabet[counter % alphabet.length]); 14 | shortUrl = shortUrl.join(''); 15 | 16 | return shortUrl; 17 | } 18 | 19 | module.exports = shorten; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "url-shortner", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "dev": "set NODE_ENV=development&& node ./bin/www", 8 | "prod": "set NODE_ENV=production&& node ./bin/www" 9 | }, 10 | "dependencies": { 11 | "body-parser": "~1.16.0", 12 | "cookie-parser": "~1.4.3", 13 | "debug": "~2.6.0", 14 | "express": "~4.14.1", 15 | "hbs": "~4.0.1", 16 | "mongodb": "^2.2.25", 17 | "morgan": "~1.7.0", 18 | "serve-favicon": "~2.3.2", 19 | "validator": "^8.0.0" 20 | }, 21 | "devDependencies": { 22 | "chai": "^3.5.0", 23 | "dotenv": "^4.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # URL Shortener Microservice 2 | 3 | ## User stories: 4 | 5 | - I can pass a URL as a parameter and I will receive a shortened URL in the JSON response. 6 | - When I visit that shortened URL, it will redirect me to my original link. 7 | - If I pass an invalid URL that doesn't follow the valid http://www.example.com format, the JSON response will contain an error instead. 8 | 9 | ### Example creation usage: 10 | 11 | ```https://u2s.herokuapp.com/add/https://abdelaziz18003.github.io/tic-tac-toe/``` 12 | 13 | 14 | ### Example creation output 15 | ```{"longUrl": "https://abdelaziz18003.github.io/tic-tac-toe/", "shortUrl": "https://u2s.herokuapp.com/l"}``` 16 | 17 | ### Usage: 18 | https://u2s.herokuapp.com/l 19 | 20 | ### Will redirect to: 21 | ```https://abdelaziz18003.github.io/tic-tac-toe/``` 22 | -------------------------------------------------------------------------------- /views/index.hbs: -------------------------------------------------------------------------------- 1 |

URL Shortener Microservice

2 | 3 | 4 |

User stories:

5 | 11 | 12 |

Example creation usage:

13 | 14 | https://u2s.herokuapp.com/add/https://abdelaziz18003.github.io/tic-tac-toe/ 15 | 16 | 17 |

Example creation output

18 |
19 |     { 
20 |         "longUrl": "https://abdelaziz18003.github.io/tic-tac-toe/", 
21 |         "shortUrl": "https://u2s.herokuapp.com/l" 
22 |     }
23 | 
24 | 25 |

Usage:

26 | https://u2s.herokuapp.com/l 27 | 28 |

Will redirect to:

29 | https://abdelaziz18003.github.io/tic-tac-toe/ 30 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var assert = require("assert"); 4 | var mongo = require("mongodb").MongoClient; 5 | var DB_URI = require("../config").DB_URI; 6 | 7 | /* GET home page. */ 8 | router.get('/', function (req, res, next) { 9 | res.render('index'); 10 | }); 11 | 12 | /* GET the shortened URL. */ 13 | router.get('/:shortUrl', function (req, res, next) { 14 | 15 | // connect to the database 16 | mongo.connect(DB_URI, function (err, db) { 17 | assert.equal(err, null, "can not connect to the database"); 18 | 19 | // search for the unique document containing the provided short url 20 | var urls = db.collection("urls"); 21 | urls.find({ shortUrl: req.params.shortUrl }).toArray(function (err, docs) { 22 | 23 | if (docs.length > 0) { 24 | 25 | // redirect to corresponding original url 26 | res.redirect(docs[0].longUrl); 27 | } else { 28 | res.render("error", { 29 | message: "page not found", 30 | error: { 31 | status: 404, 32 | stack: "the url you have entered doesn't exist in the database" 33 | } 34 | }); 35 | } 36 | }) 37 | }) 38 | }); 39 | 40 | module.exports = router; -------------------------------------------------------------------------------- /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 | 8 | var index = require('./routes/index'); 9 | var add = require('./routes/add'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'hbs'); 16 | 17 | // uncomment after placing your favicon in /public 18 | // app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', index); 26 | app.use('/add', add); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function (req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handler 36 | app.use(function (err, req, res, next) { 37 | // set locals, only providing error in development 38 | res.locals.message = err.message; 39 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 40 | 41 | // render the error page 42 | res.status(err.status || 500); 43 | res.render('error'); 44 | }); 45 | 46 | module.exports = app; -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('url-shortner:server'); 9 | var http = require('http'); 10 | var PORT = require("../config").PORT; 11 | 12 | /** 13 | * Get port from environment and store in Express. 14 | */ 15 | 16 | var port = normalizePort(process.env.PORT || PORT || '3000'); 17 | app.set('port', port); 18 | 19 | /** 20 | * Create HTTP server. 21 | */ 22 | 23 | var server = http.createServer(app); 24 | 25 | /** 26 | * Listen on provided port, on all network interfaces. 27 | */ 28 | 29 | server.listen(port); 30 | server.on('error', onError); 31 | server.on('listening', onListening); 32 | 33 | /** 34 | * Normalize a port into a number, string, or false. 35 | */ 36 | 37 | function normalizePort(val) { 38 | var port = parseInt(val, 10); 39 | 40 | if (isNaN(port)) { 41 | // named pipe 42 | return val; 43 | } 44 | 45 | if (port >= 0) { 46 | // port number 47 | return port; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * Event listener for HTTP server "error" event. 55 | */ 56 | 57 | function onError(error) { 58 | if (error.syscall !== 'listen') { 59 | throw error; 60 | } 61 | 62 | var bind = typeof port === 'string' ? 63 | 'Pipe ' + port : 64 | 'Port ' + port; 65 | 66 | // handle specific listen errors with friendly messages 67 | switch (error.code) { 68 | case 'EACCES': 69 | console.error(bind + ' requires elevated privileges'); 70 | process.exit(1); 71 | break; 72 | case 'EADDRINUSE': 73 | console.error(bind + ' is already in use'); 74 | process.exit(1); 75 | break; 76 | default: 77 | throw error; 78 | } 79 | } 80 | 81 | /** 82 | * Event listener for HTTP server "listening" event. 83 | */ 84 | 85 | function onListening() { 86 | var addr = server.address(); 87 | var bind = typeof addr === 'string' ? 88 | 'pipe ' + addr : 89 | 'port ' + addr.port; 90 | debug('Listening on ' + bind); 91 | } -------------------------------------------------------------------------------- /routes/add.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var assert = require("assert"); 3 | var router = express.Router(); 4 | var mongo = require("mongodb").MongoClient; 5 | var shorten = require("../functions/shorten"); 6 | var validator = require("validator"); 7 | 8 | // load constant from config file 9 | const DB_URI = require("../config").DB_URI; 10 | const APP_URI = require("../config").APP_URI; 11 | const PORT = require("../config").PORT; 12 | const NODE_ENV = require("../config").NODE_ENV; 13 | 14 | /* GET api description. */ 15 | router.get('/', function (req, res, next) { 16 | res.render('index'); 17 | }); 18 | 19 | /* shortening a new Url. */ 20 | router.get('/*', function (req, res, next) { 21 | 22 | // check if the passed string is a valid URL 23 | if (validator.isURL(req.params[0])) { 24 | 25 | mongo.connect(DB_URI, function (err, db) { 26 | assert.equal(null, err); 27 | 28 | var newUrl = {}; 29 | 30 | // get the max value of counter 31 | var urls = db.collection("urls"); 32 | urls.count({}, function (err, docsNum) { 33 | assert.equal(err, null, "error counting the number of docs"); 34 | 35 | // make a new url object 36 | newUrl = { 37 | longUrl: req.params[0], 38 | shortUrl: shorten(docsNum + 1) 39 | }; 40 | 41 | // save the new url to the database 42 | urls.insert(newUrl, function () { 43 | 44 | // respond with a json file containing both the long and the short urls 45 | res.json({ 46 | longUrl: newUrl.longUrl, 47 | shortUrl: "https://" + APP_URI + (NODE_ENV == "development" ? ":" + PORT : "") + "/" + newUrl.shortUrl 48 | }); 49 | db.close(); 50 | }); 51 | }); 52 | }); 53 | // response when the passed string is not a valid URL 54 | } else { 55 | res.json({ 56 | "error": "you have passed an invalid URL" 57 | }) 58 | } 59 | }) 60 | module.exports = router; -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.3: 6 | version "1.3.3" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" 8 | dependencies: 9 | mime-types "~2.1.11" 10 | negotiator "0.6.1" 11 | 12 | align-text@^0.1.1, align-text@^0.1.3: 13 | version "0.1.4" 14 | resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" 15 | dependencies: 16 | kind-of "^3.0.2" 17 | longest "^1.0.1" 18 | repeat-string "^1.5.2" 19 | 20 | amdefine@>=0.0.4: 21 | version "1.0.1" 22 | resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" 23 | 24 | array-flatten@1.1.1: 25 | version "1.1.1" 26 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 27 | 28 | assertion-error@^1.0.1: 29 | version "1.0.2" 30 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" 31 | 32 | async@^1.4.0: 33 | version "1.5.2" 34 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" 35 | 36 | basic-auth@~1.0.3: 37 | version "1.0.4" 38 | resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.0.4.tgz#030935b01de7c9b94a824b29f3fccb750d3a5290" 39 | 40 | body-parser@~1.16.0: 41 | version "1.16.1" 42 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.1.tgz#51540d045adfa7a0c6995a014bb6b1ed9b802329" 43 | dependencies: 44 | bytes "2.4.0" 45 | content-type "~1.0.2" 46 | debug "2.6.1" 47 | depd "~1.1.0" 48 | http-errors "~1.5.1" 49 | iconv-lite "0.4.15" 50 | on-finished "~2.3.0" 51 | qs "6.2.1" 52 | raw-body "~2.2.0" 53 | type-is "~1.6.14" 54 | 55 | bson@~1.0.4: 56 | version "1.0.4" 57 | resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.4.tgz#93c10d39eaa5b58415cbc4052f3e53e562b0b72c" 58 | 59 | buffer-shims@^1.0.0: 60 | version "1.0.0" 61 | resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" 62 | 63 | bytes@2.4.0: 64 | version "2.4.0" 65 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" 66 | 67 | camelcase@^1.0.2: 68 | version "1.2.1" 69 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" 70 | 71 | center-align@^0.1.1: 72 | version "0.1.3" 73 | resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" 74 | dependencies: 75 | align-text "^0.1.3" 76 | lazy-cache "^1.0.3" 77 | 78 | chai@^3.5.0: 79 | version "3.5.0" 80 | resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" 81 | dependencies: 82 | assertion-error "^1.0.1" 83 | deep-eql "^0.1.3" 84 | type-detect "^1.0.0" 85 | 86 | cliui@^2.1.0: 87 | version "2.1.0" 88 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" 89 | dependencies: 90 | center-align "^0.1.1" 91 | right-align "^0.1.1" 92 | wordwrap "0.0.2" 93 | 94 | content-disposition@0.5.2: 95 | version "0.5.2" 96 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 97 | 98 | content-type@~1.0.2: 99 | version "1.0.2" 100 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" 101 | 102 | cookie-parser@~1.4.3: 103 | version "1.4.3" 104 | resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5" 105 | dependencies: 106 | cookie "0.3.1" 107 | cookie-signature "1.0.6" 108 | 109 | cookie-signature@1.0.6: 110 | version "1.0.6" 111 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 112 | 113 | cookie@0.3.1: 114 | version "0.3.1" 115 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 116 | 117 | core-util-is@~1.0.0: 118 | version "1.0.2" 119 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 120 | 121 | debug@2.6.1: 122 | version "2.6.1" 123 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" 124 | dependencies: 125 | ms "0.7.2" 126 | 127 | debug@~2.2.0: 128 | version "2.2.0" 129 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 130 | dependencies: 131 | ms "0.7.1" 132 | 133 | debug@~2.6.0: 134 | version "2.6.3" 135 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d" 136 | dependencies: 137 | ms "0.7.2" 138 | 139 | decamelize@^1.0.0: 140 | version "1.2.0" 141 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 142 | 143 | deep-eql@^0.1.3: 144 | version "0.1.3" 145 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" 146 | dependencies: 147 | type-detect "0.1.1" 148 | 149 | depd@~1.1.0: 150 | version "1.1.0" 151 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" 152 | 153 | destroy@~1.0.4: 154 | version "1.0.4" 155 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 156 | 157 | dotenv@^4.0.0: 158 | version "4.0.0" 159 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" 160 | 161 | ee-first@1.1.1: 162 | version "1.1.1" 163 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 164 | 165 | encodeurl@~1.0.1: 166 | version "1.0.1" 167 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" 168 | 169 | es6-promise@3.2.1: 170 | version "3.2.1" 171 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4" 172 | 173 | escape-html@~1.0.3: 174 | version "1.0.3" 175 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 176 | 177 | etag@~1.7.0: 178 | version "1.7.0" 179 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8" 180 | 181 | express@~4.14.1: 182 | version "4.14.1" 183 | resolved "https://registry.yarnpkg.com/express/-/express-4.14.1.tgz#646c237f766f148c2120aff073817b9e4d7e0d33" 184 | dependencies: 185 | accepts "~1.3.3" 186 | array-flatten "1.1.1" 187 | content-disposition "0.5.2" 188 | content-type "~1.0.2" 189 | cookie "0.3.1" 190 | cookie-signature "1.0.6" 191 | debug "~2.2.0" 192 | depd "~1.1.0" 193 | encodeurl "~1.0.1" 194 | escape-html "~1.0.3" 195 | etag "~1.7.0" 196 | finalhandler "0.5.1" 197 | fresh "0.3.0" 198 | merge-descriptors "1.0.1" 199 | methods "~1.1.2" 200 | on-finished "~2.3.0" 201 | parseurl "~1.3.1" 202 | path-to-regexp "0.1.7" 203 | proxy-addr "~1.1.3" 204 | qs "6.2.0" 205 | range-parser "~1.2.0" 206 | send "0.14.2" 207 | serve-static "~1.11.2" 208 | type-is "~1.6.14" 209 | utils-merge "1.0.0" 210 | vary "~1.1.0" 211 | 212 | finalhandler@0.5.1: 213 | version "0.5.1" 214 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.1.tgz#2c400d8d4530935bc232549c5fa385ec07de6fcd" 215 | dependencies: 216 | debug "~2.2.0" 217 | escape-html "~1.0.3" 218 | on-finished "~2.3.0" 219 | statuses "~1.3.1" 220 | unpipe "~1.0.0" 221 | 222 | foreachasync@^3.0.0: 223 | version "3.0.0" 224 | resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6" 225 | 226 | forwarded@~0.1.0: 227 | version "0.1.0" 228 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" 229 | 230 | fresh@0.3.0: 231 | version "0.3.0" 232 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f" 233 | 234 | handlebars@4.0.5: 235 | version "4.0.5" 236 | resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.5.tgz#92c6ed6bb164110c50d4d8d0fbddc70806c6f8e7" 237 | dependencies: 238 | async "^1.4.0" 239 | optimist "^0.6.1" 240 | source-map "^0.4.4" 241 | optionalDependencies: 242 | uglify-js "^2.6" 243 | 244 | hbs@~4.0.1: 245 | version "4.0.1" 246 | resolved "https://registry.yarnpkg.com/hbs/-/hbs-4.0.1.tgz#4bfd98650dc8c9dac44b3ca9adf9c098e8bc33b6" 247 | dependencies: 248 | handlebars "4.0.5" 249 | walk "2.3.9" 250 | 251 | http-errors@~1.5.1: 252 | version "1.5.1" 253 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" 254 | dependencies: 255 | inherits "2.0.3" 256 | setprototypeof "1.0.2" 257 | statuses ">= 1.3.1 < 2" 258 | 259 | iconv-lite@0.4.15: 260 | version "0.4.15" 261 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" 262 | 263 | inherits@2.0.3, inherits@~2.0.1: 264 | version "2.0.3" 265 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 266 | 267 | ipaddr.js@1.2.0: 268 | version "1.2.0" 269 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.2.0.tgz#8aba49c9192799585bdd643e0ccb50e8ae777ba4" 270 | 271 | is-buffer@^1.0.2: 272 | version "1.1.5" 273 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" 274 | 275 | isarray@~1.0.0: 276 | version "1.0.0" 277 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 278 | 279 | kind-of@^3.0.2: 280 | version "3.1.0" 281 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47" 282 | dependencies: 283 | is-buffer "^1.0.2" 284 | 285 | lazy-cache@^1.0.3: 286 | version "1.0.4" 287 | resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" 288 | 289 | longest@^1.0.1: 290 | version "1.0.1" 291 | resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" 292 | 293 | media-typer@0.3.0: 294 | version "0.3.0" 295 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 296 | 297 | merge-descriptors@1.0.1: 298 | version "1.0.1" 299 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 300 | 301 | methods@~1.1.2: 302 | version "1.1.2" 303 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 304 | 305 | mime-db@~1.27.0: 306 | version "1.27.0" 307 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" 308 | 309 | mime-types@~2.1.11, mime-types@~2.1.13: 310 | version "2.1.15" 311 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" 312 | dependencies: 313 | mime-db "~1.27.0" 314 | 315 | mime@1.3.4: 316 | version "1.3.4" 317 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" 318 | 319 | minimist@~0.0.1: 320 | version "0.0.10" 321 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" 322 | 323 | mongodb-core@2.1.9: 324 | version "2.1.9" 325 | resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.9.tgz#85aa71ee4fb716196e06b787557bf139f801daf5" 326 | dependencies: 327 | bson "~1.0.4" 328 | require_optional "~1.0.0" 329 | 330 | mongodb@^2.2.25: 331 | version "2.2.25" 332 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.25.tgz#d3b25dad00eda2bdfcbc996210ba082ac686a6b6" 333 | dependencies: 334 | es6-promise "3.2.1" 335 | mongodb-core "2.1.9" 336 | readable-stream "2.1.5" 337 | 338 | morgan@~1.7.0: 339 | version "1.7.0" 340 | resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.7.0.tgz#eb10ca8e50d1abe0f8d3dad5c0201d052d981c62" 341 | dependencies: 342 | basic-auth "~1.0.3" 343 | debug "~2.2.0" 344 | depd "~1.1.0" 345 | on-finished "~2.3.0" 346 | on-headers "~1.0.1" 347 | 348 | ms@0.7.1: 349 | version "0.7.1" 350 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" 351 | 352 | ms@0.7.2: 353 | version "0.7.2" 354 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" 355 | 356 | negotiator@0.6.1: 357 | version "0.6.1" 358 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 359 | 360 | on-finished@~2.3.0: 361 | version "2.3.0" 362 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 363 | dependencies: 364 | ee-first "1.1.1" 365 | 366 | on-headers@~1.0.1: 367 | version "1.0.1" 368 | resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" 369 | 370 | optimist@^0.6.1: 371 | version "0.6.1" 372 | resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" 373 | dependencies: 374 | minimist "~0.0.1" 375 | wordwrap "~0.0.2" 376 | 377 | parseurl@~1.3.1: 378 | version "1.3.1" 379 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" 380 | 381 | path-to-regexp@0.1.7: 382 | version "0.1.7" 383 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 384 | 385 | process-nextick-args@~1.0.6: 386 | version "1.0.7" 387 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 388 | 389 | proxy-addr@~1.1.3: 390 | version "1.1.3" 391 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074" 392 | dependencies: 393 | forwarded "~0.1.0" 394 | ipaddr.js "1.2.0" 395 | 396 | qs@6.2.0: 397 | version "6.2.0" 398 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b" 399 | 400 | qs@6.2.1: 401 | version "6.2.1" 402 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" 403 | 404 | range-parser@~1.2.0: 405 | version "1.2.0" 406 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 407 | 408 | raw-body@~2.2.0: 409 | version "2.2.0" 410 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" 411 | dependencies: 412 | bytes "2.4.0" 413 | iconv-lite "0.4.15" 414 | unpipe "1.0.0" 415 | 416 | readable-stream@2.1.5: 417 | version "2.1.5" 418 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" 419 | dependencies: 420 | buffer-shims "^1.0.0" 421 | core-util-is "~1.0.0" 422 | inherits "~2.0.1" 423 | isarray "~1.0.0" 424 | process-nextick-args "~1.0.6" 425 | string_decoder "~0.10.x" 426 | util-deprecate "~1.0.1" 427 | 428 | repeat-string@^1.5.2: 429 | version "1.6.1" 430 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" 431 | 432 | require_optional@~1.0.0: 433 | version "1.0.0" 434 | resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.0.tgz#52a86137a849728eb60a55533617f8f914f59abf" 435 | dependencies: 436 | resolve-from "^2.0.0" 437 | semver "^5.1.0" 438 | 439 | resolve-from@^2.0.0: 440 | version "2.0.0" 441 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" 442 | 443 | right-align@^0.1.1: 444 | version "0.1.3" 445 | resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" 446 | dependencies: 447 | align-text "^0.1.1" 448 | 449 | semver@^5.1.0: 450 | version "5.3.0" 451 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" 452 | 453 | send@0.14.2: 454 | version "0.14.2" 455 | resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef" 456 | dependencies: 457 | debug "~2.2.0" 458 | depd "~1.1.0" 459 | destroy "~1.0.4" 460 | encodeurl "~1.0.1" 461 | escape-html "~1.0.3" 462 | etag "~1.7.0" 463 | fresh "0.3.0" 464 | http-errors "~1.5.1" 465 | mime "1.3.4" 466 | ms "0.7.2" 467 | on-finished "~2.3.0" 468 | range-parser "~1.2.0" 469 | statuses "~1.3.1" 470 | 471 | serve-favicon@~2.3.2: 472 | version "2.3.2" 473 | resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.3.2.tgz#dd419e268de012ab72b319d337f2105013f9381f" 474 | dependencies: 475 | etag "~1.7.0" 476 | fresh "0.3.0" 477 | ms "0.7.2" 478 | parseurl "~1.3.1" 479 | 480 | serve-static@~1.11.2: 481 | version "1.11.2" 482 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7" 483 | dependencies: 484 | encodeurl "~1.0.1" 485 | escape-html "~1.0.3" 486 | parseurl "~1.3.1" 487 | send "0.14.2" 488 | 489 | setprototypeof@1.0.2: 490 | version "1.0.2" 491 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" 492 | 493 | source-map@^0.4.4: 494 | version "0.4.4" 495 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" 496 | dependencies: 497 | amdefine ">=0.0.4" 498 | 499 | source-map@~0.5.1: 500 | version "0.5.6" 501 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 502 | 503 | "statuses@>= 1.3.1 < 2", statuses@~1.3.1: 504 | version "1.3.1" 505 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 506 | 507 | string_decoder@~0.10.x: 508 | version "0.10.31" 509 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 510 | 511 | type-detect@0.1.1: 512 | version "0.1.1" 513 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" 514 | 515 | type-detect@^1.0.0: 516 | version "1.0.0" 517 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" 518 | 519 | type-is@~1.6.14: 520 | version "1.6.14" 521 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2" 522 | dependencies: 523 | media-typer "0.3.0" 524 | mime-types "~2.1.13" 525 | 526 | uglify-js@^2.6: 527 | version "2.8.15" 528 | resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.15.tgz#835dd4cd5872554756e6874508d0d0561704d94d" 529 | dependencies: 530 | source-map "~0.5.1" 531 | yargs "~3.10.0" 532 | optionalDependencies: 533 | uglify-to-browserify "~1.0.0" 534 | 535 | uglify-to-browserify@~1.0.0: 536 | version "1.0.2" 537 | resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" 538 | 539 | unpipe@1.0.0, unpipe@~1.0.0: 540 | version "1.0.0" 541 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 542 | 543 | util-deprecate@~1.0.1: 544 | version "1.0.2" 545 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 546 | 547 | utils-merge@1.0.0: 548 | version "1.0.0" 549 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" 550 | 551 | validator@^8.0.0: 552 | version "8.0.0" 553 | resolved "https://registry.yarnpkg.com/validator/-/validator-8.0.0.tgz#00d6ec230ab5d3353ab1174162a96462b947bdbd" 554 | 555 | vary@~1.1.0: 556 | version "1.1.1" 557 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" 558 | 559 | walk@2.3.9: 560 | version "2.3.9" 561 | resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.9.tgz#31b4db6678f2ae01c39ea9fb8725a9031e558a7b" 562 | dependencies: 563 | foreachasync "^3.0.0" 564 | 565 | window-size@0.1.0: 566 | version "0.1.0" 567 | resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" 568 | 569 | wordwrap@0.0.2: 570 | version "0.0.2" 571 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" 572 | 573 | wordwrap@~0.0.2: 574 | version "0.0.3" 575 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" 576 | 577 | yargs@~3.10.0: 578 | version "3.10.0" 579 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" 580 | dependencies: 581 | camelcase "^1.0.2" 582 | cliui "^2.1.0" 583 | decamelize "^1.0.0" 584 | window-size "0.1.0" 585 | --------------------------------------------------------------------------------