├── README.md ├── app.js ├── controllers ├── cookbook.controller.js ├── helpers │ ├── destroyObject.js │ ├── editObject.js │ ├── readAllObjects.js │ └── updateObject.js ├── landing.controller.js └── recipe.controller.js ├── handlers └── error.handler.js ├── models ├── cookbook.model.js └── recipe.model.js ├── package-lock.json ├── package.json ├── public ├── css │ └── app.css └── js │ ├── bundle.js │ └── modules │ ├── addToCookbook.js │ ├── bling.js │ ├── closeCookbookMenu.js │ ├── findDOMAncestor.js │ ├── findDOMCousin.js │ ├── getUserCookbooks.js │ ├── openCookbookMenu.js │ ├── postNewCookbook.js │ ├── searchResultsHTML.js │ └── typeAhead.js ├── routes └── index.routes.js ├── test └── authorize.test.js ├── utils ├── authorize.js └── dbConnect.js └── views ├── 404.pug ├── editCookbook.pug ├── editRecipe.pug ├── footer.pug ├── index.pug ├── layout.pug ├── login.pug ├── mixins ├── _cookbook.pug ├── _cookbookModal.pug ├── _recipe.pug └── _recipeForm.pug ├── viewCookbooks.pug └── viewRecipes.pug /README.md: -------------------------------------------------------------------------------- 1 | # code-cookbook 2 | 3 | URL: [https://codecookbook.now.sh](https://codecookbook.now.sh) 4 | 5 | Author:Taylor Khan 6 | 7 | Code: github.com/nelsonkhan/code-cookbook-public 8 | 9 | Date Started: June 30, 2017 10 | 11 | Date Completed: July 19, 2017 12 | 13 | **About** 14 | 15 | Code Cookbook is a lightweight platform for developers to create, organize, and search for code snippets, bash commands, and other obscure bits of information. 16 | 17 | **Why?** 18 | 19 | I created Code Cookbook (referred to as CC from here on out) because I write software for the web. I often find myself having to learn obscure commands for various CLI programs, bash, and web frameworks. 20 | 21 | When I couldn’t figure something out, I would find a quick answer in documentation, Stack Overflow, or forums. 22 | 23 | After a few months in a new framework, I would forget the commands. 24 | 25 | When I needed to use the commands again, I would go back to Google, search for a problem I had already solved, scour results until I found the right answer, and then get back to work. 26 | 27 | This seemed like a waste of time. I didn’t need to read through a whole Stack Overflow question, or browse the MDN again. I just needed the syntax - with no filler. 28 | 29 | I created CC to get rid of the filler, and to act as a search engine for people like me. 30 | 31 | I knew that it would be useful if I was the only one who ever used it, and it would be even better if other people did. 32 | 33 | Aside from removing filler, it also acts as a cloud store. If I save the commands to my local machine, or write them on a scrap of paper, then I won’t have access to the information I want when I’m out and about. 34 | 35 | I could save them in a Google Doc or something similar. But then I’m gathering all the info myself. It would be nice if others could collaborate with me, without managing permissions. 36 | 37 | Finally, I wanted to build a project to show that I know the basics of the Express.js framework, and to sharpen my skills as a Javascript developer. 38 | 39 | **Stack** 40 | 41 | Technology stack is a weird concept when working with Node. 42 | 43 | On the one hand your stack could be considered your framework, server, database, and host OS. 44 | 45 | On the other hand, there are a lot of libraries and services that get used which aren’t included in the common stack acronyms. 46 | 47 | So, I’m going to list most of the tech I directly used and explain why I chose them. I won’t go into the dependencies of each, as that would be incredibly tedious for me, and boring for you! 48 | 49 | **Express** 50 | 51 | I’ve written an API using Node. I’ve written some CLI utilities using Node. Using Nightmare.js and Node, I’ve written some hefty web scrapers targeting javascript rendered sites. I’ve used Angular and React. 52 | 53 | But never a backend framework. 54 | 55 | I noticed that Javascript was moving quite rapidly a few years back. The trend was similar to the Ruby on Rails hype that preceded it. 56 | 57 | I was a happy camper using RoR, and when the community around it started to die down - I was left disappointed. I didn’t want to make the same mistakes by hopping onto a JS framework early. 58 | 59 | I have been using javascript heavily for the last few years, and now that some of the dust started to clear, I felt comfortable picking up a framework. 60 | 61 | I also wanted to compare Express to RoR and see which I preferred. 62 | 63 | In essence, the choice was experimental, and for personal development. 64 | 65 | **Pug** 66 | 67 | Pug is sort of the default choice for Express. It’s definitely possible to use other view engines, but most devs prefer Pug. I’ve already had experience with Pug and overall, I think it is the best choice available for server-side rendering. It is far cleaner, and thus clearer - than standard HTML. 68 | 69 | Brevity and readability is a good thing for HTML. Pug brings both to the table. It makes maintenance tasks such as editing or styling a far simpler affair. 70 | 71 | **Auth0** 72 | 73 | This is my second application with Auth0. I may get some flak for trusting the security of my application to a third-party. If I was building a banking site, I might be inclined to agree. But I’m not, CC is a step up from a basic CRUD app. 74 | 75 | Is my hand-rolled authentication going to be significantly more secure than Auth0? 76 | 77 | Probably not. 78 | 79 | Auth0 charges for their higher end services, and are being adopted quickly by many devs. As far as I know, there haven’t been major security issues. 80 | 81 | My other apps have custom authentication, so it’s not like it would be a learning exercise for me. 82 | 83 | At this point, why reinvent the wheel? 84 | 85 | **Mongoose / MongoDB** 86 | 87 | I used this setup on one of my previous applications. The concept of using JSON as a data store always felt like a good idea for me. 88 | 89 | When I build an API, I usually deliver JSON. When I call an API I’m usually getting JSON. 90 | 91 | Why not store the data as JSON, and query in JSON? 92 | 93 | For Javascript development, MongoDB feels like a natural fit. Storing data in arrays and in objects inside of a record is a killer feature and eliminates a lot of tedious joins tables and querying. 94 | 95 | Mongoose has some shortcomings (no fuzzy search, I’m looking at you!) but so far I’ve found that they can all be overcome with some custom code. 96 | 97 | **Bash** 98 | 99 | This might be odd to include, but I actually used bash in place of a task runner on this project. 100 | 101 | My unminified CSS is a total of 44 lines, and I figured it would be. Using a task runner just to use SASS would be total overkill. 102 | 103 | The only task I really needed automated was bundling my frontend JS. Certainly I could use a task runner, but that sounds like needless configuration. 104 | 105 | I set up a bundle command in my package.json which concatenated my files nicely using a bash one liner. Then I set up my monitor script to rebundle everything and restart the app whenever new code was saved. 106 | 107 | Gary Bernhardt, of Destroy All Software fame says "Half-assed is OK when you only need half of an ass". [[https://www.youtube.com/watch?v=sCZJblyT_XM&list=PLxd96E9IxfZXubJCt_iX1hEdxqyQlKSjG](https://www.youtube.com/watch?v=sCZJblyT_XM&list=PLxd96E9IxfZXubJCt_iX1hEdxqyQlKSjG)] 108 | 109 | **Now** 110 | 111 | I’ve used Surge, Heroku, Digital Ocean, and more. None of them match the ease of use for quick deploying with Zeit’s Now CLI. 112 | 113 | **Misc** 114 | 115 | There’s a few others, and I’ve probably missed or forgotten more. But here is a quick overview of some other tools inside the code: 116 | 117 | * Passport (authorization) 118 | 119 | * Dotenv (environment variables) 120 | 121 | * Bling (lightweight jQuery alternative) 122 | 123 | * Chai / Mocha (unit testing) 124 | 125 | **Process** 126 | 127 | I started off with the idea, an ethereal concept for what the code might do. 128 | 129 | I let it simmer for a while, to make sure I was solving an actual problem and not just a temporary frustration. 130 | 131 | When I finally decided to make CC, I had just finished reading *The Pragmatic Programmer*, and gone through an ordeal building a web app where my team had failed to gather requirements in detail up front. So, I was very gung ho about putting the idea of a use case document into practice. 132 | 133 | Even though it is a simple app, I wanted to be very thorough, I figured this was good practice for future projects. 134 | 135 | After use cases, I started creating some interface mockups. I could have gone straight into design / architecture, but I find that a visual layout helps to really crystallize what is being built. The clearer the concept is before coding, the better. 136 | 137 | Finally, I put together some design elements. I created a basic database schema layout. I used pseudocode as a placeholder for the routing, the controllers, and their methods. 138 | 139 | Finally time to code. Because I did a lot of the work up front, most of the code was a breeze. I had a few hiccups, in trying to use TDD, and in AJAX requests. 140 | 141 | I still haven’t quite figured out TDD with Express. There isn't enough custom logic in this app to really require it. Most of it is just database queries, and since those are all handled by Mongoose, which is fairly battle-tested...what is there to test? 142 | 143 | AJAX requests were much simpler. AJAX requests are made using fetch, but I wasn’t sure how to authorize the user, as everything is running server side. This is not typically how Auth0 is used from what I can gather. The Express and Fetch docs were not very helpful here so I turned to reddit. Setting "credentials": true in the fetch request happily passed along the Auth0 JWT and authorized the user. 144 | 145 | **Final Thoughts** 146 | 147 | I think I could have had a massive benefit using something like Protractor for front end testing. I spent as much time doing manual testing as I did writing code. Automating more could really help deploy speed in the future. 148 | 149 | I discovered a handful of bash tricks in this project that I’m certain will be useful in the future. 150 | 151 | I’ve posted these to CC as the following: 152 | 153 | #### **Combine / bundle files using terminal with newline** 154 | 155 | awk 'FNR==0{print ''}1' *.[file-extension] > [output-file] 156 | 157 | #### **Find all files with specific text bash** 158 | 159 | grep -rnw '/path/to/somewhere/' -e 'pattern' 160 | 161 | #### **Find and replace using bash** 162 | 163 | sed -i 's/[original]/[new]/g' [some-file] 164 | 165 | These were all pretty useful. 166 | 167 | The bundler can let you modularize code without needing a separate task runner, which is good for light projects. 168 | 169 | Find all files with specific text is recursive so it will search a whole directory. Very nice for when you want to change the name of a function for clarity. It could also be useful if someone hardcoded in a bit of sensitive information directly instead of using environment variables. 170 | 171 | Find and replace only affect one document, but if that one document has a lot of instances of the original text - this can be a lot quicker than opening up an editor. 172 | 173 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'), 2 | bodyParser = require('body-parser'), 3 | cookieParser = require('cookie-parser'), 4 | expressValidator = require('express-validator'), 5 | app = express(), 6 | mongoose = require('mongoose'), 7 | dbConnect = require('./utils/dbConnect'), 8 | passport = require('passport'), 9 | Auth0Strategy = require('passport-auth0'), 10 | session = require('express-session'), 11 | flash = require('connect-flash'), 12 | MongoStore = require('connect-mongo')(session) 13 | 14 | // import enviornment variables 15 | require('dotenv').config({ path: 'variables.env' }) 16 | 17 | // connect to db 18 | dbConnect(process.env.DATABASE) 19 | 20 | // import all models 21 | require('./models/cookbook.model') 22 | require('./models/recipe.model') 23 | 24 | // uses pug to render templates 25 | app.set('view engine', 'pug') 26 | 27 | // use the public folder to host static assets 28 | app.use(express.static('public')) 29 | 30 | // Takes the raw requests and turns them into usable properties on req.body 31 | app.use(bodyParser.json()) 32 | app.use(bodyParser.urlencoded({ extended: true })) 33 | 34 | app.use(cookieParser()) 35 | app.use(session({ 36 | secret: 'shhhh', 37 | key: 'ohyeah', 38 | resave: true, 39 | saveUnitialized: true, 40 | store: new MongoStore({ mongooseConnection: mongoose.connection }) 41 | })) 42 | 43 | // Exposes a bunch of methods for validating data. 44 | app.use(expressValidator()) 45 | 46 | // // The flash middleware let's us use req.flash('error', 'Shit!'), which will then pass that message to the next page the user requests 47 | app.use(flash()) 48 | 49 | // Use passport for authorization management 50 | app.use(passport.initialize()) 51 | app.use(passport.session()) 52 | 53 | // pass variables to our templates + all requests 54 | app.use((req, res, next) => { 55 | res.locals.flashes = req.flash() 56 | res.locals.user = req.user 57 | next() 58 | }) 59 | 60 | const routes = require('./routes/index.routes') 61 | app.use('/', routes) 62 | 63 | // Configure Passport to use Auth0 64 | const strategy = new Auth0Strategy({ 65 | domain: process.env.AUTH0_DOMAIN, 66 | clientID: process.env.AUTH0_CLIENT, 67 | clientSecret: process.env.AUTH0_SECRET, 68 | callbackURL: process.env.AUTH0_CALLBACK 69 | }, (accessToken, refreshToken, extraParams, profile, done) => { 70 | return done(null, profile) 71 | }) 72 | 73 | passport.use(strategy) 74 | 75 | // This can be used to keep a smaller payload 76 | passport.serializeUser(function(user, done) { 77 | done(null, user); 78 | }) 79 | 80 | passport.deserializeUser(function(user, done) { 81 | done(null, user) 82 | }) 83 | 84 | // 404 handler 85 | app.use(function(req, res, next){ 86 | res.status(404); 87 | 88 | res.format({ 89 | html: function () { 90 | res.render('404', { url: req.url }) 91 | }, 92 | json: function () { 93 | res.json({ error: 'Not found' }) 94 | }, 95 | default: function () { 96 | res.type('txt').send('Not found') 97 | } 98 | }) 99 | }) 100 | 101 | // Start the app! 102 | app.listen(3000, function () { 103 | console.log('Example app listening on port 3000!') 104 | }) 105 | 106 | 107 | module.exports = app -------------------------------------------------------------------------------- /controllers/cookbook.controller.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Cookbook = mongoose.model('Cookbook') 3 | const authorize = require('../utils/authorize.js') 4 | const destroyObject = require('./helpers/destroyObject') 5 | const editObject = require('./helpers/editObject') 6 | const updateObject = require('./helpers/updateObject') 7 | const readAllObjects = require('./helpers/readAllObjects') 8 | 9 | 10 | exports.addRecipe = async (req, res) => { 11 | //res.header("Access-Control-Allow-Origin", "*") 12 | //res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") 13 | 14 | let cookbook_data = await Cookbook.findOne({ "_id": req.params.cookbook_id }) 15 | if (!authorize(req.user.id, cookbook_data.author)) { 16 | res.json({"message": "There was an issue adding that to the cookbook", "type": "warning"}) 17 | return 18 | } 19 | 20 | let cookbook = await Cookbook.findOneAndUpdate( 21 | { '_id': req.params.cookbook_id }, 22 | { $push: { recipes: req.body.recipe } 23 | }).exec() 24 | res.json({"message": "Recipe added to cookbook", "type": "success"}) 25 | } 26 | 27 | exports.create = async (req, res) => { 28 | res.header("Access-Control-Allow-Origin", "*") 29 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") 30 | 31 | if(!authorize(req.user.id, req.body.author)) { 32 | res.json({"message": "There was an issue creating cookbook", "type": "warning"}) 33 | return 34 | } 35 | 36 | let cookbook = new Cookbook(req.body) 37 | await cookbook.save() 38 | res.json({"message": "New cookbook created and recipe added!", "type": "success"}) 39 | } 40 | 41 | exports.destroy = destroyObject(Cookbook) 42 | exports.edit = editObject(Cookbook, 'Cookbook') 43 | 44 | exports.getAll = async (req, res) => { 45 | res.header("Access-Control-Allow-Origin", "*") 46 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") 47 | let cookbooks = await Cookbook.find({ 'author': req.params.user_id}) 48 | res.json(cookbooks) 49 | } 50 | 51 | exports.readAll = readAllObjects(Cookbook, 'Cookbook') 52 | 53 | exports.read = async (req, res) => { 54 | let cookbook = await Cookbook.findOne({ 'slug': req.params.slug}) 55 | .populate({ 56 | path: 'recipes', 57 | model: 'Recipe', 58 | }) 59 | let authorized = authorize(req.user.id, cookbook.author) 60 | res.render('viewRecipes', { 61 | authorized, 62 | cookbook, 63 | data: cookbook.recipes, 64 | title: cookbook.title 65 | }) 66 | } 67 | 68 | exports.removeRecipe = async (req, res) => { 69 | let cookbook = await Cookbook.findOne({ _id: req.params.cookbook_id }) 70 | 71 | // user must be authorized to remove something from a cookbook 72 | if (!authorize(req.user.id, cookbook.author)) { 73 | req.flash('warning', 'You must be the author to remove this!') 74 | // return to previous route or go home 75 | res.redirect(req.header('Referrer') || '/') 76 | return 77 | } 78 | 79 | // remove recipe from cookbook 80 | let recipe_index = cookbook.recipes.indexOf(req.params.recipe_id) 81 | cookbook.recipes.splice(recipe_index, 1) 82 | 83 | await Cookbook.findOneAndUpdate({ _id: req.params.cookbook_id }, cookbook) 84 | req.flash('success', 'Recipe removed!') 85 | res.redirect(req.header('Referrer') || '/') 86 | } 87 | 88 | exports.update = updateObject(Cookbook) -------------------------------------------------------------------------------- /controllers/helpers/destroyObject.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Cookbook = mongoose.model('Cookbook') 3 | const Recipe = mongoose.model('Recipe') 4 | const authorize = require('../../utils/authorize.js') 5 | 6 | 7 | destroyObject = (objectType) => 8 | async (req, res) => { 9 | let redirectRoute = req.header('Referrer') 10 | let object = await objectType.findOne({ _id: req.params.id }) 11 | 12 | // user must be authorized before deletion 13 | if (!authorize(req.user.id, object.author)) { 14 | req.flash('warning', 'You must be the author to destroy this!') 15 | // return to previous rotue or go home 16 | res.redirect(redirectRoute || '/') 17 | return 18 | } 19 | 20 | await objectType.findOneAndRemove({ _id: req.params.id }) 21 | req.flash('success', `${object.title} succesfully removed!`) 22 | // return to previous rotue or go home 23 | res.redirect( redirectRoute || '/') 24 | } 25 | 26 | module.exports = destroyObject -------------------------------------------------------------------------------- /controllers/helpers/editObject.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Cookbook = mongoose.model('Cookbook') 3 | const Recipe = mongoose.model('Recipe') 4 | const authorize = require('../../utils/authorize.js') 5 | 6 | let editObject = (objectType, modelName) => 7 | async (req, res) => { 8 | let object = await objectType.findOne({ 'slug': req.params.slug }) 9 | 10 | if (!authorize(req.user.id, object.author)) { 11 | req.flash('warning', 'You must be the author to edit this!') 12 | res.redirect('/') 13 | return 14 | } 15 | 16 | res.render(`edit${modelName}`, { 17 | title: `Edit ${modelName}`, 18 | data: object 19 | }) 20 | } 21 | 22 | module.exports = editObject 23 | -------------------------------------------------------------------------------- /controllers/helpers/readAllObjects.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Cookbook = mongoose.model('Cookbook') 3 | const Recipe = mongoose.model('Recipe') 4 | const authorize = require('../../utils/authorize.js') 5 | 6 | readAllObjects = (objectType, objectName) => 7 | async (req, res) => { 8 | let objects = await objectType.find({ 'author': req.user.id}) 9 | res.render(`view${objectName}s`, { 10 | data: objects, 11 | authorized: true, 12 | title: `Your ${objectName}s` 13 | }) 14 | } 15 | 16 | module.exports = readAllObjects -------------------------------------------------------------------------------- /controllers/helpers/updateObject.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Cookbook = mongoose.model('Cookbook') 3 | const Recipe = mongoose.model('Recipe') 4 | const authorize = require('../../utils/authorize.js') 5 | 6 | let updateObject = (objectType) => 7 | async (req, res) => { 8 | let object_data = await objectType.findOne({ 'slug': req.params.slug }) 9 | 10 | if (!authorize(req.user.id, object_data.author)) { 11 | req.flash('warning', 'You must be the author to update this!') 12 | // return to previous rotue or go home 13 | res.redirect(redirectRoute || '/') 14 | return 15 | } 16 | 17 | let object = await objectType.findOneAndUpdate( 18 | { 'slug': req.params.slug }, 19 | req.body, 20 | { new: true, runValidators: true } 21 | ).exec() 22 | res.redirect(req.header('Referrer') || '/') 23 | } 24 | 25 | module.exports = updateObject 26 | -------------------------------------------------------------------------------- /controllers/landing.controller.js: -------------------------------------------------------------------------------- 1 | exports.home = (req, res) => { 2 | let current_user = req.user 3 | res.render('index', { user: current_user }) 4 | } 5 | 6 | exports.login = (req, res) => { 7 | res.render('login', {env: process.env}) 8 | } 9 | 10 | exports.logout = async (req, res) => { 11 | await req.logout() 12 | req.flash('success', 'Logged out!') 13 | res.redirect('/') 14 | } -------------------------------------------------------------------------------- /controllers/recipe.controller.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Recipe = mongoose.model('Recipe') 3 | const authorize = require('../utils/authorize.js') 4 | const destroyObject = require('./helpers/destroyObject') 5 | const editObject = require('./helpers/editObject') 6 | const updateObject = require('./helpers/updateObject') 7 | const readAllObjects = require('./helpers/readAllObjects') 8 | 9 | 10 | exports.create = async (req, res) => { 11 | req.body.author = req.user.id 12 | let recipe = new Recipe(req.body) 13 | await recipe.save() 14 | res.redirect('/') 15 | } 16 | 17 | exports.destroy = destroyObject(Recipe) 18 | exports.edit = editObject(Recipe, 'Recipe') 19 | 20 | exports.new = (req, res) => { 21 | let recipe = new Recipe() 22 | res.render('editRecipe', { 23 | title: 'New Recipe' 24 | }) 25 | } 26 | 27 | exports.read = async (req, res) => { 28 | let recipe = await Recipe.findOne({ 'slug': req.params.slug }) 29 | let data = [recipe] 30 | let authorized = req.user && authorize(req.user.id, recipe.author) 31 | res.render('viewRecipes', { 32 | data, 33 | authorized 34 | }) 35 | } 36 | 37 | exports.readAll = readAllObjects(Recipe, 'Recipe') 38 | 39 | exports.search = async (req, res) => { 40 | let results = [] 41 | if (req.query.q) { 42 | results = await Recipe.find( 43 | { $text: { $search: req.query.q } }, 44 | { score: { $meta: 'textScore'} } 45 | ) 46 | .sort({ score: { $meta: 'textScore'} }) 47 | .limit(5) 48 | } 49 | 50 | // search is accessible via json, so we hide the irrelevant info 51 | sanitized_results = results.map(result => { 52 | return { title: result.title, slug: result.slug, content: result.content } 53 | }) 54 | 55 | res.json(sanitized_results) 56 | } 57 | 58 | exports.update = updateObject(Recipe) -------------------------------------------------------------------------------- /handlers/error.handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | Catch Errors Handler 3 | With async/await, you need some way to catch errors 4 | Instead of using try{} catch(e) {} in each controller, we wrap the function in 5 | catchErrors(), catch any errors they throw, and pass it along to our express middleware with next() 6 | */ 7 | 8 | exports.catchErrors = (fn) => { 9 | return function(req, res, next) { 10 | return fn(req, res, next).catch(next); 11 | }; 12 | }; 13 | 14 | /* 15 | Not Found Error Handler 16 | If we hit a route that is not found, we mark it as 404 and pass it along to the next error handler to display 17 | */ 18 | exports.notFound = (req, res, next) => { 19 | const err = new Error('Not Found'); 20 | err.status = 404; 21 | next(err); 22 | }; 23 | 24 | /* 25 | MongoDB Validation Error Handler 26 | Detect if there are mongodb validation errors that we can nicely show via flash messages 27 | */ 28 | 29 | exports.flashValidationErrors = (err, req, res, next) => { 30 | if (!err.errors) return next(err); 31 | // validation errors look like 32 | const errorKeys = Object.keys(err.errors); 33 | errorKeys.forEach(key => req.flash('warning', err.errors[key].message)); 34 | res.redirect('back'); 35 | }; 36 | 37 | 38 | /* 39 | Development Error Handler 40 | In development we show good error messages so if we hit a syntax error or any other previously un-handled error, we can show good info on what happened 41 | */ 42 | exports.developmentErrors = (err, req, res, next) => { 43 | err.stack = err.stack || ''; 44 | const errorDetails = { 45 | message: err.message, 46 | status: err.status, 47 | stackHighlighted: err.stack.replace(/[a-z_-\d]+.js:\d+:\d+/gi, '$&') 48 | }; 49 | res.status(err.status || 500); 50 | res.format({ 51 | // Based on the `Accept` http header 52 | 'text/html': () => { 53 | res.render('error', errorDetails); 54 | }, // Form Submit, Reload the page 55 | 'application/json': () => res.json(errorDetails) // Ajax call, send JSON back 56 | }); 57 | }; 58 | 59 | 60 | /* 61 | Production Error Handler 62 | No stacktraces are leaked to user 63 | */ 64 | exports.productionErrors = (err, req, res, next) => { 65 | res.status(err.status || 500); 66 | res.render('error', { 67 | message: err.message, 68 | error: {} 69 | }); 70 | }; -------------------------------------------------------------------------------- /models/cookbook.model.js: -------------------------------------------------------------------------------- 1 | const slug = require('slugs') 2 | const mongoose = require('mongoose'), 3 | Schema = mongoose.Schema 4 | mongoose.Promise = global.Promise 5 | 6 | const cookbookSchema = new mongoose.Schema({ 7 | author: { 8 | required: true, 9 | type: String, 10 | }, 11 | recipes: [{ type : Schema.Types.ObjectId, ref: 'Recipie' }], 12 | slug: String, 13 | title: { 14 | required: 'Title is required', 15 | type: String, 16 | trim: true, 17 | }, 18 | }) 19 | 20 | cookbookSchema.pre('save', function (next) { 21 | // if name hasn't changed, don't do anything 22 | if (!this.isModified('title')) { return next() } 23 | // add current time in ms to title to generate unique slugs 24 | this.slug = slug(`${this.title}-${new Date().getTime()}`) 25 | next() 26 | }) 27 | 28 | module.exports = mongoose.model('Cookbook', cookbookSchema) 29 | -------------------------------------------------------------------------------- /models/recipe.model.js: -------------------------------------------------------------------------------- 1 | const slug = require('slugs') 2 | const mongoose = require('mongoose') 3 | mongoose.Promise = global.Promise 4 | 5 | const recipeSchema = new mongoose.Schema({ 6 | title: { 7 | type: String, 8 | trim: true, 9 | required: 'Title is required' 10 | }, 11 | content: { 12 | type: String, 13 | trim: true, 14 | required: 'Content is required' 15 | }, 16 | slug: String, 17 | author: String 18 | }) 19 | 20 | // index data for faster searching 21 | recipeSchema.index({ 22 | title: 'text', 23 | content: 'text' 24 | }) 25 | 26 | recipeSchema.pre('save', function (next) { 27 | // if name hasn't changed, don't do anything 28 | if (!this.isModified('title')) { return next() } 29 | // add current time in ms to title to generate unique slugs 30 | this.slug = slug(`${this.title}-${new Date().getTime()}`) 31 | next() 32 | }) 33 | 34 | module.exports = mongoose.model('Recipe', recipeSchema) -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cookbook", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@types/bluebird": { 7 | "version": "3.0.37", 8 | "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.0.37.tgz", 9 | "integrity": "sha1-LnazlKqb6kDQQkGjHAiHomAoM4g=" 10 | }, 11 | "@types/express": { 12 | "version": "4.0.36", 13 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.36.tgz", 14 | "integrity": "sha512-bT9q2eqH/E72AGBQKT50dh6AXzheTqigGZ1GwDiwmx7vfHff0bZOrvUWjvGpNWPNkRmX1vDF6wonG6rlpBHb1A==" 15 | }, 16 | "@types/express-serve-static-core": { 17 | "version": "4.0.48", 18 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.48.tgz", 19 | "integrity": "sha512-+W+fHO/hUI6JX36H8FlgdMHU3Dk4a/Fn08fW5qdd7MjPP/wJlzq9fkCrgaH0gES8vohVeqwefHwPa4ylVKyYIg==" 20 | }, 21 | "@types/mime": { 22 | "version": "1.3.1", 23 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.1.tgz", 24 | "integrity": "sha512-rek8twk9C58gHYqIrUlJsx8NQMhlxqHzln9Z9ODqiNgv3/s+ZwIrfr+djqzsnVM12xe9hL98iJ20lj2RvCBv6A==" 25 | }, 26 | "@types/node": { 27 | "version": "8.0.7", 28 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.7.tgz", 29 | "integrity": "sha512-fuCPLPe4yY0nv6Z1rTLFCEC452jl0k7i3gF/c8hdEKpYtEpt6Sk67hTGbxx8C0wmifFGPvKYd/O8CvS6dpgxMQ==" 30 | }, 31 | "@types/serve-static": { 32 | "version": "1.7.31", 33 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz", 34 | "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho=" 35 | }, 36 | "accepts": { 37 | "version": "1.3.3", 38 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 39 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" 40 | }, 41 | "acorn": { 42 | "version": "3.3.0", 43 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", 44 | "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" 45 | }, 46 | "acorn-globals": { 47 | "version": "3.1.0", 48 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", 49 | "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", 50 | "dependencies": { 51 | "acorn": { 52 | "version": "4.0.13", 53 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", 54 | "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" 55 | } 56 | } 57 | }, 58 | "ajv": { 59 | "version": "4.11.8", 60 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", 61 | "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=" 62 | }, 63 | "align-text": { 64 | "version": "0.1.4", 65 | "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", 66 | "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=" 67 | }, 68 | "amdefine": { 69 | "version": "1.0.1", 70 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 71 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" 72 | }, 73 | "array-flatten": { 74 | "version": "1.1.1", 75 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 76 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 77 | }, 78 | "asap": { 79 | "version": "2.0.5", 80 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz", 81 | "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=" 82 | }, 83 | "asn1": { 84 | "version": "0.2.3", 85 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 86 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 87 | }, 88 | "assert-plus": { 89 | "version": "0.2.0", 90 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", 91 | "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" 92 | }, 93 | "assertion-error": { 94 | "version": "1.0.2", 95 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", 96 | "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", 97 | "dev": true 98 | }, 99 | "async": { 100 | "version": "2.1.4", 101 | "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz", 102 | "integrity": "sha1-LSFgx3iAMuTdbL4lAvH5osj2zeQ=" 103 | }, 104 | "asynckit": { 105 | "version": "0.4.0", 106 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 107 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 108 | }, 109 | "auth0": { 110 | "version": "2.7.0", 111 | "resolved": "https://registry.npmjs.org/auth0/-/auth0-2.7.0.tgz", 112 | "integrity": "sha1-GjvbQ3t7JZBkjA1JK8RUHeEbXdg=" 113 | }, 114 | "auth0-js": { 115 | "version": "8.7.0", 116 | "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-8.7.0.tgz", 117 | "integrity": "sha1-Z5vxjFdKVATkbwXazK2rpMb3dOs=" 118 | }, 119 | "auth0-lock": { 120 | "version": "10.18.0", 121 | "resolved": "https://registry.npmjs.org/auth0-lock/-/auth0-lock-10.18.0.tgz", 122 | "integrity": "sha1-c8IInof4v6g50Y7RlnW6YBg2vd8=" 123 | }, 124 | "aws-sign2": { 125 | "version": "0.6.0", 126 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", 127 | "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" 128 | }, 129 | "aws4": { 130 | "version": "1.6.0", 131 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 132 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 133 | }, 134 | "axios": { 135 | "version": "0.16.2", 136 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.16.2.tgz", 137 | "integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=" 138 | }, 139 | "balanced-match": { 140 | "version": "1.0.0", 141 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 142 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 143 | "dev": true 144 | }, 145 | "base64-js": { 146 | "version": "1.2.1", 147 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", 148 | "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" 149 | }, 150 | "bcrypt-pbkdf": { 151 | "version": "1.0.1", 152 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 153 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 154 | "optional": true 155 | }, 156 | "bluebird": { 157 | "version": "2.10.2", 158 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz", 159 | "integrity": "sha1-AkpVFylTCIV/FPkfEQb8O1VfRGs=" 160 | }, 161 | "blueimp-md5": { 162 | "version": "2.3.1", 163 | "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.3.1.tgz", 164 | "integrity": "sha1-mSpnN3M7naHt1kFVDcOsqy6c/Fo=" 165 | }, 166 | "body-parser": { 167 | "version": "1.17.2", 168 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 169 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=" 170 | }, 171 | "boom": { 172 | "version": "2.10.1", 173 | "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", 174 | "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" 175 | }, 176 | "brace-expansion": { 177 | "version": "1.1.8", 178 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 179 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 180 | "dev": true 181 | }, 182 | "browser-stdout": { 183 | "version": "1.3.0", 184 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", 185 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", 186 | "dev": true 187 | }, 188 | "bson": { 189 | "version": "1.0.4", 190 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz", 191 | "integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw=" 192 | }, 193 | "buffer-shims": { 194 | "version": "1.0.0", 195 | "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", 196 | "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" 197 | }, 198 | "bytes": { 199 | "version": "2.4.0", 200 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 201 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 202 | }, 203 | "camel-case": { 204 | "version": "1.2.2", 205 | "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-1.2.2.tgz", 206 | "integrity": "sha1-Gsp8TRlTWaLOmVV5NDPG5VQlEfI=" 207 | }, 208 | "camelcase": { 209 | "version": "1.2.1", 210 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", 211 | "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" 212 | }, 213 | "caseless": { 214 | "version": "0.12.0", 215 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 216 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 217 | }, 218 | "center-align": { 219 | "version": "0.1.3", 220 | "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", 221 | "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=" 222 | }, 223 | "chai": { 224 | "version": "4.0.2", 225 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.0.2.tgz", 226 | "integrity": "sha1-L3MnxN5vOF3XeHmZ4qsCaXoyuDs=", 227 | "dev": true 228 | }, 229 | "chain-function": { 230 | "version": "1.0.0", 231 | "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.0.tgz", 232 | "integrity": "sha1-DUqzfn4Y6tC9xHuSB2QRjOWHM9w=" 233 | }, 234 | "change-case": { 235 | "version": "2.3.1", 236 | "resolved": "https://registry.npmjs.org/change-case/-/change-case-2.3.1.tgz", 237 | "integrity": "sha1-LE/ePwY7tB0AzWjg1aCdthy+iU8=" 238 | }, 239 | "character-parser": { 240 | "version": "2.2.0", 241 | "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", 242 | "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=" 243 | }, 244 | "check-error": { 245 | "version": "1.0.2", 246 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 247 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 248 | "dev": true 249 | }, 250 | "clean-css": { 251 | "version": "3.4.27", 252 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz", 253 | "integrity": "sha1-re91sxwWD/pdcvTeZ5ZuJmDBolU=" 254 | }, 255 | "cliui": { 256 | "version": "2.1.0", 257 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", 258 | "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=" 259 | }, 260 | "co": { 261 | "version": "4.6.0", 262 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 263 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 264 | }, 265 | "combined-stream": { 266 | "version": "1.0.5", 267 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 268 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" 269 | }, 270 | "commander": { 271 | "version": "2.8.1", 272 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", 273 | "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=" 274 | }, 275 | "component-emitter": { 276 | "version": "1.2.1", 277 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 278 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 279 | }, 280 | "concat-map": { 281 | "version": "0.0.1", 282 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 283 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 284 | "dev": true 285 | }, 286 | "connect-ensure-login": { 287 | "version": "0.1.1", 288 | "resolved": "https://registry.npmjs.org/connect-ensure-login/-/connect-ensure-login-0.1.1.tgz", 289 | "integrity": "sha1-F03MUSQ7nqwj+NmCFa62aU4uihI=" 290 | }, 291 | "connect-flash": { 292 | "version": "0.1.1", 293 | "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", 294 | "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" 295 | }, 296 | "connect-mongo": { 297 | "version": "1.3.2", 298 | "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-1.3.2.tgz", 299 | "integrity": "sha1-fL9Y3/8mdg5eAOAX0KhbS8kLnTc=", 300 | "dependencies": { 301 | "bluebird": { 302 | "version": "3.5.0", 303 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", 304 | "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" 305 | } 306 | } 307 | }, 308 | "constant-case": { 309 | "version": "1.1.2", 310 | "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-1.1.2.tgz", 311 | "integrity": "sha1-jsLKW6ND4Aqjjb9OIA/VrJB+/WM=" 312 | }, 313 | "constantinople": { 314 | "version": "3.1.0", 315 | "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.0.tgz", 316 | "integrity": "sha1-dWnKqKo/jVk11i4fqW+fcCzYHHk=" 317 | }, 318 | "content-disposition": { 319 | "version": "0.5.2", 320 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 321 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 322 | }, 323 | "content-type": { 324 | "version": "1.0.2", 325 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 326 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 327 | }, 328 | "cookie": { 329 | "version": "0.3.1", 330 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 331 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 332 | }, 333 | "cookie-parser": { 334 | "version": "1.4.3", 335 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", 336 | "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=" 337 | }, 338 | "cookie-signature": { 339 | "version": "1.0.6", 340 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 341 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 342 | }, 343 | "cookiejar": { 344 | "version": "2.1.1", 345 | "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", 346 | "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" 347 | }, 348 | "core-js": { 349 | "version": "1.2.7", 350 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 351 | "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" 352 | }, 353 | "core-util-is": { 354 | "version": "1.0.2", 355 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 356 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 357 | }, 358 | "crc": { 359 | "version": "3.4.4", 360 | "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", 361 | "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" 362 | }, 363 | "create-react-class": { 364 | "version": "15.6.0", 365 | "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.0.tgz", 366 | "integrity": "sha1-q0SEl8JlZuHilBPogyB9V8/nvtQ=", 367 | "dependencies": { 368 | "fbjs": { 369 | "version": "0.8.12", 370 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz", 371 | "integrity": "sha1-ELXZL3bUVXX9Y6IX1OoCvqL47QQ=" 372 | } 373 | } 374 | }, 375 | "cryptiles": { 376 | "version": "2.0.5", 377 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", 378 | "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" 379 | }, 380 | "crypto-js": { 381 | "version": "3.1.9-1", 382 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", 383 | "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" 384 | }, 385 | "dashdash": { 386 | "version": "1.14.1", 387 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 388 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 389 | "dependencies": { 390 | "assert-plus": { 391 | "version": "1.0.0", 392 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 393 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 394 | } 395 | } 396 | }, 397 | "debug": { 398 | "version": "2.6.7", 399 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 400 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 401 | }, 402 | "decamelize": { 403 | "version": "1.2.0", 404 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 405 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 406 | }, 407 | "deep-eql": { 408 | "version": "2.0.2", 409 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-2.0.2.tgz", 410 | "integrity": "sha1-sbrAblbwp2d3aG1Qyf63XC7XZ5o=", 411 | "dev": true, 412 | "dependencies": { 413 | "type-detect": { 414 | "version": "3.0.0", 415 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-3.0.0.tgz", 416 | "integrity": "sha1-RtDMhVOrt7E6NSsNbeov1Y8tm1U=", 417 | "dev": true 418 | } 419 | } 420 | }, 421 | "deep-equal": { 422 | "version": "1.0.1", 423 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 424 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 425 | "dev": true 426 | }, 427 | "define-properties": { 428 | "version": "1.1.2", 429 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 430 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 431 | "dev": true 432 | }, 433 | "defined": { 434 | "version": "1.0.0", 435 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 436 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 437 | "dev": true 438 | }, 439 | "delayed-stream": { 440 | "version": "1.0.0", 441 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 442 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 443 | }, 444 | "depd": { 445 | "version": "1.1.0", 446 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 447 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 448 | }, 449 | "destroy": { 450 | "version": "1.0.4", 451 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 452 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 453 | }, 454 | "diff": { 455 | "version": "3.2.0", 456 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", 457 | "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", 458 | "dev": true 459 | }, 460 | "doctypes": { 461 | "version": "1.1.0", 462 | "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", 463 | "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" 464 | }, 465 | "dom-helpers": { 466 | "version": "3.2.1", 467 | "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.2.1.tgz", 468 | "integrity": "sha1-MgPgf+0he9H0JLAZc1WC/Deyglo=" 469 | }, 470 | "dot-case": { 471 | "version": "1.1.2", 472 | "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-1.1.2.tgz", 473 | "integrity": "sha1-HnOCaQDeKNbeVIC8HeMdCEKwa+w=" 474 | }, 475 | "dotenv": { 476 | "version": "4.0.0", 477 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", 478 | "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=", 479 | "dev": true 480 | }, 481 | "ecc-jsbn": { 482 | "version": "0.1.1", 483 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 484 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 485 | "optional": true 486 | }, 487 | "ee-first": { 488 | "version": "1.1.1", 489 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 490 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 491 | }, 492 | "encodeurl": { 493 | "version": "1.0.1", 494 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 495 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 496 | }, 497 | "encoding": { 498 | "version": "0.1.12", 499 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 500 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=" 501 | }, 502 | "es-abstract": { 503 | "version": "1.7.0", 504 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.7.0.tgz", 505 | "integrity": "sha1-363ndOAb/Nl/lhgCmMRJyGI/uUw=", 506 | "dev": true 507 | }, 508 | "es-to-primitive": { 509 | "version": "1.1.1", 510 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 511 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 512 | "dev": true 513 | }, 514 | "es6-promise": { 515 | "version": "3.2.1", 516 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", 517 | "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" 518 | }, 519 | "escape-html": { 520 | "version": "1.0.3", 521 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 522 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 523 | }, 524 | "escape-string-regexp": { 525 | "version": "1.0.5", 526 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 527 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 528 | "dev": true 529 | }, 530 | "etag": { 531 | "version": "1.8.0", 532 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 533 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 534 | }, 535 | "express": { 536 | "version": "4.15.3", 537 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 538 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" 539 | }, 540 | "express-session": { 541 | "version": "1.15.3", 542 | "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.3.tgz", 543 | "integrity": "sha1-21RfBDWnsbIorgLagZf2UUFzXGc=" 544 | }, 545 | "express-validator": { 546 | "version": "3.2.0", 547 | "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-3.2.0.tgz", 548 | "integrity": "sha1-lTer6w9m5Dn54wtO0WxMbCMTGOI=", 549 | "dependencies": { 550 | "bluebird": { 551 | "version": "3.5.0", 552 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", 553 | "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" 554 | } 555 | } 556 | }, 557 | "extend": { 558 | "version": "3.0.1", 559 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 560 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 561 | }, 562 | "extsprintf": { 563 | "version": "1.0.2", 564 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", 565 | "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" 566 | }, 567 | "fbjs": { 568 | "version": "0.3.2", 569 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.3.2.tgz", 570 | "integrity": "sha1-AzpUBZUIS13jUJpAXQbxoqjlufs=" 571 | }, 572 | "finalhandler": { 573 | "version": "1.0.3", 574 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 575 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" 576 | }, 577 | "follow-redirects": { 578 | "version": "1.2.4", 579 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.4.tgz", 580 | "integrity": "sha512-Suw6KewLV2hReSyEOeql+UUkBVyiBm3ok1VPrVFRZnQInWpdoZbbiG5i8aJVSjTr0yQ4Ava0Sh6/joCg1Brdqw==" 581 | }, 582 | "for-each": { 583 | "version": "0.3.2", 584 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", 585 | "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", 586 | "dev": true 587 | }, 588 | "foreach": { 589 | "version": "2.0.5", 590 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 591 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 592 | "dev": true 593 | }, 594 | "forever-agent": { 595 | "version": "0.6.1", 596 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 597 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 598 | }, 599 | "form-data": { 600 | "version": "2.2.0", 601 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.2.0.tgz", 602 | "integrity": "sha1-ml47kpX5gLJiPPZPojixTOvKcHs=" 603 | }, 604 | "formatio": { 605 | "version": "1.2.0", 606 | "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", 607 | "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", 608 | "dev": true 609 | }, 610 | "formidable": { 611 | "version": "1.1.1", 612 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", 613 | "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" 614 | }, 615 | "forwarded": { 616 | "version": "0.1.0", 617 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 618 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 619 | }, 620 | "fresh": { 621 | "version": "0.5.0", 622 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 623 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 624 | }, 625 | "fs.realpath": { 626 | "version": "1.0.0", 627 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 628 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 629 | "dev": true 630 | }, 631 | "function-bind": { 632 | "version": "1.1.0", 633 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", 634 | "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=" 635 | }, 636 | "get-func-name": { 637 | "version": "2.0.0", 638 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 639 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 640 | "dev": true 641 | }, 642 | "getpass": { 643 | "version": "0.1.7", 644 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 645 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 646 | "dependencies": { 647 | "assert-plus": { 648 | "version": "1.0.0", 649 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 650 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 651 | } 652 | } 653 | }, 654 | "glob": { 655 | "version": "7.1.2", 656 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 657 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 658 | "dev": true 659 | }, 660 | "graceful-readlink": { 661 | "version": "1.0.1", 662 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 663 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" 664 | }, 665 | "growl": { 666 | "version": "1.9.2", 667 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 668 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 669 | "dev": true 670 | }, 671 | "har-schema": { 672 | "version": "1.0.5", 673 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", 674 | "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" 675 | }, 676 | "har-validator": { 677 | "version": "4.2.1", 678 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", 679 | "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=" 680 | }, 681 | "has": { 682 | "version": "1.0.1", 683 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", 684 | "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=" 685 | }, 686 | "has-flag": { 687 | "version": "1.0.0", 688 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 689 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", 690 | "dev": true 691 | }, 692 | "hawk": { 693 | "version": "3.1.3", 694 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", 695 | "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" 696 | }, 697 | "hoek": { 698 | "version": "2.16.3", 699 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", 700 | "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" 701 | }, 702 | "hooks-fixed": { 703 | "version": "2.0.0", 704 | "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.0.tgz", 705 | "integrity": "sha1-oB2JTVKsf2WZu7H2PfycQR33DLo=" 706 | }, 707 | "http-errors": { 708 | "version": "1.6.1", 709 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 710 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" 711 | }, 712 | "http-signature": { 713 | "version": "1.1.1", 714 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", 715 | "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" 716 | }, 717 | "iconv-lite": { 718 | "version": "0.4.15", 719 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 720 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 721 | }, 722 | "idtoken-verifier": { 723 | "version": "1.1.0", 724 | "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.1.0.tgz", 725 | "integrity": "sha1-Gt0wElqj5eWFnRUrNWqQio4utaA=" 726 | }, 727 | "immutable": { 728 | "version": "3.8.1", 729 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.1.tgz", 730 | "integrity": "sha1-IAgH8Rqw9ycQ6khVQt4IgHX2jNI=" 731 | }, 732 | "inflight": { 733 | "version": "1.0.6", 734 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 735 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 736 | "dev": true 737 | }, 738 | "inherits": { 739 | "version": "2.0.3", 740 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 741 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 742 | }, 743 | "ipaddr.js": { 744 | "version": "1.3.0", 745 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 746 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 747 | }, 748 | "is-buffer": { 749 | "version": "1.1.5", 750 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", 751 | "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=" 752 | }, 753 | "is-callable": { 754 | "version": "1.1.3", 755 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", 756 | "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", 757 | "dev": true 758 | }, 759 | "is-date-object": { 760 | "version": "1.0.1", 761 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 762 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 763 | "dev": true 764 | }, 765 | "is-expression": { 766 | "version": "2.1.0", 767 | "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-2.1.0.tgz", 768 | "integrity": "sha1-kb6dR968/vB3l36XIr5tz7RGXvA=" 769 | }, 770 | "is-function": { 771 | "version": "1.0.1", 772 | "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", 773 | "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=", 774 | "dev": true 775 | }, 776 | "is-lower-case": { 777 | "version": "1.1.3", 778 | "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", 779 | "integrity": "sha1-fhR75HaNxGbbO/shzGCzHmrWk5M=" 780 | }, 781 | "is-promise": { 782 | "version": "2.1.0", 783 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 784 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" 785 | }, 786 | "is-regex": { 787 | "version": "1.0.4", 788 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 789 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=" 790 | }, 791 | "is-stream": { 792 | "version": "1.1.0", 793 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 794 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 795 | }, 796 | "is-symbol": { 797 | "version": "1.0.1", 798 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 799 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", 800 | "dev": true 801 | }, 802 | "is-typedarray": { 803 | "version": "1.0.0", 804 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 805 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 806 | }, 807 | "is-upper-case": { 808 | "version": "1.1.2", 809 | "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", 810 | "integrity": "sha1-jQsfp+eTOh5YSDYA7H2WYcuvdW8=" 811 | }, 812 | "isarray": { 813 | "version": "1.0.0", 814 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 815 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 816 | }, 817 | "isomorphic-fetch": { 818 | "version": "2.2.1", 819 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 820 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 821 | "dependencies": { 822 | "whatwg-fetch": { 823 | "version": "2.0.3", 824 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", 825 | "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" 826 | } 827 | } 828 | }, 829 | "isstream": { 830 | "version": "0.1.2", 831 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 832 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 833 | }, 834 | "js-stringify": { 835 | "version": "1.0.2", 836 | "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", 837 | "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" 838 | }, 839 | "js-tokens": { 840 | "version": "3.0.2", 841 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 842 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" 843 | }, 844 | "jsbn": { 845 | "version": "0.1.1", 846 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 847 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 848 | }, 849 | "json-schema": { 850 | "version": "0.2.3", 851 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 852 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 853 | }, 854 | "json-stable-stringify": { 855 | "version": "1.0.1", 856 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 857 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=" 858 | }, 859 | "json-stringify-safe": { 860 | "version": "5.0.1", 861 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 862 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 863 | }, 864 | "json3": { 865 | "version": "3.3.2", 866 | "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", 867 | "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", 868 | "dev": true 869 | }, 870 | "jsonify": { 871 | "version": "0.0.0", 872 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 873 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" 874 | }, 875 | "jsonp": { 876 | "version": "0.2.1", 877 | "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz", 878 | "integrity": "sha1-pltPoPEL2nGaBUQep7lMVfPhW64=" 879 | }, 880 | "jsprim": { 881 | "version": "1.4.0", 882 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", 883 | "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", 884 | "dependencies": { 885 | "assert-plus": { 886 | "version": "1.0.0", 887 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 888 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 889 | } 890 | } 891 | }, 892 | "jstransformer": { 893 | "version": "1.0.0", 894 | "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", 895 | "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=" 896 | }, 897 | "kareem": { 898 | "version": "1.4.1", 899 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.4.1.tgz", 900 | "integrity": "sha1-7XYgAET6BB7zK02oJh4lU/EXNTE=" 901 | }, 902 | "kind-of": { 903 | "version": "3.2.2", 904 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 905 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" 906 | }, 907 | "lazy-cache": { 908 | "version": "1.0.4", 909 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 910 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" 911 | }, 912 | "lodash": { 913 | "version": "4.17.4", 914 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 915 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 916 | }, 917 | "lodash._baseassign": { 918 | "version": "3.2.0", 919 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", 920 | "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", 921 | "dev": true 922 | }, 923 | "lodash._basecopy": { 924 | "version": "3.0.1", 925 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", 926 | "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", 927 | "dev": true 928 | }, 929 | "lodash._basecreate": { 930 | "version": "3.0.3", 931 | "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", 932 | "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", 933 | "dev": true 934 | }, 935 | "lodash._getnative": { 936 | "version": "3.9.1", 937 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 938 | "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", 939 | "dev": true 940 | }, 941 | "lodash._isiterateecall": { 942 | "version": "3.0.9", 943 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", 944 | "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", 945 | "dev": true 946 | }, 947 | "lodash.create": { 948 | "version": "3.1.1", 949 | "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", 950 | "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", 951 | "dev": true 952 | }, 953 | "lodash.isarguments": { 954 | "version": "3.1.0", 955 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 956 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 957 | "dev": true 958 | }, 959 | "lodash.isarray": { 960 | "version": "3.0.4", 961 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 962 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 963 | "dev": true 964 | }, 965 | "lodash.keys": { 966 | "version": "3.1.2", 967 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 968 | "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 969 | "dev": true 970 | }, 971 | "lolex": { 972 | "version": "1.6.0", 973 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", 974 | "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", 975 | "dev": true 976 | }, 977 | "longest": { 978 | "version": "1.0.1", 979 | "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", 980 | "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" 981 | }, 982 | "loose-envify": { 983 | "version": "1.3.1", 984 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", 985 | "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=" 986 | }, 987 | "lower-case": { 988 | "version": "1.1.4", 989 | "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", 990 | "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" 991 | }, 992 | "lower-case-first": { 993 | "version": "1.0.2", 994 | "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", 995 | "integrity": "sha1-5dp8JvKacHO+AtUrrJmA5ZIq36E=" 996 | }, 997 | "media-typer": { 998 | "version": "0.3.0", 999 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1000 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 1001 | }, 1002 | "merge-descriptors": { 1003 | "version": "1.0.1", 1004 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1005 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 1006 | }, 1007 | "methods": { 1008 | "version": "1.1.2", 1009 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1010 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 1011 | }, 1012 | "mime": { 1013 | "version": "1.3.4", 1014 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 1015 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 1016 | }, 1017 | "mime-db": { 1018 | "version": "1.27.0", 1019 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 1020 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 1021 | }, 1022 | "mime-types": { 1023 | "version": "2.1.15", 1024 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 1025 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" 1026 | }, 1027 | "minimatch": { 1028 | "version": "3.0.4", 1029 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1030 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1031 | "dev": true 1032 | }, 1033 | "minimist": { 1034 | "version": "1.2.0", 1035 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1036 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 1037 | "dev": true 1038 | }, 1039 | "mkdirp": { 1040 | "version": "0.5.1", 1041 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1042 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1043 | "dev": true, 1044 | "dependencies": { 1045 | "minimist": { 1046 | "version": "0.0.8", 1047 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1048 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 1049 | "dev": true 1050 | } 1051 | } 1052 | }, 1053 | "mocha": { 1054 | "version": "3.4.2", 1055 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.4.2.tgz", 1056 | "integrity": "sha1-0O9NMyEm2/GNDWQMmzgt1IvpdZQ=", 1057 | "dev": true, 1058 | "dependencies": { 1059 | "commander": { 1060 | "version": "2.9.0", 1061 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 1062 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 1063 | "dev": true 1064 | }, 1065 | "debug": { 1066 | "version": "2.6.0", 1067 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", 1068 | "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", 1069 | "dev": true 1070 | }, 1071 | "glob": { 1072 | "version": "7.1.1", 1073 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", 1074 | "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", 1075 | "dev": true 1076 | }, 1077 | "ms": { 1078 | "version": "0.7.2", 1079 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", 1080 | "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", 1081 | "dev": true 1082 | } 1083 | } 1084 | }, 1085 | "mongodb": { 1086 | "version": "2.2.27", 1087 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.27.tgz", 1088 | "integrity": "sha1-NBIgNNtm2YO89qta2yaiSnD+9uY=" 1089 | }, 1090 | "mongodb-core": { 1091 | "version": "2.1.11", 1092 | "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.11.tgz", 1093 | "integrity": "sha1-HDh3bOsXSZepnCiGDu2QKNqbPho=" 1094 | }, 1095 | "mongoose": { 1096 | "version": "4.10.6", 1097 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-4.10.6.tgz", 1098 | "integrity": "sha1-zFfa2Mi5FFy9LKVpalCBqVno/Ng=" 1099 | }, 1100 | "mpath": { 1101 | "version": "0.3.0", 1102 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.3.0.tgz", 1103 | "integrity": "sha1-elj3iem1/TyUUgY0FXlg8mvV70Q=" 1104 | }, 1105 | "mpromise": { 1106 | "version": "0.5.5", 1107 | "resolved": "https://registry.npmjs.org/mpromise/-/mpromise-0.5.5.tgz", 1108 | "integrity": "sha1-9bJCWddjrMIlewoMjG2Gb9UXMuY=" 1109 | }, 1110 | "mquery": { 1111 | "version": "2.3.1", 1112 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-2.3.1.tgz", 1113 | "integrity": "sha1-mrNnSXFIAP8LtTpoHOS8TV8HyHs=", 1114 | "dependencies": { 1115 | "debug": { 1116 | "version": "2.6.8", 1117 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 1118 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=" 1119 | }, 1120 | "sliced": { 1121 | "version": "0.0.5", 1122 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz", 1123 | "integrity": "sha1-XtwETKTrb3gW1Qui/GPiXY/kcH8=" 1124 | } 1125 | } 1126 | }, 1127 | "ms": { 1128 | "version": "2.0.0", 1129 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1130 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1131 | }, 1132 | "muri": { 1133 | "version": "1.2.1", 1134 | "resolved": "https://registry.npmjs.org/muri/-/muri-1.2.1.tgz", 1135 | "integrity": "sha1-7H6lzmympSPrGrNbrNpfqBbJqjw=" 1136 | }, 1137 | "native-promise-only": { 1138 | "version": "0.8.1", 1139 | "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", 1140 | "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", 1141 | "dev": true 1142 | }, 1143 | "negotiator": { 1144 | "version": "0.6.1", 1145 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 1146 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 1147 | }, 1148 | "net": { 1149 | "version": "1.0.2", 1150 | "resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz", 1151 | "integrity": "sha1-0XV+yaf7I3HYPPR1XOPifhCCk4g=", 1152 | "dev": true 1153 | }, 1154 | "node-fetch": { 1155 | "version": "1.7.1", 1156 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz", 1157 | "integrity": "sha512-j8XsFGCLw79vWXkZtMSmmLaOk9z5SQ9bV/tkbZVCqvgwzrjAGq66igobLofHtF63NvMTp2WjytpsNTGKa+XRIQ==" 1158 | }, 1159 | "node-mocks-http": { 1160 | "version": "1.6.3", 1161 | "resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.6.3.tgz", 1162 | "integrity": "sha1-v9mZIAHfj6yii6tD52Uu2x+esik=", 1163 | "dev": true, 1164 | "dependencies": { 1165 | "fresh": { 1166 | "version": "0.3.0", 1167 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", 1168 | "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=", 1169 | "dev": true 1170 | } 1171 | } 1172 | }, 1173 | "oauth": { 1174 | "version": "0.9.15", 1175 | "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", 1176 | "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" 1177 | }, 1178 | "oauth-sign": { 1179 | "version": "0.8.2", 1180 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 1181 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 1182 | }, 1183 | "object-assign": { 1184 | "version": "4.1.1", 1185 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1186 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1187 | }, 1188 | "object-inspect": { 1189 | "version": "1.2.2", 1190 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.2.2.tgz", 1191 | "integrity": "sha1-yCEV5PzIiK6hTWTCLk8X9qcNXlo=", 1192 | "dev": true 1193 | }, 1194 | "object-keys": { 1195 | "version": "1.0.11", 1196 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", 1197 | "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", 1198 | "dev": true 1199 | }, 1200 | "on-finished": { 1201 | "version": "2.3.0", 1202 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1203 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 1204 | }, 1205 | "on-headers": { 1206 | "version": "1.0.1", 1207 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", 1208 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" 1209 | }, 1210 | "once": { 1211 | "version": "1.4.0", 1212 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1213 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1214 | "dev": true 1215 | }, 1216 | "param-case": { 1217 | "version": "1.1.2", 1218 | "resolved": "https://registry.npmjs.org/param-case/-/param-case-1.1.2.tgz", 1219 | "integrity": "sha1-3LCRpDwlm5Io8cNB57akTqC/l0M=" 1220 | }, 1221 | "parseurl": { 1222 | "version": "1.3.1", 1223 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 1224 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 1225 | }, 1226 | "pascal-case": { 1227 | "version": "1.1.2", 1228 | "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-1.1.2.tgz", 1229 | "integrity": "sha1-Pl1kogBDgwp8STRMLXS0G+DJyZs=" 1230 | }, 1231 | "passport": { 1232 | "version": "0.3.2", 1233 | "resolved": "https://registry.npmjs.org/passport/-/passport-0.3.2.tgz", 1234 | "integrity": "sha1-ndAJ+RXo/glbASSgG4+C2gdRAQI=" 1235 | }, 1236 | "passport-auth0": { 1237 | "version": "0.6.0", 1238 | "resolved": "https://registry.npmjs.org/passport-auth0/-/passport-auth0-0.6.0.tgz", 1239 | "integrity": "sha1-SlxtBt9CfgqltIc32/+eH9FJNcI=" 1240 | }, 1241 | "passport-oauth": { 1242 | "version": "1.0.0", 1243 | "resolved": "https://registry.npmjs.org/passport-oauth/-/passport-oauth-1.0.0.tgz", 1244 | "integrity": "sha1-kK/2M4dUDwIImvKM2tOep/gNd98=" 1245 | }, 1246 | "passport-oauth1": { 1247 | "version": "1.1.0", 1248 | "resolved": "https://registry.npmjs.org/passport-oauth1/-/passport-oauth1-1.1.0.tgz", 1249 | "integrity": "sha1-p96YiiEfnPRoc3cTDqdN8ycwyRg=" 1250 | }, 1251 | "passport-oauth2": { 1252 | "version": "1.4.0", 1253 | "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.4.0.tgz", 1254 | "integrity": "sha1-9i+BWDy+EmCb585vFguTlaJ7hq0=" 1255 | }, 1256 | "passport-strategy": { 1257 | "version": "1.0.0", 1258 | "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", 1259 | "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" 1260 | }, 1261 | "password-sheriff": { 1262 | "version": "1.1.0", 1263 | "resolved": "https://registry.npmjs.org/password-sheriff/-/password-sheriff-1.1.0.tgz", 1264 | "integrity": "sha1-/bPD2EWgo8kt5CKyrZNGzginFBM=" 1265 | }, 1266 | "path-case": { 1267 | "version": "1.1.2", 1268 | "resolved": "https://registry.npmjs.org/path-case/-/path-case-1.1.2.tgz", 1269 | "integrity": "sha1-UM5roNO+090LXCqcRVNpdDRAlRQ=" 1270 | }, 1271 | "path-is-absolute": { 1272 | "version": "1.0.1", 1273 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1274 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1275 | "dev": true 1276 | }, 1277 | "path-to-regexp": { 1278 | "version": "0.1.7", 1279 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1280 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1281 | }, 1282 | "pathval": { 1283 | "version": "1.1.0", 1284 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 1285 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 1286 | "dev": true 1287 | }, 1288 | "pause": { 1289 | "version": "0.0.1", 1290 | "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", 1291 | "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" 1292 | }, 1293 | "performance-now": { 1294 | "version": "0.2.0", 1295 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", 1296 | "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" 1297 | }, 1298 | "process-nextick-args": { 1299 | "version": "1.0.7", 1300 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 1301 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" 1302 | }, 1303 | "promise": { 1304 | "version": "7.1.1", 1305 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz", 1306 | "integrity": "sha1-SJZUxpJha4qlWwck+oCbt9tJxb8=" 1307 | }, 1308 | "prop-types": { 1309 | "version": "15.5.10", 1310 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", 1311 | "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", 1312 | "dependencies": { 1313 | "fbjs": { 1314 | "version": "0.8.12", 1315 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz", 1316 | "integrity": "sha1-ELXZL3bUVXX9Y6IX1OoCvqL47QQ=" 1317 | } 1318 | } 1319 | }, 1320 | "proxy-addr": { 1321 | "version": "1.1.4", 1322 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 1323 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" 1324 | }, 1325 | "pug": { 1326 | "version": "2.0.0-rc.2", 1327 | "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.0-rc.2.tgz", 1328 | "integrity": "sha1-B4RVJ3kKssa+Z9z16x8xgECB8Eo=" 1329 | }, 1330 | "pug-attrs": { 1331 | "version": "2.0.2", 1332 | "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.2.tgz", 1333 | "integrity": "sha1-i+KyIlVo/6ddG4Zpgr/59BEa/8s=" 1334 | }, 1335 | "pug-code-gen": { 1336 | "version": "1.1.1", 1337 | "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-1.1.1.tgz", 1338 | "integrity": "sha1-HPcnRO8qA56uajNAyqoRBYcSWOg=" 1339 | }, 1340 | "pug-error": { 1341 | "version": "1.3.2", 1342 | "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz", 1343 | "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY=" 1344 | }, 1345 | "pug-filters": { 1346 | "version": "2.1.3", 1347 | "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-2.1.3.tgz", 1348 | "integrity": "sha1-1ZdnoiDeeX3XVUifZoNM+aqDqlQ=" 1349 | }, 1350 | "pug-lexer": { 1351 | "version": "3.1.0", 1352 | "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-3.1.0.tgz", 1353 | "integrity": "sha1-/QhzdtSmdbT1n4/vQiiDQ06VgaI=", 1354 | "dependencies": { 1355 | "acorn": { 1356 | "version": "4.0.13", 1357 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", 1358 | "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" 1359 | }, 1360 | "is-expression": { 1361 | "version": "3.0.0", 1362 | "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", 1363 | "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=" 1364 | } 1365 | } 1366 | }, 1367 | "pug-linker": { 1368 | "version": "3.0.1", 1369 | "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.1.tgz", 1370 | "integrity": "sha1-uj+P8hPKjzowSFm0T+0Tynud+hk=" 1371 | }, 1372 | "pug-load": { 1373 | "version": "2.0.7", 1374 | "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.7.tgz", 1375 | "integrity": "sha1-Ux0MbhFUYBDphGMNA99AY2fS3nc=" 1376 | }, 1377 | "pug-parser": { 1378 | "version": "3.0.0", 1379 | "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-3.0.0.tgz", 1380 | "integrity": "sha1-N8YZ3YAPZCGHzk1s4aFkzddUh6M=" 1381 | }, 1382 | "pug-runtime": { 1383 | "version": "2.0.3", 1384 | "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.3.tgz", 1385 | "integrity": "sha1-mBYmB7D86eJU1CfzOYelrucWi9o=" 1386 | }, 1387 | "pug-strip-comments": { 1388 | "version": "1.0.2", 1389 | "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.2.tgz", 1390 | "integrity": "sha1-0xOvoBvMN0mA4TmeI+vy65vchRM=" 1391 | }, 1392 | "pug-walk": { 1393 | "version": "1.1.3", 1394 | "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.3.tgz", 1395 | "integrity": "sha1-181bI9s8qHxjbIaglz+c2OAwQ2w=" 1396 | }, 1397 | "punycode": { 1398 | "version": "1.4.1", 1399 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 1400 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 1401 | }, 1402 | "qs": { 1403 | "version": "6.4.0", 1404 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 1405 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 1406 | }, 1407 | "random-bytes": { 1408 | "version": "1.0.0", 1409 | "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", 1410 | "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" 1411 | }, 1412 | "range-parser": { 1413 | "version": "1.2.0", 1414 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 1415 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 1416 | }, 1417 | "raw-body": { 1418 | "version": "2.2.0", 1419 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 1420 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" 1421 | }, 1422 | "react": { 1423 | "version": "15.6.1", 1424 | "resolved": "https://registry.npmjs.org/react/-/react-15.6.1.tgz", 1425 | "integrity": "sha1-uqhDTsZ4C96ZfNw4C3nNM7ljk98=", 1426 | "dependencies": { 1427 | "fbjs": { 1428 | "version": "0.8.12", 1429 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz", 1430 | "integrity": "sha1-ELXZL3bUVXX9Y6IX1OoCvqL47QQ=" 1431 | } 1432 | } 1433 | }, 1434 | "react-dom": { 1435 | "version": "15.6.1", 1436 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.1.tgz", 1437 | "integrity": "sha1-LLDtQZEDjlPCCes6eaI+Kkz5lHA=", 1438 | "dependencies": { 1439 | "fbjs": { 1440 | "version": "0.8.12", 1441 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz", 1442 | "integrity": "sha1-ELXZL3bUVXX9Y6IX1OoCvqL47QQ=" 1443 | } 1444 | } 1445 | }, 1446 | "react-transition-group": { 1447 | "version": "1.2.0", 1448 | "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.0.tgz", 1449 | "integrity": "sha1-tR/JIbDDg1p+98Vxx5/ILHPpIE8=" 1450 | }, 1451 | "readable-stream": { 1452 | "version": "2.2.7", 1453 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", 1454 | "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=" 1455 | }, 1456 | "regexp-clone": { 1457 | "version": "0.0.1", 1458 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz", 1459 | "integrity": "sha1-p8LgmJH9vzj7sQ03b7cwA+aKxYk=" 1460 | }, 1461 | "repeat-string": { 1462 | "version": "1.6.1", 1463 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 1464 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" 1465 | }, 1466 | "request": { 1467 | "version": "2.81.0", 1468 | "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", 1469 | "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", 1470 | "dependencies": { 1471 | "form-data": { 1472 | "version": "2.1.4", 1473 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", 1474 | "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" 1475 | } 1476 | } 1477 | }, 1478 | "require_optional": { 1479 | "version": "1.0.1", 1480 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 1481 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==" 1482 | }, 1483 | "resolve": { 1484 | "version": "1.1.7", 1485 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", 1486 | "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" 1487 | }, 1488 | "resolve-from": { 1489 | "version": "2.0.0", 1490 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 1491 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 1492 | }, 1493 | "rest-facade": { 1494 | "version": "1.5.0", 1495 | "resolved": "https://registry.npmjs.org/rest-facade/-/rest-facade-1.5.0.tgz", 1496 | "integrity": "sha1-nYhurwJWx+zJAsF8n3v5ByQ43O4=" 1497 | }, 1498 | "resumer": { 1499 | "version": "0.0.0", 1500 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 1501 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 1502 | "dev": true 1503 | }, 1504 | "right-align": { 1505 | "version": "0.1.3", 1506 | "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", 1507 | "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=" 1508 | }, 1509 | "safe-buffer": { 1510 | "version": "5.0.1", 1511 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", 1512 | "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" 1513 | }, 1514 | "samsam": { 1515 | "version": "1.2.1", 1516 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.2.1.tgz", 1517 | "integrity": "sha1-7dOQk6MYQ3DLhZJDsr3yVefY6mc=", 1518 | "dev": true 1519 | }, 1520 | "semver": { 1521 | "version": "5.3.0", 1522 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", 1523 | "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" 1524 | }, 1525 | "send": { 1526 | "version": "0.15.3", 1527 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 1528 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" 1529 | }, 1530 | "sentence-case": { 1531 | "version": "1.1.3", 1532 | "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-1.1.3.tgz", 1533 | "integrity": "sha1-gDSq/CFFdy06vhUJqkLJ4QQtwTk=" 1534 | }, 1535 | "serve-static": { 1536 | "version": "1.12.3", 1537 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 1538 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" 1539 | }, 1540 | "setimmediate": { 1541 | "version": "1.0.5", 1542 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 1543 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 1544 | }, 1545 | "setprototypeof": { 1546 | "version": "1.0.3", 1547 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 1548 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 1549 | }, 1550 | "sinon": { 1551 | "version": "2.3.4", 1552 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.3.4.tgz", 1553 | "integrity": "sha1-RmrY0brobW21GqIYuS6Ze8Pl24g=", 1554 | "dev": true, 1555 | "dependencies": { 1556 | "isarray": { 1557 | "version": "0.0.1", 1558 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 1559 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 1560 | "dev": true 1561 | }, 1562 | "path-to-regexp": { 1563 | "version": "1.7.0", 1564 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", 1565 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 1566 | "dev": true 1567 | } 1568 | } 1569 | }, 1570 | "sinon-mongoose": { 1571 | "version": "2.0.2", 1572 | "resolved": "https://registry.npmjs.org/sinon-mongoose/-/sinon-mongoose-2.0.2.tgz", 1573 | "integrity": "sha1-ZYyuciZXkW0uHmUrApo28ehHrPU=", 1574 | "dev": true 1575 | }, 1576 | "sliced": { 1577 | "version": "1.0.1", 1578 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 1579 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 1580 | }, 1581 | "slugs": { 1582 | "version": "0.1.3", 1583 | "resolved": "https://registry.npmjs.org/slugs/-/slugs-0.1.3.tgz", 1584 | "integrity": "sha1-aa+sUbv3ctuWhFGnGiAm5lQ1CDM=" 1585 | }, 1586 | "snake-case": { 1587 | "version": "1.1.2", 1588 | "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-1.1.2.tgz", 1589 | "integrity": "sha1-DC8l4wUVjZoY09l3BmGH/vilpmo=" 1590 | }, 1591 | "sntp": { 1592 | "version": "1.0.9", 1593 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", 1594 | "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" 1595 | }, 1596 | "source-map": { 1597 | "version": "0.4.4", 1598 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", 1599 | "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=" 1600 | }, 1601 | "sshpk": { 1602 | "version": "1.13.1", 1603 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", 1604 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", 1605 | "dependencies": { 1606 | "assert-plus": { 1607 | "version": "1.0.0", 1608 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 1609 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 1610 | } 1611 | } 1612 | }, 1613 | "statuses": { 1614 | "version": "1.3.1", 1615 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 1616 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 1617 | }, 1618 | "string_decoder": { 1619 | "version": "1.0.2", 1620 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", 1621 | "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=" 1622 | }, 1623 | "string.prototype.trim": { 1624 | "version": "1.1.2", 1625 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", 1626 | "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", 1627 | "dev": true 1628 | }, 1629 | "stringstream": { 1630 | "version": "0.0.5", 1631 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", 1632 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" 1633 | }, 1634 | "superagent": { 1635 | "version": "3.5.2", 1636 | "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.5.2.tgz", 1637 | "integrity": "sha1-M2GjlxVnUEw1EGOr6q4PqiPb8/g=" 1638 | }, 1639 | "supertest": { 1640 | "version": "3.0.0", 1641 | "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.0.0.tgz", 1642 | "integrity": "sha1-jUu2j9GDDuBwM7HFpamkAhyWUpY=", 1643 | "dev": true 1644 | }, 1645 | "supports-color": { 1646 | "version": "3.1.2", 1647 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", 1648 | "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", 1649 | "dev": true 1650 | }, 1651 | "swap-case": { 1652 | "version": "1.1.2", 1653 | "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", 1654 | "integrity": "sha1-w5IDpFhzhfrTyFCgvRvK+ggZdOM=" 1655 | }, 1656 | "tape": { 1657 | "version": "4.6.3", 1658 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.6.3.tgz", 1659 | "integrity": "sha1-Y353WB6ass4XV36b1M5PV1gG2LY=", 1660 | "dev": true 1661 | }, 1662 | "text-encoding": { 1663 | "version": "0.6.4", 1664 | "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", 1665 | "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", 1666 | "dev": true 1667 | }, 1668 | "through": { 1669 | "version": "2.3.8", 1670 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1671 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1672 | "dev": true 1673 | }, 1674 | "title-case": { 1675 | "version": "1.1.2", 1676 | "resolved": "https://registry.npmjs.org/title-case/-/title-case-1.1.2.tgz", 1677 | "integrity": "sha1-+uSmrlRr+iLQg6DuqRCkDRLtT1o=" 1678 | }, 1679 | "token-stream": { 1680 | "version": "0.0.1", 1681 | "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", 1682 | "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" 1683 | }, 1684 | "tough-cookie": { 1685 | "version": "2.3.2", 1686 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", 1687 | "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=" 1688 | }, 1689 | "trim": { 1690 | "version": "0.0.1", 1691 | "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", 1692 | "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" 1693 | }, 1694 | "tunnel-agent": { 1695 | "version": "0.6.0", 1696 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1697 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" 1698 | }, 1699 | "tweetnacl": { 1700 | "version": "0.14.5", 1701 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1702 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1703 | "optional": true 1704 | }, 1705 | "type-detect": { 1706 | "version": "4.0.3", 1707 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", 1708 | "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", 1709 | "dev": true 1710 | }, 1711 | "type-is": { 1712 | "version": "1.6.15", 1713 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 1714 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 1715 | }, 1716 | "ua-parser-js": { 1717 | "version": "0.7.13", 1718 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.13.tgz", 1719 | "integrity": "sha1-zZ3S+GSTs/RNvu7zeA/adMXuFL4=" 1720 | }, 1721 | "uglify-js": { 1722 | "version": "2.8.29", 1723 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", 1724 | "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", 1725 | "dependencies": { 1726 | "source-map": { 1727 | "version": "0.5.6", 1728 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", 1729 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" 1730 | } 1731 | } 1732 | }, 1733 | "uglify-to-browserify": { 1734 | "version": "1.0.2", 1735 | "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", 1736 | "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", 1737 | "optional": true 1738 | }, 1739 | "uid-safe": { 1740 | "version": "2.1.4", 1741 | "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.4.tgz", 1742 | "integrity": "sha1-Otbzg2jG1MjHXsF2I/t5qh0HHYE=" 1743 | }, 1744 | "uid2": { 1745 | "version": "0.0.3", 1746 | "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", 1747 | "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" 1748 | }, 1749 | "unpipe": { 1750 | "version": "1.0.0", 1751 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1752 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1753 | }, 1754 | "upper-case": { 1755 | "version": "1.1.3", 1756 | "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", 1757 | "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" 1758 | }, 1759 | "upper-case-first": { 1760 | "version": "1.1.2", 1761 | "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", 1762 | "integrity": "sha1-XXm+3P8UQZUY/S7bCgUHybaFkRU=" 1763 | }, 1764 | "url-join": { 1765 | "version": "1.1.0", 1766 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", 1767 | "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=" 1768 | }, 1769 | "util-deprecate": { 1770 | "version": "1.0.2", 1771 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1772 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1773 | }, 1774 | "utils-merge": { 1775 | "version": "1.0.0", 1776 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 1777 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 1778 | }, 1779 | "uuid": { 1780 | "version": "3.1.0", 1781 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 1782 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 1783 | }, 1784 | "validator": { 1785 | "version": "6.2.1", 1786 | "resolved": "https://registry.npmjs.org/validator/-/validator-6.2.1.tgz", 1787 | "integrity": "sha1-vFdbeNFb6y4zimZbqVMMf0Ce9mc=" 1788 | }, 1789 | "vary": { 1790 | "version": "1.1.1", 1791 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 1792 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 1793 | }, 1794 | "verror": { 1795 | "version": "1.3.6", 1796 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", 1797 | "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" 1798 | }, 1799 | "void-elements": { 1800 | "version": "2.0.1", 1801 | "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", 1802 | "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" 1803 | }, 1804 | "warning": { 1805 | "version": "3.0.0", 1806 | "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", 1807 | "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=" 1808 | }, 1809 | "whatwg-fetch": { 1810 | "version": "0.9.0", 1811 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz", 1812 | "integrity": "sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA=" 1813 | }, 1814 | "winchan": { 1815 | "version": "0.2.0", 1816 | "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.0.tgz", 1817 | "integrity": "sha1-OGMCjn+XSw2hQS8oQXukJJcqvZQ=" 1818 | }, 1819 | "window-size": { 1820 | "version": "0.1.0", 1821 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", 1822 | "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" 1823 | }, 1824 | "with": { 1825 | "version": "5.1.1", 1826 | "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", 1827 | "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=" 1828 | }, 1829 | "wordwrap": { 1830 | "version": "0.0.2", 1831 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", 1832 | "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" 1833 | }, 1834 | "wrappy": { 1835 | "version": "1.0.2", 1836 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1837 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1838 | "dev": true 1839 | }, 1840 | "xtend": { 1841 | "version": "4.0.1", 1842 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 1843 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 1844 | }, 1845 | "yargs": { 1846 | "version": "3.10.0", 1847 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", 1848 | "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=" 1849 | } 1850 | } 1851 | } 1852 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codecookbook", 3 | "version": "1.0.0", 4 | "description": "An online utility to save, search, and organize snippets of text, such as code, quotes, bash/command line entries.", 5 | "main": "app.js", 6 | "now": { 7 | "dotenv": "variables.env.now" 8 | }, 9 | "scripts": { 10 | "test": "node ./node_modules/mocha/bin/mocha", 11 | "start": "node app.js", 12 | "monitor": "npm run bundle && nodemon app.js", 13 | "bundle": "cd public/js/modules && awk 'FNR==0{print ''}1' *.js > ../bundle.js" 14 | }, 15 | "keywords": [ 16 | "snippets", 17 | "code", 18 | "cookbook", 19 | "bash" 20 | ], 21 | "author": "khan", 22 | "license": "ISC", 23 | "dependencies": { 24 | "auth0": "^2.7.0", 25 | "auth0-lock": "^10.18.0", 26 | "axios": "^0.16.2", 27 | "body-parser": "^1.17.2", 28 | "connect-ensure-login": "^0.1.1", 29 | "connect-flash": "^0.1.1", 30 | "connect-mongo": "^1.3.2", 31 | "cookie-parser": "^1.4.3", 32 | "express": "^4.15.3", 33 | "express-session": "^1.15.3", 34 | "express-validator": "^3.2.0", 35 | "mongoose": "^4.10.6", 36 | "passport": "^0.3.2", 37 | "passport-auth0": "^0.6.0", 38 | "pug": "^2.0.0-rc.2", 39 | "slug": "^0.9.1", 40 | "slugs": "^0.1.3" 41 | }, 42 | "devDependencies": { 43 | "chai": "^4.0.2", 44 | "dotenv": "^4.0.0", 45 | "mocha": "^3.4.2", 46 | "node-mocks-http": "^1.6.3", 47 | "sinon": "^2.3.4", 48 | "sinon-mongoose": "^2.0.2", 49 | "supertest": "^3.0.0", 50 | "tape": "^4.6.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/css/app.css: -------------------------------------------------------------------------------- 1 | body a { 2 | color: #3273dc; 3 | } 4 | 5 | footer.footer { 6 | padding: 0px; 7 | } 8 | 9 | .is-fixed { 10 | position: fixed; 11 | } 12 | 13 | .is-fixed-bottom { 14 | bottom: 0; 15 | } 16 | 17 | .is-full-width { 18 | /* .is-fullwidth should work in bulma, but doesn't */ 19 | width: 100%; 20 | } 21 | 22 | .search__result p { 23 | padding-bottom: 10px; 24 | border-bottom: 1px solid rgba(10,10,10,.1); 25 | } 26 | 27 | .search__results { 28 | background-color: white; 29 | color: #3273dc; 30 | display: none; 31 | overflow: hidden; 32 | position: absolute; 33 | text-align: center; 34 | width: 100%; 35 | z-index: 99; 36 | } 37 | 38 | .search input.input { 39 | background-color: #363636; 40 | border: none; 41 | border-radius: 0px; 42 | color: #f5f5f5; 43 | outline: none; 44 | } -------------------------------------------------------------------------------- /public/js/bundle.js: -------------------------------------------------------------------------------- 1 | function addToCookbook (cookbookID, btnTag) { 2 | let form = findDOMCousin(btnTag, '.box', '.cookbook__form' ) 3 | let inputs = form.getElementsByTagName('input') 4 | let payload = { 5 | recipe: '' 6 | } 7 | 8 | for (var i of inputs) { 9 | if (i.name == 'recipe') payload.recipe = i.value 10 | } 11 | 12 | // assign author from form 13 | payload['author'] = form.querySelector("[name='author']").value 14 | 15 | fetch( 16 | `/cookbook/addRecipe/${cookbookID}`, 17 | { 18 | method: 'PUT', 19 | body: JSON.stringify(payload), 20 | credentials: 'same-origin', 21 | headers: { 22 | 'Accept': 'application/json, text/plain, */*', 23 | 'Content-Type': 'application/json' 24 | }, 25 | } 26 | ) 27 | .then(res => res.json()) 28 | .then(data => closeCookbookMenu(btnTag, data)) 29 | return false 30 | } 31 | // based on https://gist.github.com/paulirish/12fb951a8b893a454b32 32 | 33 | const $ = document.querySelector.bind(document); 34 | const $$ = document.querySelectorAll.bind(document); 35 | 36 | Node.prototype.on = window.on = function (name, fn) { 37 | this.addEventListener(name, fn); 38 | }; 39 | 40 | NodeList.prototype.__proto__ = Array.prototype; // eslint-disable-line 41 | 42 | NodeList.prototype.on = NodeList.prototype.addEventListener = function (name, fn) { 43 | this.forEach((elem) => { 44 | elem.on(name, fn); 45 | }); 46 | }; 47 | function closeCookbookMenu (tag, flashData) { 48 | tag.classList.add('is-loading') 49 | setTimeout(() => { 50 | document.querySelector('.is-active').classList.remove('is-active') 51 | tag.classList.remove('is-loading') 52 | document.getElementsByClassName('flashes__container')[0].innerHTML = ` 53 |
54 |

${flashData.message}

55 | 56 |
57 | ` 58 | }, 1000) 59 | } 60 | function findDOMAncestorByClass (el, cls) { 61 | // finds the closest DOM ancestor with a given class 62 | return el.classList.contains(cls) ? el : findDOMAncestorByClass(el.parentElement, cls) 63 | } 64 | function findDOMCousin (el, parentSelector, cousinSelector) { 65 | // finds the first DOM child with a given selector, given a DOM parent selector 66 | return el.closest(parentSelector).querySelector(cousinSelector) 67 | } 68 | function getUserCookbooks (user_id) { 69 | return fetch(`/cookbook/getAll/${user_id}`) 70 | .then(response => response.json()) 71 | } 72 | function openCookbookMenu(user_id, linkTag) { 73 | let modalTag = findDOMCousin(linkTag, '.box', '.cookbook__modal') 74 | modalTag.classList.add('is-active') 75 | getUserCookbooks(user_id) 76 | .then(results => { 77 | let listDiv = modalTag.getElementsByClassName('cookbook__modal__list')[0] 78 | let listHTML = results.map(cookbook => { 79 | return ` 80 |
81 |
82 |
83 | ${cookbook.title} 84 |
85 |
86 |
87 |
88 | 93 |
94 |
95 |
96 |
97 | ` 98 | }).join('') 99 | listDiv.innerHTML = listHTML 100 | }) 101 | } 102 | function postNewCookbook (btnTag) { 103 | let form = findDOMAncestorByClass(btnTag, 'cookbook__form') 104 | let inputs = form.getElementsByTagName('input') 105 | let payload = { 106 | recipes: [] 107 | } 108 | // get recipe from form 109 | for (var i of inputs) { 110 | i.name == 'recipe' ? 111 | payload.recipes.push(i.value) : 112 | payload[i.name] = i.value 113 | } 114 | 115 | fetch( 116 | '/cookbook/new', 117 | { 118 | method: 'POST', 119 | body: JSON.stringify(payload), 120 | credentials: 'same-origin', 121 | headers: { 122 | 'Accept': 'application/json, text/plain, */*', 123 | 'Content-Type': 'application/json' 124 | }, 125 | } 126 | ) 127 | .then(res => res.json()) 128 | .then(data => closeCookbookMenu(btnTag, data)) 129 | return false 130 | } 131 | function searchResultsHTML (searchResults) { 132 | // generate HTML for an array of search results 133 | return searchResults.map(result => { 134 | return ` 135 | 136 |

${result.title}

137 |
138 | ` 139 | }).join('') 140 | } 141 | function typeAhead (search) { 142 | if (!search) { return } 143 | 144 | let searchInput = search.querySelector('.input') 145 | let searchResults = search.querySelector('.search__results') 146 | 147 | searchInput.on('input', function () { 148 | // prevent unneccessary queries 149 | if (this.value.length < 3) { 150 | searchResults.style.display = 'none' 151 | return 152 | } 153 | // get the search data 154 | let query = `/search?q=${this.value}` 155 | fetch(query) 156 | .then(response => response.json()) 157 | .then(results => { 158 | // hide results div if there is no search result 159 | if (!results.length) { 160 | searchResults.style.display = 'none' 161 | return 162 | } 163 | // show results 164 | searchResults.style.display = 'block' 165 | // append html 166 | searchResults.innerHTML = searchResultsHTML(results) 167 | 168 | }) 169 | }) 170 | } 171 | 172 | // attach typeAhead script to the DOM node with class 'search' 173 | typeAhead($('.search')) 174 | -------------------------------------------------------------------------------- /public/js/modules/addToCookbook.js: -------------------------------------------------------------------------------- 1 | function addToCookbook (cookbookID, btnTag) { 2 | let form = findDOMCousin(btnTag, '.box', '.cookbook__form' ) 3 | let inputs = form.getElementsByTagName('input') 4 | let payload = { 5 | recipe: '' 6 | } 7 | 8 | for (var i of inputs) { 9 | if (i.name == 'recipe') payload.recipe = i.value 10 | } 11 | 12 | // assign author from form 13 | payload['author'] = form.querySelector("[name='author']").value 14 | 15 | fetch( 16 | `/cookbook/addRecipe/${cookbookID}`, 17 | { 18 | method: 'PUT', 19 | body: JSON.stringify(payload), 20 | credentials: 'same-origin', 21 | headers: { 22 | 'Accept': 'application/json, text/plain, */*', 23 | 'Content-Type': 'application/json' 24 | }, 25 | } 26 | ) 27 | .then(res => res.json()) 28 | .then(data => closeCookbookMenu(btnTag, data)) 29 | return false 30 | } -------------------------------------------------------------------------------- /public/js/modules/bling.js: -------------------------------------------------------------------------------- 1 | // based on https://gist.github.com/paulirish/12fb951a8b893a454b32 2 | 3 | const $ = document.querySelector.bind(document); 4 | const $$ = document.querySelectorAll.bind(document); 5 | 6 | Node.prototype.on = window.on = function (name, fn) { 7 | this.addEventListener(name, fn); 8 | }; 9 | 10 | NodeList.prototype.__proto__ = Array.prototype; // eslint-disable-line 11 | 12 | NodeList.prototype.on = NodeList.prototype.addEventListener = function (name, fn) { 13 | this.forEach((elem) => { 14 | elem.on(name, fn); 15 | }); 16 | }; -------------------------------------------------------------------------------- /public/js/modules/closeCookbookMenu.js: -------------------------------------------------------------------------------- 1 | function closeCookbookMenu (tag, flashData) { 2 | tag.classList.add('is-loading') 3 | setTimeout(() => { 4 | document.querySelector('.is-active').classList.remove('is-active') 5 | tag.classList.remove('is-loading') 6 | document.getElementsByClassName('flashes__container')[0].innerHTML = ` 7 |
8 |

${flashData.message}

9 | 10 |
11 | ` 12 | }, 1000) 13 | } -------------------------------------------------------------------------------- /public/js/modules/findDOMAncestor.js: -------------------------------------------------------------------------------- 1 | function findDOMAncestorByClass (el, cls) { 2 | // finds the closest DOM ancestor with a given class 3 | return el.classList.contains(cls) ? el : findDOMAncestorByClass(el.parentElement, cls) 4 | } -------------------------------------------------------------------------------- /public/js/modules/findDOMCousin.js: -------------------------------------------------------------------------------- 1 | function findDOMCousin (el, parentSelector, cousinSelector) { 2 | // finds the first DOM child with a given selector, given a DOM parent selector 3 | return el.closest(parentSelector).querySelector(cousinSelector) 4 | } -------------------------------------------------------------------------------- /public/js/modules/getUserCookbooks.js: -------------------------------------------------------------------------------- 1 | function getUserCookbooks (user_id) { 2 | return fetch(`/cookbook/getAll/${user_id}`) 3 | .then(response => response.json()) 4 | } -------------------------------------------------------------------------------- /public/js/modules/openCookbookMenu.js: -------------------------------------------------------------------------------- 1 | function openCookbookMenu(user_id, linkTag) { 2 | let modalTag = findDOMCousin(linkTag, '.box', '.cookbook__modal') 3 | modalTag.classList.add('is-active') 4 | getUserCookbooks(user_id) 5 | .then(results => { 6 | let listDiv = modalTag.getElementsByClassName('cookbook__modal__list')[0] 7 | let listHTML = results.map(cookbook => { 8 | return ` 9 |
10 |
11 |
12 | ${cookbook.title} 13 |
14 |
15 |
16 |
17 | 22 |
23 |
24 |
25 |
26 | ` 27 | }).join('') 28 | listDiv.innerHTML = listHTML 29 | }) 30 | } -------------------------------------------------------------------------------- /public/js/modules/postNewCookbook.js: -------------------------------------------------------------------------------- 1 | function postNewCookbook (btnTag) { 2 | let form = findDOMAncestorByClass(btnTag, 'cookbook__form') 3 | let inputs = form.getElementsByTagName('input') 4 | let payload = { 5 | recipes: [] 6 | } 7 | // get recipe from form 8 | for (var i of inputs) { 9 | i.name == 'recipe' ? 10 | payload.recipes.push(i.value) : 11 | payload[i.name] = i.value 12 | } 13 | 14 | fetch( 15 | '/cookbook/new', 16 | { 17 | method: 'POST', 18 | body: JSON.stringify(payload), 19 | credentials: 'same-origin', 20 | headers: { 21 | 'Accept': 'application/json, text/plain, */*', 22 | 'Content-Type': 'application/json' 23 | }, 24 | } 25 | ) 26 | .then(res => res.json()) 27 | .then(data => closeCookbookMenu(btnTag, data)) 28 | return false 29 | } -------------------------------------------------------------------------------- /public/js/modules/searchResultsHTML.js: -------------------------------------------------------------------------------- 1 | function searchResultsHTML (searchResults) { 2 | // generate HTML for an array of search results 3 | return searchResults.map(result => { 4 | return ` 5 | 6 |

${result.title}

7 |
8 | ` 9 | }).join('') 10 | } -------------------------------------------------------------------------------- /public/js/modules/typeAhead.js: -------------------------------------------------------------------------------- 1 | function typeAhead (search) { 2 | if (!search) { return } 3 | 4 | let searchInput = search.querySelector('.input') 5 | let searchResults = search.querySelector('.search__results') 6 | 7 | searchInput.on('input', function () { 8 | // prevent unneccessary queries 9 | if (this.value.length < 3) { 10 | searchResults.style.display = 'none' 11 | return 12 | } 13 | // get the search data 14 | let query = `/search?q=${this.value}` 15 | fetch(query) 16 | .then(response => response.json()) 17 | .then(results => { 18 | // hide results div if there is no search result 19 | if (!results.length) { 20 | searchResults.style.display = 'none' 21 | return 22 | } 23 | // show results 24 | searchResults.style.display = 'block' 25 | // append html 26 | searchResults.innerHTML = searchResultsHTML(results) 27 | 28 | }) 29 | }) 30 | } 31 | 32 | // attach typeAhead script to the DOM node with class 'search' 33 | typeAhead($('.search')) -------------------------------------------------------------------------------- /routes/index.routes.js: -------------------------------------------------------------------------------- 1 | const { catchErrors, flashValidationErrors } = require('../handlers/error.handler') 2 | const cookbook = require('../controllers/cookbook.controller') 3 | const ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn() 4 | const express = require('express') 5 | const landing = require('../controllers/landing.controller') 6 | const passport = require('passport') 7 | const router = express.Router() 8 | const recipe = require('../controllers/recipe.controller') 9 | 10 | // home 11 | router.get('/', landing.home) 12 | 13 | // authentication (via Auth0 & passport) 14 | router.get('/login', landing.login) 15 | router.get('/logout', landing.logout) 16 | router.get('/callback', 17 | passport.authenticate('auth0', { 18 | failureRedirect: '/', 19 | failureFlash: 'There was a problem logging in.', 20 | successFlash: 'You are now logged in!' 21 | }), 22 | function (req, res) { 23 | res.redirect(req.session.returnTo || '/') 24 | } 25 | ) 26 | 27 | 28 | // search recipe 29 | router.get('/search', catchErrors(recipe.search)) 30 | // view recipe 31 | router.get('/recipe/view/:slug', catchErrors(recipe.read)) 32 | // new recipe 33 | router.get('/recipe/edit', ensureLoggedIn, recipe.new) 34 | router.post('/recipe/edit', ensureLoggedIn, catchErrors(recipe.create), flashValidationErrors) 35 | // edit recipe 36 | router.get('/recipe/edit/:slug', ensureLoggedIn, catchErrors(recipe.edit)) 37 | router.post('/recipe/edit/:slug', ensureLoggedIn, catchErrors(recipe.update), flashValidationErrors) 38 | // destroy recipe 39 | router.get('/recipe/delete/:id', ensureLoggedIn, catchErrors(recipe.destroy)) 40 | // view all recipe created by currently logged in account 41 | router.get('/recipe/:user_id', ensureLoggedIn, catchErrors(recipe.readAll)) 42 | 43 | 44 | // view cookbooks 45 | router.get('/cookbook/view/:slug', cookbook.read) 46 | router.get('/cookbook/:user_id', ensureLoggedIn, cookbook.readAll) 47 | router.get('/cookbook/getAll/:user_id', cookbook.getAll) 48 | 49 | // save cookbook 50 | router.post('/cookbook/new', ensureLoggedIn, catchErrors(cookbook.create)) 51 | // add to cookbook 52 | router.put('/cookbook/addRecipe/:cookbook_id', ensureLoggedIn, catchErrors(cookbook.addRecipe)) 53 | // edit cookbook 54 | router.get('/cookbook/edit/:slug', ensureLoggedIn, catchErrors(cookbook.edit)) 55 | router.post('/cookbook/edit/:slug', catchErrors(cookbook.update), flashValidationErrors) 56 | // destroy cookbook 57 | router.get('/cookbook/delete/:id', ensureLoggedIn, catchErrors(cookbook.destroy)) 58 | //remove a recipe from a cookbook 59 | router.get('/cookbook/removeRecipe/:cookbook_id/:recipe_id', ensureLoggedIn, catchErrors(cookbook.removeRecipe)) 60 | 61 | module.exports = router -------------------------------------------------------------------------------- /test/authorize.test.js: -------------------------------------------------------------------------------- 1 | const authorize = require('../utils/authorize') 2 | const chai = require('chai') 3 | const expect = chai.expect 4 | 5 | describe('It should authorize a user', function () { 6 | it('returns false without a user_id and an author_id', function () { 7 | expect(authorize()).to.eql(false) 8 | }) 9 | 10 | it('returns false with a mismatched user_id and an author_id', function () { 11 | expect(authorize('123', 'abc')).to.eql(false) 12 | }) 13 | 14 | it('returns true with matching user_id and an author_id', function () { 15 | expect(authorize('123', '123')).to.eql(true) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /utils/authorize.js: -------------------------------------------------------------------------------- 1 | function authorize (user_id, author_id) { 2 | // verifies the current user is the author of a given object 3 | if (!author_id || !user_id) return false 4 | if (author_id != user_id) return false 5 | return true 6 | } 7 | 8 | module.exports = authorize -------------------------------------------------------------------------------- /utils/dbConnect.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | function dbConnect (db) { 4 | mongoose.connect(db) 5 | mongoose.Promise = global.Promise 6 | mongoose.connection.on('error', (err) => { 7 | console.error(`Ya dun goofed. ${err.message}`) 8 | }) 9 | } 10 | 11 | module.exports = dbConnect -------------------------------------------------------------------------------- /views/404.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1.has-text-centered The page you're looking for was not found! -------------------------------------------------------------------------------- /views/editCookbook.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | section.section 5 | .container 6 | h1= title 7 | if title 8 | hr 9 | .card 10 | .card-content 11 | form(action=`/cookbook/edit/${data.slug || ''}` method="POST") 12 | .field 13 | label.label Title 14 | p.control 15 | input.input(value=data.title name="title") 16 | .field 17 | p.control 18 | input.button.is-info(type="submit" value="Save") -------------------------------------------------------------------------------- /views/editRecipe.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_recipeForm 4 | 5 | block content 6 | section.section 7 | .container 8 | h1= title 9 | .card 10 | +recipeForm(data) 11 | 12 | -------------------------------------------------------------------------------- /views/footer.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | 4 | block footer 5 | footer.footer 6 | p.test -------------------------------------------------------------------------------- /views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | section.section 5 | .container 6 | h1.title How Many Times Did You Visit Stack Overflow Today? 7 | p If you're like most software developers, the answer is... 8 | strong too many to count! 9 | p. 10 | Constant searching of Stack Overflow, digging through documentation, and scouring forums is all too common. 11 | Usually, the search is for something you already know - its on the tip of your tongue - but you can't remember the syntax. 12 | p. 13 | Code Cookbook aims to change that. Its a lightweight platform for developers to create, organize, and search for 14 | code snippets, bash commands, and other obscure bits of information. 15 | p. 16 | Code Cookbook calls these bits of information recipes, and any time you create one, it gets linked to your account for easy access. 17 | You can also organize recipes into cookbooks. Use cookbooks as guides for deploying to Heroku, a syntax reminder for a framework 18 | you use once in a blue moon, or whatever else you can think of! 19 | p. 20 | Recipes are visible to everyone and can be found via the search bar at the top. As more users create recipes it will be easier to 21 | find those pesky bits of code. -------------------------------------------------------------------------------- /views/layout.pug: -------------------------------------------------------------------------------- 1 | 2 | html(lang="en") 3 | head 4 | meta(charset="UTF-8") 5 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 6 | meta(http-equiv="X-UA-Compatible", content="ie=edge") 7 | link(href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.4.3/css/bulma.min.css" rel="stylesheet") 8 | link(href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet") 9 | script(src="https://cdn.auth0.com/js/auth0/8.0.0/auth0.min.js") 10 | script(src="https://cdn.auth0.com/js/lock/10.18.0/lock.min.js") 11 | 12 | link(href="/css/app.css" rel="stylesheet") 13 | 14 | title Code Cookbook 15 | body 16 | section.hero.is-dark.is-medium 17 | .hero-head 18 | header.nav 19 | .container 20 | .nav-left 21 | a.nav-item(href='/') Code Cookbook 22 | .nav-right 23 | .nav-item 24 | form.is-dark 25 | .container 26 | .control.search 27 | input.input(placeholder="Search recipes...") 28 | .search__results 29 | 30 | block messages 31 | if locals.flashes 32 | section.section 33 | .container.flashes__container 34 | - const categories = Object.keys(locals.flashes) 35 | each category in categories 36 | each message in flashes[category] 37 | .notification(class=`is-${category}`) 38 | p.flash__text!= message 39 | button.delete(onClick="this.parentElement.remove()") 40 | .content 41 | block content 42 | 43 | section.section 44 | 45 | footer.footer.is-fixed.is-fixed-bottom.is-full-width 46 | .card 47 | .card-footer 48 | a.card-footer-item(href="/recipe/edit") 49 | span.icon 50 | i.fa.fa-pencil 51 | p.is-hidden-mobile New Recipe 52 | a.card-footer-item(href=`/recipe/${locals.user ? locals.user.id : 'edit'}`) 53 | span.icon 54 | i.fa.fa-file-text 55 | p.is-hidden-mobile My Recipes 56 | a.card-footer-item(href=`/cookbook/${locals.user ? locals.user.id : 'edit'}`) 57 | span.icon 58 | i.fa.fa-book 59 | p.is-hidden-mobile My Cookbooks 60 | 61 | if !locals.user 62 | a.card-footer-item(href="/login") 63 | span.icon 64 | i.fa.fa-user 65 | span.is-hidden-mobile Login 66 | else 67 | a.card-footer-item(href="/logout") 68 | span.icon 69 | i.fa.fa-user 70 | span.is-hidden-mobile Logout 71 | 72 | script(src='/js/bundle.js' type="text/javascript") 73 | -------------------------------------------------------------------------------- /views/login.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | 5 | script. 6 | 7 | var lock = new Auth0Lock( 8 | '#{env.AUTH0_CLIENT}', 9 | '#{env.AUTH0_DOMAIN}', 10 | { 11 | auth: { 12 | redirectUrl: '#{env.AUTH0_CALLBACK}', 13 | rememberLastLogin: true, 14 | }, 15 | closable: false 16 | } 17 | ) 18 | 19 | lock.on("authenticated", function (authResult) { 20 | // Use the token in authResult to getUserInfo() and save it to localStorage 21 | lock.getUserInfo(authResult.accessToken, function (error, profile) { 22 | if (error) { 23 | // Handle error 24 | console.warn(error) 25 | return; 26 | } 27 | 28 | localStorage.setItem('accessToken', authResult.accessToken) 29 | localStorage.setItem('profile', JSON.stringify(profile)) 30 | }) 31 | }) 32 | 33 | lock.show() -------------------------------------------------------------------------------- /views/mixins/_cookbook.pug: -------------------------------------------------------------------------------- 1 | mixin cookbook(cookbook = {}) 2 | .box 3 | h1 4 | a(href=`/cookbook/view/${cookbook.slug}`)= cookbook.title 5 | nav.level.is-mobile 6 | .level-left 7 | if authorized 8 | a.level-item(href=`/cookbook/edit/${cookbook.slug}`) 9 | span.icon.is-medium 10 | i.fa.fa-pencil-square-o 11 | a.level-item(onclick=`if (confirm('Are you sure you want to delete?')) window.location = "/cookbook/delete/${cookbook._id}"`) 12 | span.icon.is-medium 13 | i.fa.fa-trash -------------------------------------------------------------------------------- /views/mixins/_cookbookModal.pug: -------------------------------------------------------------------------------- 1 | mixin cookbookModal(recipe = {}) 2 | if locals.user 3 | .modal.cookbook__modal 4 | .modal-background(onclick="this.parentElement.classList.remove('is-active')") 5 | .modal-content 6 | .box 7 | p 8 | strong Add to Cookbook 9 | form.form.cookbook__form 10 | .control 11 | input.input(type="hidden" value=recipe._id name="recipe") 12 | .control 13 | input.input(type="hidden" value=locals.user.id name="author") 14 | .field.has-addons 15 | .control.is-expanded 16 | input.input.cookbook__input(placeholder="New cookbook", name='title') 17 | .control 18 | button.button.is-info(onclick=`return postNewCookbook(this)`) 19 | span.icon 20 | i.fa.fa-check 21 | hr 22 | .cookbook__modal__list 23 | -------------------------------------------------------------------------------- /views/mixins/_recipe.pug: -------------------------------------------------------------------------------- 1 | mixin recipe(recipe = {}, cookbook = {}) 2 | include _cookbookModal 3 | .box 4 | +cookbookModal(recipe) 5 | h4= recipe.title 6 | pre= recipe.content 7 | nav.level.is-mobile 8 | .level-left 9 | if authorized 10 | a.level-item(href=`/recipe/edit/${recipe.slug}`) 11 | span.icon.is-small 12 | i.fa.fa-pencil-square-o 13 | a.level-item(onclick=`if (confirm('Are you sure you want to delete?')) window.location = "/recipe/delete/${recipe._id}"`) 14 | span.icon.is-small 15 | i.fa.fa-trash 16 | if cookbook.id 17 | a.level-item(onclick=`if (confirm('Are you sure you want to remove this from the cookbook?')) window.location = "/cookbook/removeRecipe/${cookbook.id}/${recipe._id}"`) 18 | span.icon.is-small 19 | i.fa.fa-chain-broken 20 | if locals.user 21 | a.level-item 22 | span.icon.is-small(onclick=`openCookbookMenu('${locals.user.id}', this)`) 23 | i.fa.fa-plus 24 | -------------------------------------------------------------------------------- /views/mixins/_recipeForm.pug: -------------------------------------------------------------------------------- 1 | mixin recipeForm(data = {}) 2 | .card 3 | .card-content 4 | form(action=`/recipe/edit/${data.slug || ''}` method="POST") 5 | .field 6 | label.label Title 7 | p.control 8 | input.input(value=data.title name="title") 9 | .field 10 | label.label Content 11 | p.control 12 | textarea.textarea(name="content")= data.content 13 | .field 14 | p.control 15 | input.button.is-info(type="submit" value="Save") -------------------------------------------------------------------------------- /views/viewCookbooks.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_cookbook 4 | 5 | block content 6 | section.section 7 | .container 8 | h1= title 9 | if title 10 | hr 11 | each cookbook in data 12 | +cookbook(cookbook) 13 | 14 | 15 | -------------------------------------------------------------------------------- /views/viewRecipes.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | include mixins/_recipe 4 | 5 | block content 6 | section.section 7 | .container 8 | h1= title 9 | if title 10 | hr 11 | each recipe in data 12 | +recipe(recipe, cookbook || {}) 13 | 14 | 15 | --------------------------------------------------------------------------------