├── .buildignore ├── .gitattributes ├── _.gitattributes ├── client ├── app │ ├── landing │ │ ├── js │ │ │ ├── preloader.js │ │ │ ├── parallax.js │ │ │ ├── jQuery.headroom.js │ │ │ ├── jquery.inview.min.js │ │ │ ├── countUp.min.js │ │ │ ├── headroom.min.js │ │ │ ├── jquery.countdown.min.js │ │ │ └── validator.js │ │ ├── .DS_Store │ │ ├── img │ │ │ ├── 02zoomin.png │ │ │ ├── about │ │ │ │ ├── 01.png │ │ │ │ └── 02.png │ │ │ ├── logos │ │ │ │ ├── 01.png │ │ │ │ ├── 02.png │ │ │ │ ├── 03.png │ │ │ │ ├── 04.png │ │ │ │ ├── 05.png │ │ │ │ └── 06.png │ │ │ ├── 03pattern.png │ │ │ ├── 04textarea.png │ │ │ ├── avatars │ │ │ │ ├── 01.jpg │ │ │ │ ├── 02.jpg │ │ │ │ ├── 03.jpg │ │ │ │ ├── 04.jpg │ │ │ │ ├── 05.jpg │ │ │ │ ├── 06.jpg │ │ │ │ ├── 07.jpg │ │ │ │ ├── 08.jpg │ │ │ │ ├── 09.jpg │ │ │ │ ├── 10.jpg │ │ │ │ ├── 11.jpg │ │ │ │ ├── 12.jpg │ │ │ │ ├── jonah.JPG │ │ │ │ ├── rioa.jpg │ │ │ │ ├── jordan.jpg │ │ │ │ └── seunghoon.PNG │ │ │ ├── gallery │ │ │ │ ├── 01.jpg │ │ │ │ ├── 02.jpg │ │ │ │ ├── 03.jpg │ │ │ │ ├── 04.jpg │ │ │ │ ├── 05.jpg │ │ │ │ └── big │ │ │ │ │ ├── 01.jpg │ │ │ │ │ ├── 02.jpg │ │ │ │ │ ├── 03.jpg │ │ │ │ │ ├── 04.jpg │ │ │ │ │ └── 05.jpg │ │ │ ├── grey_bg │ │ │ │ ├── 01.jpg │ │ │ │ └── 02.jpg │ │ │ ├── logoMidUp.jpg │ │ │ ├── preloader.gif │ │ │ ├── header │ │ │ │ ├── bg02.jpg │ │ │ │ ├── bg03.jpg │ │ │ │ ├── bg03-blue.jpg │ │ │ │ ├── bg03-green.jpg │ │ │ │ ├── bg03-pink.jpg │ │ │ │ ├── bg03-yellow.jpg │ │ │ │ ├── img-iphone.png │ │ │ │ ├── bg03-coldgrey.jpg │ │ │ │ ├── bg03-pantone2014.jpg │ │ │ │ └── bg03-pantone2015.jpg │ │ │ ├── 01-logo-ORANGE.png │ │ │ ├── favicon │ │ │ │ ├── favicon.ico │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── apple-touch-icon-72x72.png │ │ │ │ └── apple-touch-icon-114x114.png │ │ │ ├── parallax_bg │ │ │ │ ├── 01.jpg │ │ │ │ ├── 01-old.jpg │ │ │ │ ├── 01-blue.jpg │ │ │ │ ├── 01-green.jpg │ │ │ │ ├── 01-pink.jpg │ │ │ │ ├── 01-coldgrey.jpg │ │ │ │ ├── 01-yellow.jpg │ │ │ │ ├── 01-pantone2014.jpg │ │ │ │ └── 01-pantone2015.jpg │ │ │ └── lightbox │ │ │ │ ├── controls.png │ │ │ │ └── loading.gif │ │ └── fonts │ │ │ ├── ElegantIcons.eot │ │ │ ├── ElegantIcons.ttf │ │ │ └── ElegantIcons.woff │ ├── admin │ │ ├── admin.css │ │ ├── admin.js │ │ ├── admin.controller.js │ │ └── admin.html │ ├── main │ │ ├── main.js │ │ ├── main.controller.js │ │ ├── main.css │ │ └── main.controller.spec.js │ ├── google-maps │ │ ├── google-maps.js │ │ ├── google-maps.css │ │ └── google-maps.html │ ├── account │ │ ├── login │ │ │ ├── login.css │ │ │ ├── login.controller.js │ │ │ └── login.html │ │ ├── account.js │ │ ├── settings │ │ │ ├── settings.controller.js │ │ │ └── settings.html │ │ └── signup │ │ │ ├── signup.controller.js │ │ │ └── signup.html │ ├── app.css │ └── app.js ├── robots.txt ├── .DS_Store ├── assets │ └── images │ │ └── yeoman.png ├── components │ ├── mongoose-error │ │ └── mongoose-error.directive.js │ ├── auth │ │ ├── user.service.js │ │ └── auth.service.js │ ├── modal │ │ ├── modal.css │ │ ├── modal.html │ │ └── modal.service.js │ └── navbar │ │ ├── navbar.controller.js │ │ └── navbar.html ├── .jshintrc ├── favicon.ico └── index.html ├── .bowerrc ├── _.travis.yml ├── .gitignore ├── .travis.yml ├── server ├── .jshintrc-spec ├── config │ ├── environment │ │ ├── test.js │ │ ├── development.js │ │ ├── production.js │ │ └── index.js │ ├── local.env.sample.js │ ├── seed.js │ ├── socketio.js │ └── express.js ├── api │ ├── thing │ │ ├── thing.model.js │ │ ├── index.js │ │ ├── thing.spec.js │ │ ├── thing.socket.js │ │ └── thing.controller.js │ └── user │ │ ├── index.js │ │ ├── user.model.spec.js │ │ ├── user.controller.js │ │ └── user.model.js ├── .jshintrc ├── components │ └── errors │ │ └── index.js ├── auth │ ├── twitter │ │ ├── index.js │ │ └── passport.js │ ├── facebook │ │ ├── index.js │ │ └── passport.js │ ├── local │ │ ├── index.js │ │ └── passport.js │ ├── google │ │ ├── index.js │ │ └── passport.js │ ├── index.js │ └── auth.service.js ├── routes.js ├── app.js └── views │ └── 404.html ├── dist ├── server │ ├── config │ │ ├── environment │ │ │ ├── test.js │ │ │ ├── development.js │ │ │ ├── production.js │ │ │ └── index.js │ │ ├── local.env.js │ │ ├── local.env.sample.js │ │ ├── seed.js │ │ ├── socketio.js │ │ └── express.js │ ├── api │ │ ├── thing │ │ │ ├── thing.model.js │ │ │ ├── index.js │ │ │ ├── thing.spec.js │ │ │ ├── thing.socket.js │ │ │ └── thing.controller.js │ │ └── user │ │ │ ├── index.js │ │ │ ├── user.model.spec.js │ │ │ ├── user.controller.js │ │ │ └── user.model.js │ ├── components │ │ └── errors │ │ │ └── index.js │ ├── auth │ │ ├── twitter │ │ │ ├── index.js │ │ │ └── passport.js │ │ ├── facebook │ │ │ ├── index.js │ │ │ └── passport.js │ │ ├── local │ │ │ ├── index.js │ │ │ └── passport.js │ │ ├── google │ │ │ ├── index.js │ │ │ └── passport.js │ │ ├── index.js │ │ └── auth.service.js │ ├── routes.js │ ├── app.js │ └── views │ │ └── 404.html └── package.json ├── e2e └── main │ ├── main.po.js │ └── main.spec.js ├── _.jshintrc ├── .editorconfig ├── _.editorconfig ├── _.gitignore ├── bower.json ├── README.md ├── .yo-rc.json ├── protractor.conf.js ├── karma.conf.js ├── package.json ├── _PRESS-RELEASE.md └── _CONTRIBUTING.md /.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /_.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /client/app/landing/js/preloader.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /client/app/admin/admin.css: -------------------------------------------------------------------------------- 1 | .trash { color:rgb(209, 91, 71); } 2 | -------------------------------------------------------------------------------- /_.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.8' 4 | - '0.10' 5 | -------------------------------------------------------------------------------- /client/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | .tmp 4 | .idea 5 | client/bower_components 6 | /server/config/local.env.js 7 | -------------------------------------------------------------------------------- /client/app/landing/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/.DS_Store -------------------------------------------------------------------------------- /client/assets/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/assets/images/yeoman.png -------------------------------------------------------------------------------- /client/app/landing/img/02zoomin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/02zoomin.png -------------------------------------------------------------------------------- /client/app/landing/img/about/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/about/01.png -------------------------------------------------------------------------------- /client/app/landing/img/about/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/about/02.png -------------------------------------------------------------------------------- /client/app/landing/img/logos/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/logos/01.png -------------------------------------------------------------------------------- /client/app/landing/img/logos/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/logos/02.png -------------------------------------------------------------------------------- /client/app/landing/img/logos/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/logos/03.png -------------------------------------------------------------------------------- /client/app/landing/img/logos/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/logos/04.png -------------------------------------------------------------------------------- /client/app/landing/img/logos/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/logos/05.png -------------------------------------------------------------------------------- /client/app/landing/img/logos/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/logos/06.png -------------------------------------------------------------------------------- /client/app/landing/img/03pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/03pattern.png -------------------------------------------------------------------------------- /client/app/landing/img/04textarea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/04textarea.png -------------------------------------------------------------------------------- /client/app/landing/img/avatars/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/01.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/02.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/03.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/04.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/05.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/06.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/07.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/08.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/09.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/10.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/11.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/12.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/01.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/02.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/03.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/04.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/05.jpg -------------------------------------------------------------------------------- /client/app/landing/img/grey_bg/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/grey_bg/01.jpg -------------------------------------------------------------------------------- /client/app/landing/img/grey_bg/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/grey_bg/02.jpg -------------------------------------------------------------------------------- /client/app/landing/img/logoMidUp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/logoMidUp.jpg -------------------------------------------------------------------------------- /client/app/landing/img/preloader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/preloader.gif -------------------------------------------------------------------------------- /client/app/landing/img/avatars/jonah.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/jonah.JPG -------------------------------------------------------------------------------- /client/app/landing/img/avatars/rioa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/rioa.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/bg02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg02.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/bg03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg03.jpg -------------------------------------------------------------------------------- /client/app/landing/fonts/ElegantIcons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/fonts/ElegantIcons.eot -------------------------------------------------------------------------------- /client/app/landing/fonts/ElegantIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/fonts/ElegantIcons.ttf -------------------------------------------------------------------------------- /client/app/landing/fonts/ElegantIcons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/fonts/ElegantIcons.woff -------------------------------------------------------------------------------- /client/app/landing/img/01-logo-ORANGE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/01-logo-ORANGE.png -------------------------------------------------------------------------------- /client/app/landing/img/avatars/jordan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/jordan.jpg -------------------------------------------------------------------------------- /client/app/landing/img/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/favicon/favicon.ico -------------------------------------------------------------------------------- /client/app/landing/img/gallery/big/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/big/01.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/big/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/big/02.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/big/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/big/03.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/big/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/big/04.jpg -------------------------------------------------------------------------------- /client/app/landing/img/gallery/big/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/gallery/big/05.jpg -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01.jpg -------------------------------------------------------------------------------- /client/app/landing/img/avatars/seunghoon.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/avatars/seunghoon.PNG -------------------------------------------------------------------------------- /client/app/landing/img/header/bg03-blue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg03-blue.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/bg03-green.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg03-green.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/bg03-pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg03-pink.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/bg03-yellow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg03-yellow.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/img-iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/img-iphone.png -------------------------------------------------------------------------------- /client/app/landing/img/lightbox/controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/lightbox/controls.png -------------------------------------------------------------------------------- /client/app/landing/img/lightbox/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/lightbox/loading.gif -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01-old.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01-old.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/bg03-coldgrey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg03-coldgrey.jpg -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01-blue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01-blue.jpg -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01-green.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01-green.jpg -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01-pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01-pink.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/bg03-pantone2014.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg03-pantone2014.jpg -------------------------------------------------------------------------------- /client/app/landing/img/header/bg03-pantone2015.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/header/bg03-pantone2015.jpg -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01-coldgrey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01-coldgrey.jpg -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01-yellow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01-yellow.jpg -------------------------------------------------------------------------------- /client/app/landing/img/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.11' 5 | before_script: 6 | - npm install -g bower grunt-cli 7 | - bower install 8 | services: mongodb -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01-pantone2014.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01-pantone2014.jpg -------------------------------------------------------------------------------- /client/app/landing/img/parallax_bg/01-pantone2015.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/parallax_bg/01-pantone2015.jpg -------------------------------------------------------------------------------- /client/app/landing/img/favicon/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/favicon/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /client/app/landing/img/favicon/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RioaM/Meet-Me-In-The-Middle/HEAD/client/app/landing/img/favicon/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /server/.jshintrc-spec: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ".jshintrc", 3 | "globals": { 4 | "describe": true, 5 | "it": true, 6 | "before": true, 7 | "beforeEach": true, 8 | "after": true, 9 | "afterEach": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /server/config/environment/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Test specific configuration 4 | // =========================== 5 | module.exports = { 6 | // MongoDB connection options 7 | mongo: { 8 | uri: 'mongodb://localhost/meetmeinthemiddle-test' 9 | } 10 | }; -------------------------------------------------------------------------------- /dist/server/config/environment/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Test specific configuration 4 | // =========================== 5 | module.exports = { 6 | // MongoDB connection options 7 | mongo: { 8 | uri: 'mongodb://localhost/meetmeinthemiddle-test' 9 | } 10 | }; -------------------------------------------------------------------------------- /server/api/thing/thing.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | Schema = mongoose.Schema; 5 | 6 | var ThingSchema = new Schema({ 7 | name: String, 8 | info: String, 9 | active: Boolean 10 | }); 11 | 12 | module.exports = mongoose.model('Thing', ThingSchema); -------------------------------------------------------------------------------- /client/app/main/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('main', { 7 | url: '/', 8 | templateUrl: 'app/main/main.html', 9 | controller: 'MainCtrl' 10 | }); 11 | }); -------------------------------------------------------------------------------- /dist/server/api/thing/thing.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | Schema = mongoose.Schema; 5 | 6 | var ThingSchema = new Schema({ 7 | name: String, 8 | info: String, 9 | active: Boolean 10 | }); 11 | 12 | module.exports = mongoose.model('Thing', ThingSchema); -------------------------------------------------------------------------------- /client/app/admin/admin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('admin', { 7 | url: '/admin', 8 | templateUrl: 'app/admin/admin.html', 9 | controller: 'AdminCtrl' 10 | }); 11 | }); -------------------------------------------------------------------------------- /server/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "eqeqeq": true, 6 | "immed": true, 7 | "latedef": "nofunc", 8 | "newcap": true, 9 | "noarg": true, 10 | "regexp": true, 11 | "undef": true, 12 | "smarttabs": true, 13 | "asi": true, 14 | "debug": true 15 | } 16 | -------------------------------------------------------------------------------- /server/config/environment/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Development specific configuration 4 | // ================================== 5 | module.exports = { 6 | // MongoDB connection options 7 | mongo: { 8 | uri: 'mongodb://localhost/meetmeinthemiddle-dev' 9 | }, 10 | 11 | seedDB: true 12 | }; 13 | -------------------------------------------------------------------------------- /dist/server/config/environment/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Development specific configuration 4 | // ================================== 5 | module.exports = { 6 | // MongoDB connection options 7 | mongo: { 8 | uri: 'mongodb://localhost/meetmeinthemiddle-dev' 9 | }, 10 | 11 | seedDB: true 12 | }; 13 | -------------------------------------------------------------------------------- /client/app/google-maps/google-maps.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('google-maps', { 7 | url: '/maps', 8 | templateUrl: 'app/google-maps/google-maps.html', 9 | controller: 'MapsCtrl' 10 | }); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /server/api/thing/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./thing.controller'); 5 | 6 | var router = express.Router(); 7 | 8 | router.get('/', controller.index); 9 | router.get('/:id', controller.show); 10 | router.post('/', controller.create); 11 | router.put('/:id', controller.update); 12 | router.patch('/:id', controller.update); 13 | router.delete('/:id', controller.destroy); 14 | 15 | module.exports = router; -------------------------------------------------------------------------------- /dist/server/api/thing/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./thing.controller'); 5 | 6 | var router = express.Router(); 7 | 8 | router.get('/', controller.index); 9 | router.get('/:id', controller.show); 10 | router.post('/', controller.create); 11 | router.put('/:id', controller.update); 12 | router.patch('/:id', controller.update); 13 | router.delete('/:id', controller.destroy); 14 | 15 | module.exports = router; -------------------------------------------------------------------------------- /client/app/account/login/login.css: -------------------------------------------------------------------------------- 1 | .btn-facebook { 2 | color: #fff; 3 | background-color: #3B5998; 4 | border-color: #133783; 5 | } 6 | 7 | .btn-twitter { 8 | color: #fff; 9 | background-color: #2daddc; 10 | border-color: #0271bf; 11 | } 12 | 13 | .btn-google-plus { 14 | color: #fff; 15 | background-color: #dd4b39; 16 | border-color: #c53727; 17 | } 18 | 19 | .btn-github { 20 | color: #fff; 21 | background-color: #fafafa; 22 | border-color: #ccc; 23 | } 24 | -------------------------------------------------------------------------------- /e2e/main/main.po.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file uses the Page Object pattern to define the main page for tests 3 | * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var MainPage = function() { 9 | this.heroEl = element(by.css('.hero-unit')); 10 | this.h1El = this.heroEl.element(by.css('h1')); 11 | this.imgEl = this.heroEl.element(by.css('img')); 12 | }; 13 | 14 | module.exports = new MainPage(); 15 | 16 | -------------------------------------------------------------------------------- /server/components/errors/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Error responses 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports[404] = function pageNotFound(req, res) { 8 | var viewFilePath = '404'; 9 | var statusCode = 404; 10 | var result = { 11 | status: statusCode 12 | }; 13 | 14 | res.status(result.status); 15 | res.render(viewFilePath, function (err) { 16 | if (err) { return res.json(result, result.status); } 17 | 18 | res.render(viewFilePath); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /_.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /dist/server/components/errors/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Error responses 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports[404] = function pageNotFound(req, res) { 8 | var viewFilePath = '404'; 9 | var statusCode = 404; 10 | var result = { 11 | status: statusCode 12 | }; 13 | 14 | res.status(result.status); 15 | res.render(viewFilePath, function (err) { 16 | if (err) { return res.json(result, result.status); } 17 | 18 | res.render(viewFilePath); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /e2e/main/main.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Main View', function() { 4 | var page; 5 | 6 | beforeEach(function() { 7 | browser.get('/'); 8 | page = require('./main.po'); 9 | }); 10 | 11 | it('should include jumbotron with correct data', function() { 12 | expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); 13 | expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); 14 | expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /_.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /client/components/mongoose-error/mongoose-error.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Removes server error when user updates input 5 | */ 6 | angular.module('meetMeInTheMiddleApp') 7 | .directive('mongooseError', function () { 8 | return { 9 | restrict: 'A', 10 | require: 'ngModel', 11 | link: function(scope, element, attrs, ngModel) { 12 | element.on('keydown', function() { 13 | return ngModel.$setValidity('mongoose', true); 14 | }); 15 | } 16 | }; 17 | }); -------------------------------------------------------------------------------- /client/components/auth/user.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .factory('User', function ($resource) { 5 | return $resource('/api/users/:id/:controller', { 6 | id: '@_id' 7 | }, 8 | { 9 | changePassword: { 10 | method: 'PUT', 11 | params: { 12 | controller:'password' 13 | } 14 | }, 15 | get: { 16 | method: 'GET', 17 | params: { 18 | id:'me' 19 | } 20 | } 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /client/app/admin/admin.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .controller('AdminCtrl', function ($scope, $http, Auth, User) { 5 | 6 | // Use the User $resource to fetch all users 7 | $scope.users = User.query(); 8 | 9 | $scope.delete = function(user) { 10 | User.remove({ id: user._id }); 11 | angular.forEach($scope.users, function(u, i) { 12 | if (u === user) { 13 | $scope.users.splice(i, 1); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /server/auth/twitter/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('twitter', { 11 | failureRedirect: '/signup', 12 | session: false 13 | })) 14 | 15 | .get('/callback', passport.authenticate('twitter', { 16 | failureRedirect: '/signup', 17 | session: false 18 | }), auth.setTokenCookie); 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /dist/server/auth/twitter/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('twitter', { 11 | failureRedirect: '/signup', 12 | session: false 13 | })) 14 | 15 | .get('/callback', passport.authenticate('twitter', { 16 | failureRedirect: '/signup', 17 | session: false 18 | }), auth.setTokenCookie); 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /client/app/admin/admin.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

The delete user and user index api routes are restricted to users with the 'admin' role.

5 | 12 |
-------------------------------------------------------------------------------- /server/api/thing/thing.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var app = require('../../app'); 5 | var request = require('supertest'); 6 | 7 | describe('GET /api/things', function() { 8 | 9 | it('should respond with JSON array', function(done) { 10 | request(app) 11 | .get('/api/things') 12 | .expect(200) 13 | .expect('Content-Type', /json/) 14 | .end(function(err, res) { 15 | if (err) return done(err); 16 | res.body.should.be.instanceof(Array); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /dist/server/api/thing/thing.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var app = require('../../app'); 5 | var request = require('supertest'); 6 | 7 | describe('GET /api/things', function() { 8 | 9 | it('should respond with JSON array', function(done) { 10 | request(app) 11 | .get('/api/things') 12 | .expect(200) 13 | .expect('Content-Type', /json/) 14 | .end(function(err, res) { 15 | if (err) return done(err); 16 | res.body.should.be.instanceof(Array); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /server/auth/facebook/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('facebook', { 11 | scope: ['email', 'user_about_me'], 12 | failureRedirect: '/signup', 13 | session: false 14 | })) 15 | 16 | .get('/callback', passport.authenticate('facebook', { 17 | failureRedirect: '/signup', 18 | session: false 19 | }), auth.setTokenCookie); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /dist/server/auth/facebook/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('facebook', { 11 | scope: ['email', 'user_about_me'], 12 | failureRedirect: '/signup', 13 | session: false 14 | })) 15 | 16 | .get('/callback', passport.authenticate('facebook', { 17 | failureRedirect: '/signup', 18 | session: false 19 | }), auth.setTokenCookie); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /server/api/thing/thing.socket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Broadcast updates to client when the model changes 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var thing = require('./thing.model'); 8 | 9 | exports.register = function(socket) { 10 | thing.schema.post('save', function (doc) { 11 | onSave(socket, doc); 12 | }); 13 | thing.schema.post('remove', function (doc) { 14 | onRemove(socket, doc); 15 | }); 16 | } 17 | 18 | function onSave(socket, doc, cb) { 19 | socket.emit('thing:save', doc); 20 | } 21 | 22 | function onRemove(socket, doc, cb) { 23 | socket.emit('thing:remove', doc); 24 | } -------------------------------------------------------------------------------- /dist/server/api/thing/thing.socket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Broadcast updates to client when the model changes 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var thing = require('./thing.model'); 8 | 9 | exports.register = function(socket) { 10 | thing.schema.post('save', function (doc) { 11 | onSave(socket, doc); 12 | }); 13 | thing.schema.post('remove', function (doc) { 14 | onRemove(socket, doc); 15 | }); 16 | } 17 | 18 | function onSave(socket, doc, cb) { 19 | socket.emit('thing:save', doc); 20 | } 21 | 22 | function onRemove(socket, doc, cb) { 23 | socket.emit('thing:remove', doc); 24 | } -------------------------------------------------------------------------------- /client/components/modal/modal.css: -------------------------------------------------------------------------------- 1 | /*.modal-primary .modal-header, 2 | .modal-info .modal-header, 3 | .modal-success .modal-header, 4 | .modal-warning .modal-header, 5 | .modal-danger .modal-header { 6 | color: #fff; 7 | border-radius: 5px 5px 0 0; 8 | } 9 | .modal-primary .modal-header { 10 | background: #428bca; 11 | } 12 | .modal-info .modal-header { 13 | background: #5bc0de; 14 | } 15 | .modal-success .modal-header { 16 | background: #5cb85c; 17 | } 18 | .modal-warning .modal-header { 19 | background: #f0ad4e; 20 | } 21 | .modal-danger .modal-header { 22 | background: #d9534f; 23 | }*/ -------------------------------------------------------------------------------- /client/components/modal/modal.html: -------------------------------------------------------------------------------- 1 | 5 | 9 | -------------------------------------------------------------------------------- /server/auth/local/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router.post('/', function(req, res, next) { 10 | passport.authenticate('local', function (err, user, info) { 11 | var error = err || info; 12 | if (error) return res.json(401, error); 13 | if (!user) return res.json(404, {message: 'Something went wrong, please try again.'}); 14 | 15 | var token = auth.signToken(user._id, user.role); 16 | res.json({token: token}); 17 | })(req, res, next) 18 | }); 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /dist/server/auth/local/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router.post('/', function(req, res, next) { 10 | passport.authenticate('local', function (err, user, info) { 11 | var error = err || info; 12 | if (error) return res.json(401, error); 13 | if (!user) return res.json(404, {message: 'Something went wrong, please try again.'}); 14 | 15 | var token = auth.signToken(user._id, user.role); 16 | res.json({token: token}); 17 | })(req, res, next) 18 | }); 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /client/app/google-maps/google-maps.css: -------------------------------------------------------------------------------- 1 | .angular-google-map-container { height: 400px; } 2 | 3 | .controls { 4 | margin-top: 16px; 5 | border: 1px solid transparent; 6 | border-radius: 2px 0 0 2px; 7 | box-sizing: border-box; 8 | -moz-box-sizing: border-box; 9 | height: 32px; 10 | outline: none; 11 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); 12 | } 13 | 14 | #pac-input { 15 | background-color: #fff; 16 | font-family: Roboto; 17 | font-size: 15px; 18 | font-weight: 300; 19 | margin-left: 12px; 20 | padding: 0 11px 0 13px; 21 | text-overflow: ellipsis; 22 | width: 400px; 23 | } 24 | 25 | #pac-input:focus { 26 | border-color: #4d90fe; 27 | } -------------------------------------------------------------------------------- /client/components/navbar/navbar.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .controller('NavbarCtrl', function ($scope, $location, Auth) { 5 | $scope.menu = [{ 6 | 'title': 'Home', 7 | 'link': '/' 8 | }]; 9 | 10 | $scope.isCollapsed = true; 11 | $scope.isLoggedIn = Auth.isLoggedIn; 12 | $scope.isAdmin = Auth.isAdmin; 13 | $scope.getCurrentUser = Auth.getCurrentUser; 14 | 15 | $scope.logout = function() { 16 | Auth.logout(); 17 | $location.path('/login'); 18 | }; 19 | 20 | $scope.isActive = function(route) { 21 | return route === $location.path(); 22 | }; 23 | }); -------------------------------------------------------------------------------- /server/auth/google/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('google', { 11 | failureRedirect: '/signup', 12 | scope: [ 13 | 'https://www.googleapis.com/auth/userinfo.profile', 14 | 'https://www.googleapis.com/auth/userinfo.email' 15 | ], 16 | session: false 17 | })) 18 | 19 | .get('/callback', passport.authenticate('google', { 20 | failureRedirect: '/signup', 21 | session: false 22 | }), auth.setTokenCookie); 23 | 24 | module.exports = router; -------------------------------------------------------------------------------- /dist/server/auth/google/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('google', { 11 | failureRedirect: '/signup', 12 | scope: [ 13 | 'https://www.googleapis.com/auth/userinfo.profile', 14 | 'https://www.googleapis.com/auth/userinfo.email' 15 | ], 16 | session: false 17 | })) 18 | 19 | .get('/callback', passport.authenticate('google', { 20 | failureRedirect: '/signup', 21 | session: false 22 | }), auth.setTokenCookie); 23 | 24 | module.exports = router; -------------------------------------------------------------------------------- /_.gitignore: -------------------------------------------------------------------------------- 1 | ### node etc ### 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled Dirs (http://nodejs.org/api/addons.html) 22 | build/ 23 | dist/ 24 | 25 | # Dependency directorys 26 | # Deployed apps should consider commenting these lines out: 27 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 28 | node_modules/ 29 | bower_components/ 30 | -------------------------------------------------------------------------------- /client/app/account/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('login', { 7 | url: '/login', 8 | templateUrl: 'app/account/login/login.html', 9 | controller: 'LoginCtrl' 10 | }) 11 | .state('signup', { 12 | url: '/signup', 13 | templateUrl: 'app/account/signup/signup.html', 14 | controller: 'SignupCtrl' 15 | }) 16 | .state('settings', { 17 | url: '/settings', 18 | templateUrl: 'app/account/settings/settings.html', 19 | controller: 'SettingsCtrl', 20 | authenticate: true 21 | }); 22 | }); -------------------------------------------------------------------------------- /server/api/user/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./user.controller'); 5 | var config = require('../../config/environment'); 6 | var auth = require('../../auth/auth.service'); 7 | 8 | var router = express.Router(); 9 | 10 | router.get('/', auth.hasRole('admin'), controller.index); 11 | router.delete('/:id', auth.hasRole('admin'), controller.destroy); 12 | router.get('/me', auth.isAuthenticated(), controller.me); 13 | router.put('/:id/password', auth.isAuthenticated(), controller.changePassword); 14 | router.get('/:id', auth.isAuthenticated(), controller.show); 15 | router.post('/', controller.create); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /dist/server/api/user/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./user.controller'); 5 | var config = require('../../config/environment'); 6 | var auth = require('../../auth/auth.service'); 7 | 8 | var router = express.Router(); 9 | 10 | router.get('/', auth.hasRole('admin'), controller.index); 11 | router.delete('/:id', auth.hasRole('admin'), controller.destroy); 12 | router.get('/me', auth.isAuthenticated(), controller.me); 13 | router.put('/:id/password', auth.isAuthenticated(), controller.changePassword); 14 | router.get('/:id', auth.isAuthenticated(), controller.show); 15 | router.post('/', controller.create); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /dist/server/config/local.env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use local.env.js for environment variables that grunt will set when the server starts locally. 4 | // Use for your api keys, secrets, etc. This file should not be tracked by git. 5 | // 6 | // You will need to set these on the server you deploy to. 7 | 8 | module.exports = { 9 | DOMAIN: 'http://localhost:9000', 10 | SESSION_SECRET: "meetmeinthemiddle-secret", 11 | 12 | FACEBOOK_ID: 'app-id', 13 | FACEBOOK_SECRET: 'secret', 14 | 15 | TWITTER_ID: 'app-id', 16 | TWITTER_SECRET: 'secret', 17 | 18 | GOOGLE_ID: 'app-id', 19 | GOOGLE_SECRET: 'secret', 20 | 21 | // Control debug level for modules using visionmedia/debug 22 | DEBUG: '' 23 | }; 24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meet-me-in-the-middle", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": ">=1.2.*", 6 | "json3": "~3.3.1", 7 | "es5-shim": "~3.0.1", 8 | "jquery": "~1.11.0", 9 | "bootstrap": "~3.1.1", 10 | "angular-resource": ">=1.2.*", 11 | "angular-cookies": ">=1.2.*", 12 | "angular-sanitize": ">=1.2.*", 13 | "angular-bootstrap": "~0.11.0", 14 | "font-awesome": ">=4.1.0", 15 | "lodash": "~3.5.0", 16 | "angular-ui-router": "~0.2.10", 17 | "angular-google-maps": "~2.0.19", 18 | "angularjs-google-directions": "*" 19 | }, 20 | "devDependencies": { 21 | "angular-mocks": ">=1.2.*", 22 | "angular-scenario": ">=1.2.*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/app/landing/js/parallax.js: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////// // 2 | // PARALLAX EFFECT DISABLED ON MOBILE // 3 | ////////////////////////////////////////////////////// // 4 | if (document.documentElement.clientWidth > 991) { 5 | (function(){ 6 | "use strict"; 7 | 8 | var parallax = document.querySelectorAll(".parallax"), 9 | speed = -0.15; 10 | 11 | window.onscroll = function(){ 12 | [].slice.call(parallax).forEach(function(el,i){ 13 | 14 | var windowYOffset = window.pageYOffset, 15 | elBackgrounPos = "0 " + (windowYOffset * speed) + "px"; 16 | 17 | el.style.backgroundPosition = elBackgrounPos; 18 | 19 | }); 20 | }; 21 | 22 | })(); 23 | } -------------------------------------------------------------------------------- /server/config/environment/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Production specific configuration 4 | // ================================= 5 | module.exports = { 6 | // Server IP 7 | ip: process.env.OPENSHIFT_NODEJS_IP || 8 | process.env.IP || 9 | undefined, 10 | 11 | // Server port 12 | port: process.env.OPENSHIFT_NODEJS_PORT || 13 | process.env.PORT || 14 | 8080, 15 | 16 | // MongoDB connection options 17 | mongo: { 18 | uri: process.env.MONGOLAB_URI || 19 | process.env.MONGOHQ_URL || 20 | process.env.OPENSHIFT_MONGODB_DB_URL+process.env.OPENSHIFT_APP_NAME || 21 | 'mongodb://localhost/meetmeinthemiddle' 22 | } 23 | }; -------------------------------------------------------------------------------- /dist/server/config/environment/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Production specific configuration 4 | // ================================= 5 | module.exports = { 6 | // Server IP 7 | ip: process.env.OPENSHIFT_NODEJS_IP || 8 | process.env.IP || 9 | undefined, 10 | 11 | // Server port 12 | port: process.env.OPENSHIFT_NODEJS_PORT || 13 | process.env.PORT || 14 | 8080, 15 | 16 | // MongoDB connection options 17 | mongo: { 18 | uri: process.env.MONGOLAB_URI || 19 | process.env.MONGOHQ_URL || 20 | process.env.OPENSHIFT_MONGODB_DB_URL+process.env.OPENSHIFT_APP_NAME || 21 | 'mongodb://localhost/meetmeinthemiddle' 22 | } 23 | }; -------------------------------------------------------------------------------- /server/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var config = require('../config/environment'); 6 | var User = require('../api/user/user.model'); 7 | 8 | // Passport Configuration 9 | require('./local/passport').setup(User, config); 10 | require('./facebook/passport').setup(User, config); 11 | require('./google/passport').setup(User, config); 12 | require('./twitter/passport').setup(User, config); 13 | 14 | var router = express.Router(); 15 | 16 | router.use('/local', require('./local')); 17 | router.use('/facebook', require('./facebook')); 18 | router.use('/twitter', require('./twitter')); 19 | router.use('/google', require('./google')); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /dist/server/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var config = require('../config/environment'); 6 | var User = require('../api/user/user.model'); 7 | 8 | // Passport Configuration 9 | require('./local/passport').setup(User, config); 10 | require('./facebook/passport').setup(User, config); 11 | require('./google/passport').setup(User, config); 12 | require('./twitter/passport').setup(User, config); 13 | 14 | var router = express.Router(); 15 | 16 | router.use('/local', require('./local')); 17 | router.use('/facebook', require('./facebook')); 18 | router.use('/twitter', require('./twitter')); 19 | router.use('/google', require('./google')); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /server/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application routes 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var errors = require('./components/errors'); 8 | 9 | module.exports = function(app) { 10 | 11 | // Insert routes below 12 | app.use('/api/things', require('./api/thing')); 13 | app.use('/api/users', require('./api/user')); 14 | 15 | app.use('/auth', require('./auth')); 16 | 17 | // All undefined asset or api routes should return a 404 18 | app.route('/:url(api|auth|components|app|bower_components|assets)/*') 19 | .get(errors[404]); 20 | 21 | // All other routes should redirect to the index.html 22 | app.route('/*') 23 | .get(function(req, res) { 24 | res.sendfile(app.get('appPath') + '/index.html'); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /dist/server/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application routes 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var errors = require('./components/errors'); 8 | 9 | module.exports = function(app) { 10 | 11 | // Insert routes below 12 | app.use('/api/things', require('./api/thing')); 13 | app.use('/api/users', require('./api/user')); 14 | 15 | app.use('/auth', require('./auth')); 16 | 17 | // All undefined asset or api routes should return a 404 18 | app.route('/:url(api|auth|components|app|bower_components|assets)/*') 19 | .get(errors[404]); 20 | 21 | // All other routes should redirect to the index.html 22 | app.route('/*') 23 | .get(function(req, res) { 24 | res.sendfile(app.get('appPath') + '/index.html'); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /server/config/local.env.sample.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use local.env.js for environment variables that grunt will set when the server starts locally. 4 | // Use for your api keys, secrets, etc. This file should not be tracked by git. 5 | // 6 | // You will need to set these on the server you deploy to. 7 | 8 | module.exports = { 9 | DOMAIN: 'http://localhost:9000', 10 | SESSION_SECRET: 'meetmeinthemiddle-secret', 11 | 12 | FACEBOOK_ID: 'app-id', 13 | FACEBOOK_SECRET: 'secret', 14 | 15 | TWITTER_ID: 'app-id', 16 | TWITTER_SECRET: 'secret', 17 | 18 | GOOGLE_ID: 'app-id', 19 | GOOGLE_SECRET: 'secret', 20 | 21 | // Control debug level for modules using visionmedia/debug 22 | DEBUG: '' 23 | }; 24 | -------------------------------------------------------------------------------- /client/app/account/settings/settings.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .controller('SettingsCtrl', function ($scope, User, Auth) { 5 | $scope.errors = {}; 6 | 7 | $scope.changePassword = function(form) { 8 | $scope.submitted = true; 9 | if(form.$valid) { 10 | Auth.changePassword( $scope.user.oldPassword, $scope.user.newPassword ) 11 | .then( function() { 12 | $scope.message = 'Password successfully changed.'; 13 | }) 14 | .catch( function() { 15 | form.password.$setValidity('mongoose', false); 16 | $scope.errors.other = 'Incorrect password'; 17 | $scope.message = ''; 18 | }); 19 | } 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /dist/server/config/local.env.sample.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use local.env.js for environment variables that grunt will set when the server starts locally. 4 | // Use for your api keys, secrets, etc. This file should not be tracked by git. 5 | // 6 | // You will need to set these on the server you deploy to. 7 | 8 | module.exports = { 9 | DOMAIN: 'http://localhost:9000', 10 | SESSION_SECRET: 'meetmeinthemiddle-secret', 11 | 12 | FACEBOOK_ID: 'app-id', 13 | FACEBOOK_SECRET: 'secret', 14 | 15 | TWITTER_ID: 'app-id', 16 | TWITTER_SECRET: 'secret', 17 | 18 | GOOGLE_ID: 'app-id', 19 | GOOGLE_SECRET: 'secret', 20 | 21 | // Control debug level for modules using visionmedia/debug 22 | DEBUG: '' 23 | }; 24 | -------------------------------------------------------------------------------- /client/app/main/main.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .controller('MainCtrl', function ($scope, $http) { //Ko: socket.io was made injected at /client/app/app.js line 6 5 | $scope.awesomeThings = []; 6 | 7 | $http.get('/api/things').success(function(awesomeThings) { 8 | $scope.awesomeThings = awesomeThings; 9 | }); 10 | 11 | $scope.addThing = function() { 12 | if($scope.newThing === '') { 13 | return; 14 | } 15 | $http.post('/api/things', { name: $scope.newThing }); 16 | $scope.newThing = ''; 17 | }; 18 | 19 | $scope.deleteThing = function(thing) { 20 | $http.delete('/api/things/' + thing._id); 21 | }; 22 | 23 | $scope.$on('$destroy', function () { 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /client/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "jQuery": true, 23 | "angular": true, 24 | "console": true, 25 | "$": true, 26 | "_": true, 27 | "moment": true, 28 | "describe": true, 29 | "beforeEach": true, 30 | "module": true, 31 | "inject": true, 32 | "it": true, 33 | "expect": true, 34 | "browser": true, 35 | "element": true, 36 | "by": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/app/account/login/login.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .controller('LoginCtrl', function ($scope, Auth, $location, $window) { 5 | $scope.user = {}; 6 | $scope.errors = {}; 7 | 8 | $scope.login = function(form) { 9 | $scope.submitted = true; 10 | 11 | if(form.$valid) { 12 | Auth.login({ 13 | email: $scope.user.email, 14 | password: $scope.user.password 15 | }) 16 | .then( function() { 17 | // Logged in, redirect to home 18 | $location.path('/'); 19 | }) 20 | .catch( function(err) { 21 | $scope.errors.other = err.message; 22 | }); 23 | } 24 | }; 25 | 26 | $scope.loginOauth = function(provider) { 27 | $window.location.href = '/auth/' + provider; 28 | }; 29 | }); 30 | -------------------------------------------------------------------------------- /server/auth/local/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var LocalStrategy = require('passport-local').Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new LocalStrategy({ 6 | usernameField: 'email', 7 | passwordField: 'password' // this is the virtual field on the model 8 | }, 9 | function(email, password, done) { 10 | User.findOne({ 11 | email: email.toLowerCase() 12 | }, function(err, user) { 13 | if (err) return done(err); 14 | 15 | if (!user) { 16 | return done(null, false, { message: 'This email is not registered.' }); 17 | } 18 | if (!user.authenticate(password)) { 19 | return done(null, false, { message: 'This password is not correct.' }); 20 | } 21 | return done(null, user); 22 | }); 23 | } 24 | )); 25 | }; -------------------------------------------------------------------------------- /dist/server/auth/local/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var LocalStrategy = require('passport-local').Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new LocalStrategy({ 6 | usernameField: 'email', 7 | passwordField: 'password' // this is the virtual field on the model 8 | }, 9 | function(email, password, done) { 10 | User.findOne({ 11 | email: email.toLowerCase() 12 | }, function(err, user) { 13 | if (err) return done(err); 14 | 15 | if (!user) { 16 | return done(null, false, { message: 'This email is not registered.' }); 17 | } 18 | if (!user.authenticate(password)) { 19 | return done(null, false, { message: 'This password is not correct.' }); 20 | } 21 | return done(null, user); 22 | }); 23 | } 24 | )); 25 | }; -------------------------------------------------------------------------------- /client/app/main/main.css: -------------------------------------------------------------------------------- 1 | /*.thing-form { 2 | margin: 20px 0; 3 | } 4 | 5 | #banner { 6 | border-bottom: none; 7 | margin-top: -20px; 8 | } 9 | 10 | #banner h1 { 11 | font-size: 60px; 12 | line-height: 1; 13 | letter-spacing: -1px; 14 | } 15 | 16 | .hero-unit { 17 | position: relative; 18 | padding: 30px 15px; 19 | color: #F5F5F5; 20 | text-align: center; 21 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); 22 | background: #4393B9; 23 | } 24 | 25 | .footer { 26 | text-align: center; 27 | padding: 30px 0; 28 | margin-top: 70px; 29 | border-top: 1px solid #E5E5E5; 30 | }*/ 31 | 32 | /* 33 | Navbar style 34 | */ 35 | .inButton { 36 | display: block; 37 | height: 100%; 38 | } 39 | 40 | .carousel-team { 41 | max-width: 800px; 42 | margin: 0 auto; 43 | } 44 | 45 | .owl-stage { 46 | margin: 0 auto; 47 | 48 | } -------------------------------------------------------------------------------- /client/app/main/main.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('meetMeInTheMiddleApp')); 7 | beforeEach(module('socketMock')); 8 | 9 | var MainCtrl, 10 | scope, 11 | $httpBackend; 12 | 13 | // Initialize the controller and a mock scope 14 | beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) { 15 | $httpBackend = _$httpBackend_; 16 | $httpBackend.expectGET('/api/things') 17 | .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']); 18 | 19 | scope = $rootScope.$new(); 20 | MainCtrl = $controller('MainCtrl', { 21 | $scope: scope 22 | }); 23 | })); 24 | 25 | it('should attach a list of things to the scope', function () { 26 | $httpBackend.flush(); 27 | expect(scope.awesomeThings.length).toBe(4); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /server/auth/twitter/passport.js: -------------------------------------------------------------------------------- 1 | exports.setup = function (User, config) { 2 | var passport = require('passport'); 3 | var TwitterStrategy = require('passport-twitter').Strategy; 4 | 5 | passport.use(new TwitterStrategy({ 6 | consumerKey: config.twitter.clientID, 7 | consumerSecret: config.twitter.clientSecret, 8 | callbackURL: config.twitter.callbackURL 9 | }, 10 | function(token, tokenSecret, profile, done) { 11 | User.findOne({ 12 | 'twitter.id_str': profile.id 13 | }, function(err, user) { 14 | if (err) { 15 | return done(err); 16 | } 17 | if (!user) { 18 | user = new User({ 19 | name: profile.displayName, 20 | username: profile.username, 21 | role: 'user', 22 | provider: 'twitter', 23 | twitter: profile._json 24 | }); 25 | user.save(function(err) { 26 | if (err) return done(err); 27 | return done(err, user); 28 | }); 29 | } else { 30 | return done(err, user); 31 | } 32 | }); 33 | } 34 | )); 35 | }; -------------------------------------------------------------------------------- /dist/server/auth/twitter/passport.js: -------------------------------------------------------------------------------- 1 | exports.setup = function (User, config) { 2 | var passport = require('passport'); 3 | var TwitterStrategy = require('passport-twitter').Strategy; 4 | 5 | passport.use(new TwitterStrategy({ 6 | consumerKey: config.twitter.clientID, 7 | consumerSecret: config.twitter.clientSecret, 8 | callbackURL: config.twitter.callbackURL 9 | }, 10 | function(token, tokenSecret, profile, done) { 11 | User.findOne({ 12 | 'twitter.id_str': profile.id 13 | }, function(err, user) { 14 | if (err) { 15 | return done(err); 16 | } 17 | if (!user) { 18 | user = new User({ 19 | name: profile.displayName, 20 | username: profile.username, 21 | role: 'user', 22 | provider: 'twitter', 23 | twitter: profile._json 24 | }); 25 | user.save(function(err) { 26 | if (err) return done(err); 27 | return done(err, user); 28 | }); 29 | } else { 30 | return done(err, user); 31 | } 32 | }); 33 | } 34 | )); 35 | }; -------------------------------------------------------------------------------- /server/auth/google/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new GoogleStrategy({ 6 | clientID: config.google.clientID, 7 | clientSecret: config.google.clientSecret, 8 | callbackURL: config.google.callbackURL 9 | }, 10 | function(accessToken, refreshToken, profile, done) { 11 | User.findOne({ 12 | 'google.id': profile.id 13 | }, function(err, user) { 14 | if (!user) { 15 | user = new User({ 16 | name: profile.displayName, 17 | email: profile.emails[0].value, 18 | role: 'user', 19 | username: profile.username, 20 | provider: 'google', 21 | google: profile._json 22 | }); 23 | user.save(function(err) { 24 | if (err) done(err); 25 | return done(err, user); 26 | }); 27 | } else { 28 | return done(err, user); 29 | } 30 | }); 31 | } 32 | )); 33 | }; 34 | -------------------------------------------------------------------------------- /dist/server/auth/google/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new GoogleStrategy({ 6 | clientID: config.google.clientID, 7 | clientSecret: config.google.clientSecret, 8 | callbackURL: config.google.callbackURL 9 | }, 10 | function(accessToken, refreshToken, profile, done) { 11 | User.findOne({ 12 | 'google.id': profile.id 13 | }, function(err, user) { 14 | if (!user) { 15 | user = new User({ 16 | name: profile.displayName, 17 | email: profile.emails[0].value, 18 | role: 'user', 19 | username: profile.username, 20 | provider: 'google', 21 | google: profile._json 22 | }); 23 | user.save(function(err) { 24 | if (err) done(err); 25 | return done(err, user); 26 | }); 27 | } else { 28 | return done(err, user); 29 | } 30 | }); 31 | } 32 | )); 33 | }; 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meet Me In The Middle 2 | 3 | > An application that locates the midpoint between two users using the Google Maps API. 4 | 5 | ## Team 6 | 7 | - __Product Owner__: Ko Seunghoon 8 | - __Scrum Master__: Jordan Genung 9 | - __Development Team Members__: Jonah Nisenson, Rioa Mattsson 10 | 11 | ## Table of Contents 12 | 13 | 1. [Usage](#Usage) 14 | 1. [Requirements](#requirements) 15 | 1. [Development](#development) 16 | 1. [Installing Dependencies](#installing-dependencies) 17 | 1. [Tasks](#tasks) 18 | 1. [Team](#team) 19 | 1. [Contributing](#contributing) 20 | 21 | ## Usage 22 | 23 | > Some usage instructions 24 | 25 | ## Requirements 26 | 27 | - Node 0.10.x 28 | - Redis 2.6.x 29 | - Postgresql 9.1.x 30 | - etc 31 | - etc 32 | 33 | ## Development 34 | 35 | ### Installing Dependencies 36 | 37 | From within the root directory: 38 | 39 | ```sh 40 | sudo npm install -g bower 41 | npm install 42 | bower install 43 | ``` 44 | 45 | ### Roadmap 46 | 47 | View the project roadmap [here](LINK_TO_PROJECT_ISSUES) 48 | 49 | 50 | ## Contributing 51 | 52 | See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. 53 | -------------------------------------------------------------------------------- /dist/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application file 3 | */ 4 | 5 | 'use strict'; 6 | 7 | // Set default node environment to development 8 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 9 | 10 | var express = require('express'); 11 | var mongoose = require('mongoose'); 12 | var config = require('./config/environment'); 13 | 14 | // Connect to database 15 | mongoose.connect(config.mongo.uri, config.mongo.options); 16 | 17 | // Populate DB with sample data 18 | if(config.seedDB) { require('./config/seed'); } 19 | 20 | // Setup server 21 | var app = express(); 22 | var server = require('http').createServer(app); 23 | var socketio = require('socket.io')(server, { 24 | serveClient: (config.env === 'production') ? false : true, 25 | path: '/socket.io-client' 26 | }); 27 | require('./config/socketio')(socketio); 28 | require('./config/express')(app); 29 | require('./routes')(app); 30 | 31 | // Start server 32 | server.listen(config.port, config.ip, function () { 33 | console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); 34 | }); 35 | 36 | // Expose app 37 | //exports = module.exports = app; 38 | module.exports = app; 39 | -------------------------------------------------------------------------------- /server/auth/facebook/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var FacebookStrategy = require('passport-facebook').Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new FacebookStrategy({ 6 | clientID: config.facebook.clientID, 7 | clientSecret: config.facebook.clientSecret, 8 | callbackURL: config.facebook.callbackURL 9 | }, 10 | function(accessToken, refreshToken, profile, done) { 11 | User.findOne({ 12 | 'facebook.id': profile.id 13 | }, 14 | function(err, user) { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (!user) { 19 | user = new User({ 20 | name: profile.displayName, 21 | email: profile.emails[0].value, 22 | role: 'user', 23 | username: profile.username, 24 | provider: 'facebook', 25 | facebook: profile._json 26 | }); 27 | user.save(function(err) { 28 | if (err) done(err); 29 | return done(err, user); 30 | }); 31 | } else { 32 | return done(err, user); 33 | } 34 | }) 35 | } 36 | )); 37 | }; -------------------------------------------------------------------------------- /dist/server/auth/facebook/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var FacebookStrategy = require('passport-facebook').Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new FacebookStrategy({ 6 | clientID: config.facebook.clientID, 7 | clientSecret: config.facebook.clientSecret, 8 | callbackURL: config.facebook.callbackURL 9 | }, 10 | function(accessToken, refreshToken, profile, done) { 11 | User.findOne({ 12 | 'facebook.id': profile.id 13 | }, 14 | function(err, user) { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (!user) { 19 | user = new User({ 20 | name: profile.displayName, 21 | email: profile.emails[0].value, 22 | role: 'user', 23 | username: profile.username, 24 | provider: 'facebook', 25 | facebook: profile._json 26 | }); 27 | user.save(function(err) { 28 | if (err) done(err); 29 | return done(err, user); 30 | }); 31 | } else { 32 | return done(err, user); 33 | } 34 | }) 35 | } 36 | )); 37 | }; -------------------------------------------------------------------------------- /client/app/account/signup/signup.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .controller('SignupCtrl', function ($scope, Auth, $location, $window) { 5 | $scope.user = {}; 6 | $scope.errors = {}; 7 | 8 | $scope.register = function(form) { 9 | $scope.submitted = true; 10 | 11 | if(form.$valid) { 12 | Auth.createUser({ 13 | name: $scope.user.name, 14 | email: $scope.user.email, 15 | password: $scope.user.password 16 | }) 17 | .then( function() { 18 | // Account created, redirect to home 19 | $location.path('/'); 20 | }) 21 | .catch( function(err) { 22 | err = err.data; 23 | $scope.errors = {}; 24 | 25 | // Update validity of form fields that match the mongoose errors 26 | angular.forEach(err.errors, function(error, field) { 27 | form[field].$setValidity('mongoose', false); 28 | $scope.errors[field] = error.message; 29 | }); 30 | }); 31 | } 32 | }; 33 | 34 | $scope.loginOauth = function(provider) { 35 | $window.location.href = '/auth/' + provider; 36 | }; 37 | }); 38 | -------------------------------------------------------------------------------- /client/app/landing/js/jQuery.headroom.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | if(!$) { 4 | return; 5 | } 6 | 7 | //////////// 8 | // Plugin // 9 | //////////// 10 | 11 | $.fn.headroom = function(option) { 12 | return this.each(function() { 13 | var $this = $(this), 14 | data = $this.data('headroom'), 15 | options = typeof option === 'object' && option; 16 | 17 | options = $.extend(true, {}, Headroom.options, options); 18 | 19 | if (!data) { 20 | data = new Headroom(this, options); 21 | data.init(); 22 | $this.data('headroom', data); 23 | } 24 | if (typeof option === 'string') { 25 | data[option](); 26 | } 27 | }); 28 | }; 29 | 30 | ////////////// 31 | // Data API // 32 | ////////////// 33 | 34 | $('[data-headroom]').each(function() { 35 | var $this = $(this); 36 | $this.headroom($this.data()); 37 | }); 38 | 39 | }(window.Zepto || window.jQuery)); 40 | 41 | 42 | //navbar configuration below 43 | $(".navbar-fixed-top").headroom({ 44 | "offset": 205, 45 | "tolerance": 5, 46 | "classes": { 47 | "pinned": "swingInX", 48 | "unpinned": "swingOutX" 49 | } 50 | }); 51 | // to destroy 52 | $(".navbar-fixed-top").headroom("destroy"); -------------------------------------------------------------------------------- /client/app/landing/js/jquery.inview.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(){"use strict";var e,i,n={height:a.innerHeight,width:a.innerWidth};return n.height||(e=r.compatMode,(e||!t.support.boxModel)&&(i="CSS1Compat"===e?f:r.body,n={height:i.clientHeight,width:i.clientWidth})),n}function i(){return{top:a.pageYOffset||f.scrollTop||r.body.scrollTop,left:a.pageXOffset||f.scrollLeft||r.body.scrollLeft}}function n(){var n,l=t(),r=0;if(t.each(d,function(t,e){var i=e.data.selector,n=e.$element;l=l.add(i?n.find(i):n)}),n=l.length)for(o=o||e(),h=h||i();n>r;r++)if(t.contains(f,l[r])){var a,c,p,s=t(l[r]),u={height:s.height(),width:s.width()},g=s.offset(),v=s.data("inview");if(!h||!o)return;g.top+u.height>h.top&&g.toph.left&&g.leftg.left?"right":h.left+o.widthg.top?"bottom":h.top+o.height 2 | 3 |
4 |
5 |
6 |

Change Password

7 |
8 |
9 |
10 | 11 |
12 | 13 | 14 | 16 |

17 | {{ errors.other }} 18 |

19 |
20 | 21 |
22 | 23 | 24 | 27 |

29 | Password must be at least 3 characters. 30 |

31 |
32 | 33 |

{{ message }}

34 | 35 | 36 |
37 |
38 |
39 |
-------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angular-fullstack": { 3 | "insertRoutes": true, 4 | "registerRoutesFile": "server/routes.js", 5 | "routesNeedle": "// Insert routes below", 6 | "routesBase": "/api/", 7 | "pluralizeRoutes": true, 8 | "insertSockets": true, 9 | "registerSocketsFile": "server/config/socketio.js", 10 | "socketsNeedle": "// Insert sockets below", 11 | "filters": { 12 | "js": true, 13 | "html": true, 14 | "css": true, 15 | "uirouter": true, 16 | "bootstrap": true, 17 | "uibootstrap": true, 18 | "socketio": true, 19 | "mongoose": true, 20 | "auth": true, 21 | "oauth": true, 22 | "googleAuth": true, 23 | "facebookAuth": true, 24 | "twitterAuth": true 25 | } 26 | }, 27 | "generator-ng-component": { 28 | "routeDirectory": "client/app/", 29 | "directiveDirectory": "client/app/", 30 | "filterDirectory": "client/app/", 31 | "serviceDirectory": "client/app/", 32 | "basePath": "client", 33 | "moduleName": "", 34 | "filters": [ 35 | "uirouter" 36 | ], 37 | "extensions": [ 38 | "js", 39 | "html", 40 | "css" 41 | ], 42 | "directiveSimpleTemplates": "", 43 | "directiveComplexTemplates": "", 44 | "filterTemplates": "", 45 | "serviceTemplates": "", 46 | "factoryTemplates": "", 47 | "controllerTemplates": "", 48 | "decoratorTemplates": "", 49 | "providerTemplates": "", 50 | "routeTemplates": "" 51 | } 52 | } -------------------------------------------------------------------------------- /client/app/app.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Bootstrap Fonts 4 | */ 5 | 6 | /*@font-face { 7 | font-family: 'Glyphicons Halflings'; 8 | src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot'); 9 | src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), 10 | url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), 11 | url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), 12 | url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); 13 | } 14 | */ 15 | /** 16 | *Font Awesome Fonts 17 | */ 18 | 19 | /*@font-face { 20 | font-family: 'FontAwesome'; 21 | src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0'); 22 | src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), 23 | url('../bower_components/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), 24 | url('../bower_components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), 25 | url('../bower_components/font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg'); 26 | font-weight: normal; 27 | font-style: normal; 28 | } 29 | */ 30 | /** 31 | * App-wide Styles 32 | */ 33 | 34 | /*.browsehappy { 35 | margin: 0.2em 0; 36 | background: #ccc; 37 | color: #000; 38 | padding: 0.2em 0; 39 | } 40 | */ -------------------------------------------------------------------------------- /server/api/user/user.model.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var app = require('../../app'); 5 | var User = require('./user.model'); 6 | 7 | var user = new User({ 8 | provider: 'local', 9 | name: 'Fake User', 10 | email: 'test@test.com', 11 | password: 'password' 12 | }); 13 | 14 | describe('User Model', function() { 15 | before(function(done) { 16 | // Clear users before testing 17 | User.remove().exec().then(function() { 18 | done(); 19 | }); 20 | }); 21 | 22 | afterEach(function(done) { 23 | User.remove().exec().then(function() { 24 | done(); 25 | }); 26 | }); 27 | 28 | it('should begin with no users', function(done) { 29 | User.find({}, function(err, users) { 30 | users.should.have.length(0); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('should fail when saving a duplicate user', function(done) { 36 | user.save(function() { 37 | var userDup = new User(user); 38 | userDup.save(function(err) { 39 | should.exist(err); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | it('should fail when saving without an email', function(done) { 46 | user.email = ''; 47 | user.save(function(err) { 48 | should.exist(err); 49 | done(); 50 | }); 51 | }); 52 | 53 | it("should authenticate user if password is valid", function() { 54 | return user.authenticate('password').should.be.true; 55 | }); 56 | 57 | it("should not authenticate user if password is invalid", function() { 58 | return user.authenticate('blah').should.not.be.true; 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /dist/server/api/user/user.model.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var app = require('../../app'); 5 | var User = require('./user.model'); 6 | 7 | var user = new User({ 8 | provider: 'local', 9 | name: 'Fake User', 10 | email: 'test@test.com', 11 | password: 'password' 12 | }); 13 | 14 | describe('User Model', function() { 15 | before(function(done) { 16 | // Clear users before testing 17 | User.remove().exec().then(function() { 18 | done(); 19 | }); 20 | }); 21 | 22 | afterEach(function(done) { 23 | User.remove().exec().then(function() { 24 | done(); 25 | }); 26 | }); 27 | 28 | it('should begin with no users', function(done) { 29 | User.find({}, function(err, users) { 30 | users.should.have.length(0); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('should fail when saving a duplicate user', function(done) { 36 | user.save(function() { 37 | var userDup = new User(user); 38 | userDup.save(function(err) { 39 | should.exist(err); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | it('should fail when saving without an email', function(done) { 46 | user.email = ''; 47 | user.save(function(err) { 48 | should.exist(err); 49 | done(); 50 | }); 51 | }); 52 | 53 | it("should authenticate user if password is valid", function() { 54 | return user.authenticate('password').should.be.true; 55 | }); 56 | 57 | it("should not authenticate user if password is invalid", function() { 58 | return user.authenticate('blah').should.not.be.true; 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /client/app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp', [ 4 | 'ngCookies', 5 | 'ngResource', 6 | 'ngSanitize', 7 | 'ui.router', 8 | 'ui.bootstrap', 9 | 'uiGmapgoogle-maps' 10 | ]) 11 | .config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) { 12 | $urlRouterProvider 13 | .otherwise('/'); 14 | 15 | $locationProvider.html5Mode(true); 16 | $httpProvider.interceptors.push('authInterceptor'); 17 | }) 18 | 19 | .factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) { 20 | return { 21 | // Add authorization token to headers 22 | request: function (config) { 23 | config.headers = config.headers || {}; 24 | if ($cookieStore.get('token')) { 25 | config.headers.Authorization = 'Bearer ' + $cookieStore.get('token'); 26 | } 27 | return config; 28 | }, 29 | 30 | // Intercept 401s and redirect you to login 31 | responseError: function(response) { 32 | if(response.status === 401) { 33 | $location.path('/login'); 34 | // remove any stale tokens 35 | $cookieStore.remove('token'); 36 | return $q.reject(response); 37 | } 38 | else { 39 | return $q.reject(response); 40 | } 41 | } 42 | }; 43 | }) 44 | .run(function ($rootScope, $location, Auth) { 45 | // Redirect to login if route requires auth and you're not logged in 46 | $rootScope.$on('$stateChangeStart', function (event, next) { 47 | Auth.isLoggedInAsync(function(loggedIn) { 48 | if (next.authenticate && !loggedIn) { 49 | $location.path('/login'); 50 | } 51 | }); 52 | }); 53 | }); -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration 2 | // https://github.com/angular/protractor/blob/master/referenceConf.js 3 | 4 | 'use strict'; 5 | 6 | exports.config = { 7 | // The timeout for each script run on the browser. This should be longer 8 | // than the maximum time your application needs to stabilize between tasks. 9 | allScriptsTimeout: 110000, 10 | 11 | // A base URL for your application under test. Calls to protractor.get() 12 | // with relative paths will be prepended with this. 13 | baseUrl: 'http://localhost:' + (process.env.PORT || '9000'), 14 | 15 | // If true, only chromedriver will be started, not a standalone selenium. 16 | // Tests for browsers other than chrome will not run. 17 | chromeOnly: true, 18 | 19 | // list of files / patterns to load in the browser 20 | specs: [ 21 | 'e2e/**/*.spec.js' 22 | ], 23 | 24 | // Patterns to exclude. 25 | exclude: [], 26 | 27 | // ----- Capabilities to be passed to the webdriver instance ---- 28 | // 29 | // For a full list of available capabilities, see 30 | // https://code.google.com/p/selenium/wiki/DesiredCapabilities 31 | // and 32 | // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js 33 | capabilities: { 34 | 'browserName': 'chrome' 35 | }, 36 | 37 | // ----- The test framework ----- 38 | // 39 | // Jasmine and Cucumber are fully supported as a test and assertion framework. 40 | // Mocha has limited beta support. You will need to include your own 41 | // assertion framework if working with mocha. 42 | framework: 'jasmine', 43 | 44 | // ----- Options to be passed to minijasminenode ----- 45 | // 46 | // See the full list at https://github.com/juliemr/minijasminenode 47 | jasmineNodeOpts: { 48 | defaultTimeoutInterval: 30000 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /server/config/seed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Populate DB with sample data on server start 3 | * to disable, edit config/environment/index.js, and set `seedDB: false` 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var Thing = require('../api/thing/thing.model'); 9 | var User = require('../api/user/user.model'); 10 | 11 | Thing.find({}).remove(function() { 12 | Thing.create({ 13 | name : 'Development Tools', 14 | info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.' 15 | }, { 16 | name : 'Server and Client integration', 17 | info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.' 18 | }, { 19 | name : 'Smart Build System', 20 | info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html' 21 | }, { 22 | name : 'Modular Structure', 23 | info : 'Best practice client and server structures allow for more code reusability and maximum scalability' 24 | }, { 25 | name : 'Optimized Build', 26 | info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.' 27 | },{ 28 | name : 'Deployment Ready', 29 | info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators' 30 | }); 31 | }); 32 | 33 | User.find({}).remove(function() { 34 | User.create({ 35 | provider: 'local', 36 | name: 'Test User', 37 | email: 'test@test.com', 38 | password: 'test' 39 | }, { 40 | provider: 'local', 41 | role: 'admin', 42 | name: 'Admin', 43 | email: 'admin@admin.com', 44 | password: 'admin' 45 | }, function() { 46 | console.log('finished populating users'); 47 | } 48 | ); 49 | }); -------------------------------------------------------------------------------- /dist/server/config/seed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Populate DB with sample data on server start 3 | * to disable, edit config/environment/index.js, and set `seedDB: false` 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var Thing = require('../api/thing/thing.model'); 9 | var User = require('../api/user/user.model'); 10 | 11 | Thing.find({}).remove(function() { 12 | Thing.create({ 13 | name : 'Development Tools', 14 | info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.' 15 | }, { 16 | name : 'Server and Client integration', 17 | info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.' 18 | }, { 19 | name : 'Smart Build System', 20 | info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html' 21 | }, { 22 | name : 'Modular Structure', 23 | info : 'Best practice client and server structures allow for more code reusability and maximum scalability' 24 | }, { 25 | name : 'Optimized Build', 26 | info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.' 27 | },{ 28 | name : 'Deployment Ready', 29 | info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators' 30 | }); 31 | }); 32 | 33 | User.find({}).remove(function() { 34 | User.create({ 35 | provider: 'local', 36 | name: 'Test User', 37 | email: 'test@test.com', 38 | password: 'test' 39 | }, { 40 | provider: 'local', 41 | role: 'admin', 42 | name: 'Admin', 43 | email: 'admin@admin.com', 44 | password: 'admin' 45 | }, function() { 46 | console.log('finished populating users'); 47 | } 48 | ); 49 | }); -------------------------------------------------------------------------------- /dist/server/config/socketio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Socket.io configuration 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var config = require('./environment'); 8 | 9 | // When the user disconnects.. perform this 10 | function onDisconnect(socket) { 11 | } 12 | 13 | // When the user connects.. perform this 14 | function onConnect(socket) { 15 | // When the client emits 'info', this listens and executes 16 | socket.on('info', function (data) { 17 | console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2)); 18 | }); 19 | 20 | // Insert sockets below 21 | require('../api/thing/thing.socket').register(socket); 22 | } 23 | 24 | module.exports = function (socketio) { 25 | // socket.io (v1.x.x) is powered by debug. 26 | // In order to see all the debug output, set DEBUG (in server/config/local.env.js) to including the desired scope. 27 | // 28 | // ex: DEBUG: "http*,socket.io:socket" 29 | 30 | // We can authenticate socket.io users and access their token through socket.handshake.decoded_token 31 | // 32 | // 1. You will need to send the token in `client/components/socket/socket.service.js` 33 | // 34 | // 2. Require authentication here: 35 | // socketio.use(require('socketio-jwt').authorize({ 36 | // secret: config.secrets.session, 37 | // handshake: true 38 | // })); 39 | 40 | socketio.on('connection', function (socket) { 41 | socket.address = socket.handshake.address !== null ? 42 | socket.handshake.address.address + ':' + socket.handshake.address.port : 43 | process.env.DOMAIN; 44 | 45 | socket.connectedAt = new Date(); 46 | 47 | // Call onDisconnect. 48 | socket.on('disconnect', function () { 49 | onDisconnect(socket); 50 | console.info('[%s] DISCONNECTED', socket.address); 51 | }); 52 | 53 | // Call onConnect. 54 | onConnect(socket); 55 | console.info('[%s] CONNECTED', socket.address); 56 | }); 57 | }; -------------------------------------------------------------------------------- /server/config/environment/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var _ = require('lodash'); 5 | 6 | function requiredProcessEnv(name) { 7 | if(!process.env[name]) { 8 | throw new Error('You must set the ' + name + ' environment variable'); 9 | } 10 | return process.env[name]; 11 | } 12 | 13 | // All configurations will extend these options 14 | // ============================================ 15 | var all = { 16 | env: process.env.NODE_ENV, 17 | 18 | // Root path of server 19 | root: path.normalize(__dirname + '/../../..'), 20 | 21 | // Server port 22 | port: process.env.PORT || 9000, 23 | 24 | // Should we populate the DB with sample data? 25 | seedDB: false, 26 | 27 | // Secret for session, you will want to change this and make it an environment variable 28 | secrets: { 29 | session: 'meet-me-in-the-middle-secret' 30 | }, 31 | 32 | // List of user roles 33 | userRoles: ['guest', 'user', 'admin'], 34 | 35 | // MongoDB connection options 36 | mongo: { 37 | options: { 38 | db: { 39 | safe: true 40 | } 41 | } 42 | }, 43 | 44 | facebook: { 45 | clientID: process.env.FACEBOOK_ID || 'id', 46 | clientSecret: process.env.FACEBOOK_SECRET || 'secret', 47 | callbackURL: (process.env.DOMAIN || '') + '/auth/facebook/callback' 48 | }, 49 | 50 | twitter: { 51 | clientID: process.env.TWITTER_ID || 'id', 52 | clientSecret: process.env.TWITTER_SECRET || 'secret', 53 | callbackURL: (process.env.DOMAIN || '') + '/auth/twitter/callback' 54 | }, 55 | 56 | google: { 57 | clientID: process.env.GOOGLE_ID || 'id', 58 | clientSecret: process.env.GOOGLE_SECRET || 'secret', 59 | callbackURL: (process.env.DOMAIN || '') + '/auth/google/callback' 60 | } 61 | }; 62 | 63 | // Export the config object based on the NODE_ENV 64 | // ============================================== 65 | module.exports = _.merge( 66 | all, 67 | require('./' + process.env.NODE_ENV + '.js') || {}); -------------------------------------------------------------------------------- /dist/server/config/environment/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var _ = require('lodash'); 5 | 6 | function requiredProcessEnv(name) { 7 | if(!process.env[name]) { 8 | throw new Error('You must set the ' + name + ' environment variable'); 9 | } 10 | return process.env[name]; 11 | } 12 | 13 | // All configurations will extend these options 14 | // ============================================ 15 | var all = { 16 | env: process.env.NODE_ENV, 17 | 18 | // Root path of server 19 | root: path.normalize(__dirname + '/../../..'), 20 | 21 | // Server port 22 | port: process.env.PORT || 9000, 23 | 24 | // Should we populate the DB with sample data? 25 | seedDB: false, 26 | 27 | // Secret for session, you will want to change this and make it an environment variable 28 | secrets: { 29 | session: 'meet-me-in-the-middle-secret' 30 | }, 31 | 32 | // List of user roles 33 | userRoles: ['guest', 'user', 'admin'], 34 | 35 | // MongoDB connection options 36 | mongo: { 37 | options: { 38 | db: { 39 | safe: true 40 | } 41 | } 42 | }, 43 | 44 | facebook: { 45 | clientID: process.env.FACEBOOK_ID || 'id', 46 | clientSecret: process.env.FACEBOOK_SECRET || 'secret', 47 | callbackURL: (process.env.DOMAIN || '') + '/auth/facebook/callback' 48 | }, 49 | 50 | twitter: { 51 | clientID: process.env.TWITTER_ID || 'id', 52 | clientSecret: process.env.TWITTER_SECRET || 'secret', 53 | callbackURL: (process.env.DOMAIN || '') + '/auth/twitter/callback' 54 | }, 55 | 56 | google: { 57 | clientID: process.env.GOOGLE_ID || 'id', 58 | clientSecret: process.env.GOOGLE_SECRET || 'secret', 59 | callbackURL: (process.env.DOMAIN || '') + '/auth/google/callback' 60 | } 61 | }; 62 | 63 | // Export the config object based on the NODE_ENV 64 | // ============================================== 65 | module.exports = _.merge( 66 | all, 67 | require('./' + process.env.NODE_ENV + '.js') || {}); -------------------------------------------------------------------------------- /server/config/socketio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Socket.io configuration 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var config = require('./environment'); 8 | 9 | // When the user disconnects.. perform this 10 | function onDisconnect(socket) { 11 | } 12 | 13 | // When the user connects.. perform this 14 | function onConnect(socket) { 15 | // When the client emits 'info', this listens and executes 16 | socket.on('info', function (data) { 17 | console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2)); 18 | }); 19 | 20 | // Insert sockets below 21 | require('../api/thing/thing.socket').register(socket); 22 | } 23 | 24 | module.exports = function (socketio) { 25 | // socket.io (v1.x.x) is powered by debug. 26 | // In order to see all the debug output, set DEBUG (in server/config/local.env.js) to including the desired scope. 27 | // 28 | // ex: DEBUG: "http*,socket.io:socket" 29 | 30 | // We can authenticate socket.io users and access their token through socket.handshake.decoded_token 31 | // 32 | // 1. You will need to send the token in `client/components/socket/socket.service.js` 33 | // 34 | // 2. Require authentication here: 35 | // socketio.use(require('socketio-jwt').authorize({ 36 | // secret: config.secrets.session, 37 | // handshake: true 38 | // })); 39 | 40 | //Ko: all server-side socket.io logics go here. 41 | socketio.on('connection', function (socket) { 42 | socket.address = socket.handshake.address !== null ? 43 | socket.handshake.address.address + ':' + socket.handshake.address.port : 44 | process.env.DOMAIN; 45 | 46 | socket.connectedAt = new Date(); 47 | 48 | // Call onDisconnect. 49 | socket.on('disconnect', function () { 50 | onDisconnect(socket); 51 | console.info('[%s] DISCONNECTED', socket.address); 52 | }); 53 | 54 | // Call onConnect. 55 | onConnect(socket); 56 | console.info('[%s] CONNECTED', socket.address); 57 | 58 | //Ko: call on place pin 59 | socket.on('test', function(){ 60 | console.log("testtest"); 61 | }) 62 | }); 63 | 64 | }; -------------------------------------------------------------------------------- /server/api/thing/thing.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Using Rails-like standard naming convention for endpoints. 3 | * GET /things -> index 4 | * POST /things -> create 5 | * GET /things/:id -> show 6 | * PUT /things/:id -> update 7 | * DELETE /things/:id -> destroy 8 | */ 9 | 10 | 'use strict'; 11 | 12 | var _ = require('lodash'); 13 | var Thing = require('./thing.model'); 14 | 15 | // Get list of things 16 | exports.index = function(req, res) { 17 | Thing.find(function (err, things) { 18 | if(err) { return handleError(res, err); } 19 | return res.json(200, things); 20 | }); 21 | }; 22 | 23 | // Get a single thing 24 | exports.show = function(req, res) { 25 | Thing.findById(req.params.id, function (err, thing) { 26 | if(err) { return handleError(res, err); } 27 | if(!thing) { return res.send(404); } 28 | return res.json(thing); 29 | }); 30 | }; 31 | 32 | // Creates a new thing in the DB. 33 | exports.create = function(req, res) { 34 | Thing.create(req.body, function(err, thing) { 35 | if(err) { return handleError(res, err); } 36 | return res.json(201, thing); 37 | }); 38 | }; 39 | 40 | // Updates an existing thing in the DB. 41 | exports.update = function(req, res) { 42 | if(req.body._id) { delete req.body._id; } 43 | Thing.findById(req.params.id, function (err, thing) { 44 | if (err) { return handleError(res, err); } 45 | if(!thing) { return res.send(404); } 46 | var updated = _.merge(thing, req.body); 47 | updated.save(function (err) { 48 | if (err) { return handleError(res, err); } 49 | return res.json(200, thing); 50 | }); 51 | }); 52 | }; 53 | 54 | // Deletes a thing from the DB. 55 | exports.destroy = function(req, res) { 56 | Thing.findById(req.params.id, function (err, thing) { 57 | if(err) { return handleError(res, err); } 58 | if(!thing) { return res.send(404); } 59 | thing.remove(function(err) { 60 | if(err) { return handleError(res, err); } 61 | return res.send(204); 62 | }); 63 | }); 64 | }; 65 | 66 | function handleError(res, err) { 67 | return res.send(500, err); 68 | } -------------------------------------------------------------------------------- /dist/server/api/thing/thing.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Using Rails-like standard naming convention for endpoints. 3 | * GET /things -> index 4 | * POST /things -> create 5 | * GET /things/:id -> show 6 | * PUT /things/:id -> update 7 | * DELETE /things/:id -> destroy 8 | */ 9 | 10 | 'use strict'; 11 | 12 | var _ = require('lodash'); 13 | var Thing = require('./thing.model'); 14 | 15 | // Get list of things 16 | exports.index = function(req, res) { 17 | Thing.find(function (err, things) { 18 | if(err) { return handleError(res, err); } 19 | return res.json(200, things); 20 | }); 21 | }; 22 | 23 | // Get a single thing 24 | exports.show = function(req, res) { 25 | Thing.findById(req.params.id, function (err, thing) { 26 | if(err) { return handleError(res, err); } 27 | if(!thing) { return res.send(404); } 28 | return res.json(thing); 29 | }); 30 | }; 31 | 32 | // Creates a new thing in the DB. 33 | exports.create = function(req, res) { 34 | Thing.create(req.body, function(err, thing) { 35 | if(err) { return handleError(res, err); } 36 | return res.json(201, thing); 37 | }); 38 | }; 39 | 40 | // Updates an existing thing in the DB. 41 | exports.update = function(req, res) { 42 | if(req.body._id) { delete req.body._id; } 43 | Thing.findById(req.params.id, function (err, thing) { 44 | if (err) { return handleError(res, err); } 45 | if(!thing) { return res.send(404); } 46 | var updated = _.merge(thing, req.body); 47 | updated.save(function (err) { 48 | if (err) { return handleError(res, err); } 49 | return res.json(200, thing); 50 | }); 51 | }); 52 | }; 53 | 54 | // Deletes a thing from the DB. 55 | exports.destroy = function(req, res) { 56 | Thing.findById(req.params.id, function (err, thing) { 57 | if(err) { return handleError(res, err); } 58 | if(!thing) { return res.send(404); } 59 | thing.remove(function(err) { 60 | if(err) { return handleError(res, err); } 61 | return res.send(204); 62 | }); 63 | }); 64 | }; 65 | 66 | function handleError(res, err) { 67 | return res.send(500, err); 68 | } -------------------------------------------------------------------------------- /server/config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Express configuration 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var express = require('express'); 8 | var favicon = require('serve-favicon'); 9 | var morgan = require('morgan'); 10 | var compression = require('compression'); 11 | var bodyParser = require('body-parser'); 12 | var methodOverride = require('method-override'); 13 | var cookieParser = require('cookie-parser'); 14 | var errorHandler = require('errorhandler'); 15 | var path = require('path'); 16 | var config = require('./environment'); 17 | var passport = require('passport'); 18 | var session = require('express-session'); 19 | var mongoStore = require('connect-mongo')(session); 20 | var mongoose = require('mongoose'); 21 | 22 | module.exports = function(app) { 23 | var env = app.get('env'); 24 | 25 | app.set('views', config.root + '/server/views'); 26 | app.engine('html', require('ejs').renderFile); 27 | app.set('view engine', 'html'); 28 | app.use(compression()); 29 | app.use(bodyParser.urlencoded({ extended: false })); 30 | app.use(bodyParser.json()); 31 | app.use(methodOverride()); 32 | app.use(cookieParser()); 33 | app.use(passport.initialize()); 34 | 35 | // Persist sessions with mongoStore 36 | // We need to enable sessions for passport twitter because its an oauth 1.0 strategy 37 | app.use(session({ 38 | secret: config.secrets.session, 39 | resave: true, 40 | saveUninitialized: true, 41 | store: new mongoStore({ mongoose_connection: mongoose.connection }) 42 | })); 43 | 44 | if ('production' === env) { 45 | app.use(favicon(path.join(config.root, 'public', 'favicon.ico'))); 46 | app.use(express.static(path.join(config.root, 'public'))); 47 | app.set('appPath', config.root + '/public'); 48 | app.use(morgan('dev')); 49 | } 50 | 51 | if ('development' === env || 'test' === env) { 52 | app.use(require('connect-livereload')()); 53 | app.use(express.static(path.join(config.root, '.tmp'))); 54 | app.use(express.static(path.join(config.root, 'client'))); 55 | app.set('appPath', 'client'); 56 | app.use(morgan('dev')); 57 | app.use(errorHandler()); // Error handler - has to be last 58 | } 59 | }; -------------------------------------------------------------------------------- /dist/server/config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Express configuration 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var express = require('express'); 8 | var favicon = require('serve-favicon'); 9 | var morgan = require('morgan'); 10 | var compression = require('compression'); 11 | var bodyParser = require('body-parser'); 12 | var methodOverride = require('method-override'); 13 | var cookieParser = require('cookie-parser'); 14 | var errorHandler = require('errorhandler'); 15 | var path = require('path'); 16 | var config = require('./environment'); 17 | var passport = require('passport'); 18 | var session = require('express-session'); 19 | var mongoStore = require('connect-mongo')(session); 20 | var mongoose = require('mongoose'); 21 | 22 | module.exports = function(app) { 23 | var env = app.get('env'); 24 | 25 | app.set('views', config.root + '/server/views'); 26 | app.engine('html', require('ejs').renderFile); 27 | app.set('view engine', 'html'); 28 | app.use(compression()); 29 | app.use(bodyParser.urlencoded({ extended: false })); 30 | app.use(bodyParser.json()); 31 | app.use(methodOverride()); 32 | app.use(cookieParser()); 33 | app.use(passport.initialize()); 34 | 35 | // Persist sessions with mongoStore 36 | // We need to enable sessions for passport twitter because its an oauth 1.0 strategy 37 | app.use(session({ 38 | secret: config.secrets.session, 39 | resave: true, 40 | saveUninitialized: true, 41 | store: new mongoStore({ mongoose_connection: mongoose.connection }) 42 | })); 43 | 44 | if ('production' === env) { 45 | app.use(favicon(path.join(config.root, 'public', 'favicon.ico'))); 46 | app.use(express.static(path.join(config.root, 'public'))); 47 | app.set('appPath', config.root + '/public'); 48 | app.use(morgan('dev')); 49 | } 50 | 51 | if ('development' === env || 'test' === env) { 52 | app.use(require('connect-livereload')()); 53 | app.use(express.static(path.join(config.root, '.tmp'))); 54 | app.use(express.static(path.join(config.root, 'client'))); 55 | app.set('appPath', 'client'); 56 | app.use(morgan('dev')); 57 | app.use(errorHandler()); // Error handler - has to be last 58 | } 59 | }; -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application file 3 | */ 4 | 5 | 'use strict'; 6 | 7 | // Set default node environment to development 8 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 9 | 10 | var express = require('express'); 11 | var mongoose = require('mongoose'); 12 | var config = require('./config/environment'); 13 | 14 | // Connect to database 15 | mongoose.connect(config.mongo.uri, config.mongo.options); 16 | 17 | // Populate DB with sample data 18 | if(config.seedDB) { require('./config/seed'); } 19 | 20 | // Setup server 21 | var app = express(); 22 | var server = require('http').createServer(app); 23 | //Ko: Socket is hooked here 24 | // var socketio = require('socket.io')(server, { 25 | // serveClient: (config.env === 'production') ? false : true, 26 | // path: '/socket.io-client' 27 | // }); 28 | // //Ko: Server-side socket logic is defined here in ./config/socketio.js 29 | // require('./config/socketio')(socketio); 30 | var socket = require('socket.io'); 31 | var io = socket(server); 32 | 33 | var dataCollection = {}; 34 | io.on('connection', function(socket){ 35 | 36 | // data = {id:c, coors: { latitude: num, longitude: num}} 37 | socket.on('move-pin', function(data){ 38 | // If it's new socket.id 39 | dataCollection[socket.id] = data; 40 | 41 | // Sendback all the data 42 | //dataCollection = {socket.id1:{longitude:num, latitude: num, roomNumber: num}, ..., socket.idN:{longitude:num, latitude:num, roomNumber: num}} 43 | io.emit('move-pin', dataCollection) 44 | 45 | 46 | // Testing 47 | console.log('TESTING SOCKET.IO' + socket.id) 48 | 49 | console.dir(dataCollection); 50 | 51 | }); 52 | 53 | // Delete the data after disconnecting. 54 | socket.on('disconnect', function(data){ 55 | delete dataCollection[socket.id]; 56 | io.emit('move-pin', dataCollection); 57 | }) 58 | }) 59 | 60 | 61 | require('./config/express')(app); 62 | require('./routes')(app); 63 | 64 | // Start server 65 | server.listen(config.port, config.ip, function () { 66 | console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); 67 | }); 68 | 69 | // Expose app 70 | exports = module.exports = app; -------------------------------------------------------------------------------- /client/app/account/login/login.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Login

7 |

Accounts are reset on server restart from server/config/seed.js. Default account is test@test.com / test

8 |

Admin account is admin@admin.com / admin

9 |
10 |
11 |
12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 | 23 |
24 | 25 |
26 |

27 | Please enter your email and password. 28 |

29 |

30 | Please enter a valid email. 31 |

32 | 33 |

{{ errors.other }}

34 |
35 | 36 |
37 | 40 | 41 | Register 42 | 43 |
44 | 45 |
46 | 57 |
58 |
59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /server/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var passport = require('passport'); 5 | var config = require('../config/environment'); 6 | var jwt = require('jsonwebtoken'); 7 | var expressJwt = require('express-jwt'); 8 | var compose = require('composable-middleware'); 9 | var User = require('../api/user/user.model'); 10 | var validateJwt = expressJwt({ secret: config.secrets.session }); 11 | 12 | /** 13 | * Attaches the user object to the request if authenticated 14 | * Otherwise returns 403 15 | */ 16 | function isAuthenticated() { 17 | return compose() 18 | // Validate jwt 19 | .use(function(req, res, next) { 20 | // allow access_token to be passed through query parameter as well 21 | if(req.query && req.query.hasOwnProperty('access_token')) { 22 | req.headers.authorization = 'Bearer ' + req.query.access_token; 23 | } 24 | validateJwt(req, res, next); 25 | }) 26 | // Attach user to request 27 | .use(function(req, res, next) { 28 | User.findById(req.user._id, function (err, user) { 29 | if (err) return next(err); 30 | if (!user) return res.send(401); 31 | 32 | req.user = user; 33 | next(); 34 | }); 35 | }); 36 | } 37 | 38 | /** 39 | * Checks if the user role meets the minimum requirements of the route 40 | */ 41 | function hasRole(roleRequired) { 42 | if (!roleRequired) throw new Error('Required role needs to be set'); 43 | 44 | return compose() 45 | .use(isAuthenticated()) 46 | .use(function meetsRequirements(req, res, next) { 47 | if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) { 48 | next(); 49 | } 50 | else { 51 | res.send(403); 52 | } 53 | }); 54 | } 55 | 56 | /** 57 | * Returns a jwt token signed by the app secret 58 | */ 59 | function signToken(id) { 60 | return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 }); 61 | } 62 | 63 | /** 64 | * Set token cookie directly for oAuth strategies 65 | */ 66 | function setTokenCookie(req, res) { 67 | if (!req.user) return res.json(404, { message: 'Something went wrong, please try again.'}); 68 | var token = signToken(req.user._id, req.user.role); 69 | res.cookie('token', JSON.stringify(token)); 70 | res.redirect('/'); 71 | } 72 | 73 | exports.isAuthenticated = isAuthenticated; 74 | exports.hasRole = hasRole; 75 | exports.signToken = signToken; 76 | exports.setTokenCookie = setTokenCookie; -------------------------------------------------------------------------------- /dist/server/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var passport = require('passport'); 5 | var config = require('../config/environment'); 6 | var jwt = require('jsonwebtoken'); 7 | var expressJwt = require('express-jwt'); 8 | var compose = require('composable-middleware'); 9 | var User = require('../api/user/user.model'); 10 | var validateJwt = expressJwt({ secret: config.secrets.session }); 11 | 12 | /** 13 | * Attaches the user object to the request if authenticated 14 | * Otherwise returns 403 15 | */ 16 | function isAuthenticated() { 17 | return compose() 18 | // Validate jwt 19 | .use(function(req, res, next) { 20 | // allow access_token to be passed through query parameter as well 21 | if(req.query && req.query.hasOwnProperty('access_token')) { 22 | req.headers.authorization = 'Bearer ' + req.query.access_token; 23 | } 24 | validateJwt(req, res, next); 25 | }) 26 | // Attach user to request 27 | .use(function(req, res, next) { 28 | User.findById(req.user._id, function (err, user) { 29 | if (err) return next(err); 30 | if (!user) return res.send(401); 31 | 32 | req.user = user; 33 | next(); 34 | }); 35 | }); 36 | } 37 | 38 | /** 39 | * Checks if the user role meets the minimum requirements of the route 40 | */ 41 | function hasRole(roleRequired) { 42 | if (!roleRequired) throw new Error('Required role needs to be set'); 43 | 44 | return compose() 45 | .use(isAuthenticated()) 46 | .use(function meetsRequirements(req, res, next) { 47 | if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) { 48 | next(); 49 | } 50 | else { 51 | res.send(403); 52 | } 53 | }); 54 | } 55 | 56 | /** 57 | * Returns a jwt token signed by the app secret 58 | */ 59 | function signToken(id) { 60 | return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 }); 61 | } 62 | 63 | /** 64 | * Set token cookie directly for oAuth strategies 65 | */ 66 | function setTokenCookie(req, res) { 67 | if (!req.user) return res.json(404, { message: 'Something went wrong, please try again.'}); 68 | var token = signToken(req.user._id, req.user.role); 69 | res.cookie('token', JSON.stringify(token)); 70 | res.redirect('/'); 71 | } 72 | 73 | exports.isAuthenticated = isAuthenticated; 74 | exports.hasRole = hasRole; 75 | exports.signToken = signToken; 76 | exports.setTokenCookie = setTokenCookie; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['jasmine'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'client/bower_components/jquery/dist/jquery.js', 15 | 'client/bower_components/angular/angular.js', 16 | 'client/bower_components/angular-mocks/angular-mocks.js', 17 | 'client/bower_components/angular-resource/angular-resource.js', 18 | 'client/bower_components/angular-cookies/angular-cookies.js', 19 | 'client/bower_components/angular-sanitize/angular-sanitize.js', 20 | 'client/bower_components/angular-route/angular-route.js', 21 | 'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', 22 | 'client/bower_components/lodash/dist/lodash.compat.js', 23 | 'client/bower_components/angular-socket-io/socket.js', 24 | 'client/bower_components/angular-ui-router/release/angular-ui-router.js', 25 | 'client/app/app.js', 26 | 'client/app/app.coffee', 27 | 'client/app/**/*.js', 28 | 'client/app/**/*.coffee', 29 | 'client/components/**/*.js', 30 | 'client/components/**/*.coffee', 31 | 'client/app/**/*.jade', 32 | 'client/components/**/*.jade', 33 | 'client/app/**/*.html', 34 | 'client/components/**/*.html' 35 | ], 36 | 37 | preprocessors: { 38 | '**/*.jade': 'ng-jade2js', 39 | '**/*.html': 'html2js', 40 | '**/*.coffee': 'coffee', 41 | }, 42 | 43 | ngHtml2JsPreprocessor: { 44 | stripPrefix: 'client/' 45 | }, 46 | 47 | ngJade2JsPreprocessor: { 48 | stripPrefix: 'client/' 49 | }, 50 | 51 | // list of files / patterns to exclude 52 | exclude: [], 53 | 54 | // web server port 55 | port: 8080, 56 | 57 | // level of logging 58 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 59 | logLevel: config.LOG_INFO, 60 | 61 | 62 | // enable / disable watching file and executing tests whenever any file changes 63 | autoWatch: false, 64 | 65 | 66 | // Start these browsers, currently available: 67 | // - Chrome 68 | // - ChromeCanary 69 | // - Firefox 70 | // - Opera 71 | // - Safari (only Mac) 72 | // - PhantomJS 73 | // - IE (only Windows) 74 | browsers: ['PhantomJS'], 75 | 76 | 77 | // Continuous Integration mode 78 | // if true, it capture browsers, run tests and exit 79 | singleRun: false 80 | }); 81 | }; 82 | -------------------------------------------------------------------------------- /client/components/modal/modal.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .factory('Modal', function ($rootScope, $modal) { 5 | /** 6 | * Opens a modal 7 | * @param {Object} scope - an object to be merged with modal's scope 8 | * @param {String} modalClass - (optional) class(es) to be applied to the modal 9 | * @return {Object} - the instance $modal.open() returns 10 | */ 11 | function openModal(scope, modalClass) { 12 | var modalScope = $rootScope.$new(); 13 | scope = scope || {}; 14 | modalClass = modalClass || 'modal-default'; 15 | 16 | angular.extend(modalScope, scope); 17 | 18 | return $modal.open({ 19 | templateUrl: 'components/modal/modal.html', 20 | windowClass: modalClass, 21 | scope: modalScope 22 | }); 23 | } 24 | 25 | // Public API here 26 | return { 27 | 28 | /* Confirmation modals */ 29 | confirm: { 30 | 31 | /** 32 | * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)') 33 | * @param {Function} del - callback, ran when delete is confirmed 34 | * @return {Function} - the function to open the modal (ex. myModalFn) 35 | */ 36 | delete: function(del) { 37 | del = del || angular.noop; 38 | 39 | /** 40 | * Open a delete confirmation modal 41 | * @param {String} name - name or info to show on modal 42 | * @param {All} - any additional args are passed staight to del callback 43 | */ 44 | return function() { 45 | var args = Array.prototype.slice.call(arguments), 46 | name = args.shift(), 47 | deleteModal; 48 | 49 | deleteModal = openModal({ 50 | modal: { 51 | dismissable: true, 52 | title: 'Confirm Delete', 53 | html: '

Are you sure you want to delete ' + name + ' ?

', 54 | buttons: [{ 55 | classes: 'btn-danger', 56 | text: 'Delete', 57 | click: function(e) { 58 | deleteModal.close(e); 59 | } 60 | }, { 61 | classes: 'btn-default', 62 | text: 'Cancel', 63 | click: function(e) { 64 | deleteModal.dismiss(e); 65 | } 66 | }] 67 | } 68 | }, 'modal-danger'); 69 | 70 | deleteModal.result.then(function(event) { 71 | del.apply(event, args); 72 | }); 73 | }; 74 | } 75 | } 76 | }; 77 | }); 78 | -------------------------------------------------------------------------------- /server/api/user/user.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var User = require('./user.model'); 4 | var passport = require('passport'); 5 | var config = require('../../config/environment'); 6 | var jwt = require('jsonwebtoken'); 7 | 8 | var validationError = function(res, err) { 9 | return res.json(422, err); 10 | }; 11 | 12 | /** 13 | * Get list of users 14 | * restriction: 'admin' 15 | */ 16 | exports.index = function(req, res) { 17 | User.find({}, '-salt -hashedPassword', function (err, users) { 18 | if(err) return res.send(500, err); 19 | res.json(200, users); 20 | }); 21 | }; 22 | 23 | /** 24 | * Creates a new user 25 | */ 26 | exports.create = function (req, res, next) { 27 | var newUser = new User(req.body); 28 | newUser.provider = 'local'; 29 | newUser.role = 'user'; 30 | newUser.save(function(err, user) { 31 | if (err) return validationError(res, err); 32 | var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 }); 33 | res.json({ token: token }); 34 | }); 35 | }; 36 | 37 | /** 38 | * Get a single user 39 | */ 40 | exports.show = function (req, res, next) { 41 | var userId = req.params.id; 42 | 43 | User.findById(userId, function (err, user) { 44 | if (err) return next(err); 45 | if (!user) return res.send(401); 46 | res.json(user.profile); 47 | }); 48 | }; 49 | 50 | /** 51 | * Deletes a user 52 | * restriction: 'admin' 53 | */ 54 | exports.destroy = function(req, res) { 55 | User.findByIdAndRemove(req.params.id, function(err, user) { 56 | if(err) return res.send(500, err); 57 | return res.send(204); 58 | }); 59 | }; 60 | 61 | /** 62 | * Change a users password 63 | */ 64 | exports.changePassword = function(req, res, next) { 65 | var userId = req.user._id; 66 | var oldPass = String(req.body.oldPassword); 67 | var newPass = String(req.body.newPassword); 68 | 69 | User.findById(userId, function (err, user) { 70 | if(user.authenticate(oldPass)) { 71 | user.password = newPass; 72 | user.save(function(err) { 73 | if (err) return validationError(res, err); 74 | res.send(200); 75 | }); 76 | } else { 77 | res.send(403); 78 | } 79 | }); 80 | }; 81 | 82 | /** 83 | * Get my info 84 | */ 85 | exports.me = function(req, res, next) { 86 | var userId = req.user._id; 87 | User.findOne({ 88 | _id: userId 89 | }, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt 90 | if (err) return next(err); 91 | if (!user) return res.json(401); 92 | res.json(user); 93 | }); 94 | }; 95 | 96 | /** 97 | * Authentication callback 98 | */ 99 | exports.authCallback = function(req, res, next) { 100 | res.redirect('/'); 101 | }; 102 | -------------------------------------------------------------------------------- /dist/server/api/user/user.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var User = require('./user.model'); 4 | var passport = require('passport'); 5 | var config = require('../../config/environment'); 6 | var jwt = require('jsonwebtoken'); 7 | 8 | var validationError = function(res, err) { 9 | return res.json(422, err); 10 | }; 11 | 12 | /** 13 | * Get list of users 14 | * restriction: 'admin' 15 | */ 16 | exports.index = function(req, res) { 17 | User.find({}, '-salt -hashedPassword', function (err, users) { 18 | if(err) return res.send(500, err); 19 | res.json(200, users); 20 | }); 21 | }; 22 | 23 | /** 24 | * Creates a new user 25 | */ 26 | exports.create = function (req, res, next) { 27 | var newUser = new User(req.body); 28 | newUser.provider = 'local'; 29 | newUser.role = 'user'; 30 | newUser.save(function(err, user) { 31 | if (err) return validationError(res, err); 32 | var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 }); 33 | res.json({ token: token }); 34 | }); 35 | }; 36 | 37 | /** 38 | * Get a single user 39 | */ 40 | exports.show = function (req, res, next) { 41 | var userId = req.params.id; 42 | 43 | User.findById(userId, function (err, user) { 44 | if (err) return next(err); 45 | if (!user) return res.send(401); 46 | res.json(user.profile); 47 | }); 48 | }; 49 | 50 | /** 51 | * Deletes a user 52 | * restriction: 'admin' 53 | */ 54 | exports.destroy = function(req, res) { 55 | User.findByIdAndRemove(req.params.id, function(err, user) { 56 | if(err) return res.send(500, err); 57 | return res.send(204); 58 | }); 59 | }; 60 | 61 | /** 62 | * Change a users password 63 | */ 64 | exports.changePassword = function(req, res, next) { 65 | var userId = req.user._id; 66 | var oldPass = String(req.body.oldPassword); 67 | var newPass = String(req.body.newPassword); 68 | 69 | User.findById(userId, function (err, user) { 70 | if(user.authenticate(oldPass)) { 71 | user.password = newPass; 72 | user.save(function(err) { 73 | if (err) return validationError(res, err); 74 | res.send(200); 75 | }); 76 | } else { 77 | res.send(403); 78 | } 79 | }); 80 | }; 81 | 82 | /** 83 | * Get my info 84 | */ 85 | exports.me = function(req, res, next) { 86 | var userId = req.user._id; 87 | User.findOne({ 88 | _id: userId 89 | }, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt 90 | if (err) return next(err); 91 | if (!user) return res.json(401); 92 | res.json(user); 93 | }); 94 | }; 95 | 96 | /** 97 | * Authentication callback 98 | */ 99 | exports.authCallback = function(req, res, next) { 100 | res.redirect('/'); 101 | }; 102 | -------------------------------------------------------------------------------- /client/app/landing/js/countUp.min.js: -------------------------------------------------------------------------------- 1 | //function countUp(a,b,c,d,e,f){"use strict";for(var g=0,h=["webkit","moz","ms","o"],i=0;ithis.endVal?!0:!1,this.startTime=null,this.timestamp=null,this.remaining=null,this.frameVal=this.startVal,this.rAF=null,this.decimals=Math.max(0,d||0),this.dec=Math.pow(10,this.decimals),this.duration=1e3*e||2e3,this.version=function(){return"1.3.2"},this.printValue=function(a){var b=isNaN(a)?"--":j.formatNumber(a);"INPUT"==j.d.tagName?this.d.value=b:"text"==j.d.tagName?this.d.textContent=b:this.d.innerHTML=b},this.easeOutExpo=function(a,b,c,d){return 1024*c*(-Math.pow(2,-10*a/d)+1)/1023+b},this.count=function(a){null===j.startTime&&(j.startTime=a),j.timestamp=a;var b=a-j.startTime;if(j.remaining=j.duration-b,j.options.useEasing)if(j.countDown){var c=j.easeOutExpo(b,0,j.startVal-j.endVal,j.duration);j.frameVal=j.startVal-c}else j.frameVal=j.easeOutExpo(b,j.startVal,j.endVal-j.startVal,j.duration);else if(j.countDown){var c=(j.startVal-j.endVal)*(b/j.duration);j.frameVal=j.startVal-c}else j.frameVal=j.startVal+(j.endVal-j.startVal)*(b/j.duration);j.frameVal=j.countDown?j.frameValj.endVal?j.endVal:j.frameVal,j.frameVal=Math.round(j.frameVal*j.dec)/j.dec,j.printValue(j.frameVal),b1?j.options.decimal+b[1]:"",e=/(\d+)(\d{3})/,j.options.useGrouping)for(;e.test(c);)c=c.replace(e,"$1"+j.options.separator+"$2");return j.options.prefix+c+d+j.options.suffix},j.printValue(j.startVal)} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meet-me-in-the-middle", 3 | "version": "0.0.0", 4 | "main": "server/app.js", 5 | "dependencies": { 6 | "angular-google-maps": "^2.0.19", 7 | "body-parser": "~1.5.0", 8 | "composable-middleware": "^0.3.0", 9 | "compression": "~1.0.1", 10 | "connect-mongo": "^0.4.1", 11 | "cookie-parser": "~1.0.1", 12 | "ejs": "~0.8.4", 13 | "errorhandler": "~1.0.0", 14 | "express": "~4.0.0", 15 | "express-jwt": "^0.1.3", 16 | "express-session": "~1.0.2", 17 | "jsonwebtoken": "^0.3.0", 18 | "lodash": "~2.4.1", 19 | "method-override": "~1.0.0", 20 | "mongoose": "~3.8.8", 21 | "morgan": "~1.0.0", 22 | "passport": "~0.2.0", 23 | "passport-facebook": "latest", 24 | "passport-google-oauth": "latest", 25 | "passport-local": "~0.1.6", 26 | "passport-twitter": "latest", 27 | "serve-favicon": "~2.0.1", 28 | "socket.io": "^1.3.5" 29 | }, 30 | "devDependencies": { 31 | "grunt": "~0.4.4", 32 | "grunt-autoprefixer": "~0.7.2", 33 | "grunt-wiredep": "~1.8.0", 34 | "grunt-concurrent": "~0.5.0", 35 | "grunt-contrib-clean": "~0.5.0", 36 | "grunt-contrib-concat": "~0.4.0", 37 | "grunt-contrib-copy": "~0.5.0", 38 | "grunt-contrib-cssmin": "~0.9.0", 39 | "grunt-contrib-htmlmin": "~0.2.0", 40 | "grunt-contrib-imagemin": "~0.7.1", 41 | "grunt-contrib-jshint": "~0.10.0", 42 | "grunt-contrib-uglify": "~0.4.0", 43 | "grunt-contrib-watch": "~0.6.1", 44 | "grunt-google-cdn": "~0.4.0", 45 | "grunt-newer": "~0.7.0", 46 | "grunt-ng-annotate": "^0.2.3", 47 | "grunt-rev": "~0.1.0", 48 | "grunt-svgmin": "~0.4.0", 49 | "grunt-usemin": "~2.1.1", 50 | "grunt-env": "~0.4.1", 51 | "grunt-node-inspector": "~0.1.5", 52 | "grunt-nodemon": "~0.2.0", 53 | "grunt-angular-templates": "^0.5.4", 54 | "grunt-dom-munger": "^3.4.0", 55 | "grunt-protractor-runner": "^1.1.0", 56 | "grunt-asset-injector": "^0.1.0", 57 | "grunt-karma": "~0.8.2", 58 | "grunt-build-control": "DaftMonk/grunt-build-control", 59 | "grunt-mocha-test": "~0.10.2", 60 | "jit-grunt": "^0.5.0", 61 | "time-grunt": "~0.3.1", 62 | "grunt-express-server": "~0.4.17", 63 | "grunt-open": "~0.2.3", 64 | "open": "~0.0.4", 65 | "jshint-stylish": "~0.1.5", 66 | "connect-livereload": "~0.4.0", 67 | "karma-ng-scenario": "~0.1.0", 68 | "karma-firefox-launcher": "~0.1.3", 69 | "karma-script-launcher": "~0.1.0", 70 | "karma-html2js-preprocessor": "~0.1.0", 71 | "karma-ng-jade2js-preprocessor": "^0.1.2", 72 | "karma-jasmine": "~0.1.5", 73 | "karma-chrome-launcher": "~0.1.3", 74 | "requirejs": "~2.1.11", 75 | "karma-requirejs": "~0.2.1", 76 | "karma-coffee-preprocessor": "~0.2.1", 77 | "karma-jade-preprocessor": "0.0.11", 78 | "karma-phantomjs-launcher": "~0.1.4", 79 | "karma": "~0.12.9", 80 | "karma-ng-html2js-preprocessor": "~0.1.0", 81 | "supertest": "~0.11.0", 82 | "should": "~3.3.1" 83 | }, 84 | "engines": { 85 | "node": ">=0.10.0" 86 | }, 87 | "scripts": { 88 | "start": "node server/app.js", 89 | "test": "grunt test", 90 | "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update" 91 | }, 92 | "private": true 93 | } 94 | -------------------------------------------------------------------------------- /dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meet-me-in-the-middle", 3 | "version": "0.0.0", 4 | "main": "server/app.js", 5 | "dependencies": { 6 | "express": "~4.0.0", 7 | "morgan": "~1.0.0", 8 | "body-parser": "~1.5.0", 9 | "method-override": "~1.0.0", 10 | "serve-favicon": "~2.0.1", 11 | "cookie-parser": "~1.0.1", 12 | "express-session": "~1.0.2", 13 | "errorhandler": "~1.0.0", 14 | "compression": "~1.0.1", 15 | "lodash": "~2.4.1", 16 | "ejs": "~0.8.4", 17 | "mongoose": "~3.8.8", 18 | "jsonwebtoken": "^0.3.0", 19 | "express-jwt": "^0.1.3", 20 | "passport": "~0.2.0", 21 | "passport-local": "~0.1.6", 22 | "passport-facebook": "latest", 23 | "passport-twitter": "latest", 24 | "passport-google-oauth": "latest", 25 | "composable-middleware": "^0.3.0", 26 | "connect-mongo": "^0.4.1", 27 | "socket.io": "^1.0.6", 28 | "socket.io-client": "^1.0.6", 29 | "socketio-jwt": "^2.0.2" 30 | }, 31 | "devDependencies": { 32 | "grunt": "~0.4.4", 33 | "grunt-autoprefixer": "~0.7.2", 34 | "grunt-wiredep": "~1.8.0", 35 | "grunt-concurrent": "~0.5.0", 36 | "grunt-contrib-clean": "~0.5.0", 37 | "grunt-contrib-concat": "~0.4.0", 38 | "grunt-contrib-copy": "~0.5.0", 39 | "grunt-contrib-cssmin": "~0.9.0", 40 | "grunt-contrib-htmlmin": "~0.2.0", 41 | "grunt-contrib-imagemin": "~0.7.1", 42 | "grunt-contrib-jshint": "~0.10.0", 43 | "grunt-contrib-uglify": "~0.4.0", 44 | "grunt-contrib-watch": "~0.6.1", 45 | "grunt-google-cdn": "~0.4.0", 46 | "grunt-newer": "~0.7.0", 47 | "grunt-ng-annotate": "^0.2.3", 48 | "grunt-rev": "~0.1.0", 49 | "grunt-svgmin": "~0.4.0", 50 | "grunt-usemin": "~2.1.1", 51 | "grunt-env": "~0.4.1", 52 | "grunt-node-inspector": "~0.1.5", 53 | "grunt-nodemon": "~0.2.0", 54 | "grunt-angular-templates": "^0.5.4", 55 | "grunt-dom-munger": "^3.4.0", 56 | "grunt-protractor-runner": "^1.1.0", 57 | "grunt-asset-injector": "^0.1.0", 58 | "grunt-karma": "~0.8.2", 59 | "grunt-build-control": "DaftMonk/grunt-build-control", 60 | "grunt-mocha-test": "~0.10.2", 61 | "jit-grunt": "^0.5.0", 62 | "time-grunt": "~0.3.1", 63 | "grunt-express-server": "~0.4.17", 64 | "grunt-open": "~0.2.3", 65 | "open": "~0.0.4", 66 | "jshint-stylish": "~0.1.5", 67 | "connect-livereload": "~0.4.0", 68 | "karma-ng-scenario": "~0.1.0", 69 | "karma-firefox-launcher": "~0.1.3", 70 | "karma-script-launcher": "~0.1.0", 71 | "karma-html2js-preprocessor": "~0.1.0", 72 | "karma-ng-jade2js-preprocessor": "^0.1.2", 73 | "karma-jasmine": "~0.1.5", 74 | "karma-chrome-launcher": "~0.1.3", 75 | "requirejs": "~2.1.11", 76 | "karma-requirejs": "~0.2.1", 77 | "karma-coffee-preprocessor": "~0.2.1", 78 | "karma-jade-preprocessor": "0.0.11", 79 | "karma-phantomjs-launcher": "~0.1.4", 80 | "karma": "~0.12.9", 81 | "karma-ng-html2js-preprocessor": "~0.1.0", 82 | "supertest": "~0.11.0", 83 | "should": "~3.3.1" 84 | }, 85 | "engines": { 86 | "node": ">=0.10.0" 87 | }, 88 | "scripts": { 89 | "start": "node server/app.js", 90 | "test": "grunt test", 91 | "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update" 92 | }, 93 | "private": true 94 | } 95 | -------------------------------------------------------------------------------- /client/app/account/signup/signup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Sign up

7 |
8 |
9 |
10 | 11 |
13 | 14 | 15 | 17 |

18 | A name is required 19 |

20 |
21 | 22 |
24 | 25 | 26 | 29 |

30 | Doesn't look like a valid email. 31 |

32 |

33 | What's your email address? 34 |

35 |

36 | {{ errors.email }} 37 |

38 |
39 | 40 |
42 | 43 | 44 | 48 |

50 | Password must be at least 3 characters. 51 |

52 |

53 | {{ errors.password }} 54 |

55 |
56 | 57 |
58 | 61 | 62 | Login 63 | 64 |
65 | 66 |
67 | 78 |
79 |
80 |
81 |
82 |
83 | -------------------------------------------------------------------------------- /_PRESS-RELEASE.md: -------------------------------------------------------------------------------- 1 | # Project Name # 2 | 3 | 18 | 19 | ## Heading ## 20 | > Name the product in a way the reader (i.e. your target customers) will understand. 21 | 22 | ## Sub-Heading ## 23 | > Describe who the market for the product is and what benefit they get. One sentence only underneath the title. 24 | 25 | ## Summary ## 26 | > Give a summary of the product and the benefit. Assume the reader will not read anything else so make this paragraph good. 27 | 28 | ## Problem ## 29 | > Describe the problem your product solves. 30 | 31 | ## Solution ## 32 | > Describe how your product elegantly solves the problem. 33 | 34 | ## Quote from You ## 35 | > A quote from a spokesperson in your company. 36 | 37 | ## How to Get Started ## 38 | > Describe how easy it is to get started. 39 | 40 | ## Customer Quote ## 41 | > Provide a quote from a hypothetical customer that describes how they experienced the benefit. 42 | 43 | ## Closing and Call to Action ## 44 | > Wrap it up and give pointers where the reader should go next. 45 | -------------------------------------------------------------------------------- /server/api/user/user.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | var crypto = require('crypto'); 6 | var authTypes = ['github', 'twitter', 'facebook', 'google']; 7 | 8 | var UserSchema = new Schema({ 9 | name: String, 10 | email: { type: String, lowercase: true }, 11 | role: { 12 | type: String, 13 | default: 'user' 14 | }, 15 | hashedPassword: String, 16 | provider: String, 17 | salt: String, 18 | facebook: {}, 19 | twitter: {}, 20 | google: {}, 21 | github: {} 22 | }); 23 | 24 | /** 25 | * Virtuals 26 | */ 27 | UserSchema 28 | .virtual('password') 29 | .set(function(password) { 30 | this._password = password; 31 | this.salt = this.makeSalt(); 32 | this.hashedPassword = this.encryptPassword(password); 33 | }) 34 | .get(function() { 35 | return this._password; 36 | }); 37 | 38 | // Public profile information 39 | UserSchema 40 | .virtual('profile') 41 | .get(function() { 42 | return { 43 | 'name': this.name, 44 | 'role': this.role 45 | }; 46 | }); 47 | 48 | // Non-sensitive info we'll be putting in the token 49 | UserSchema 50 | .virtual('token') 51 | .get(function() { 52 | return { 53 | '_id': this._id, 54 | 'role': this.role 55 | }; 56 | }); 57 | 58 | /** 59 | * Validations 60 | */ 61 | 62 | // Validate empty email 63 | UserSchema 64 | .path('email') 65 | .validate(function(email) { 66 | if (authTypes.indexOf(this.provider) !== -1) return true; 67 | return email.length; 68 | }, 'Email cannot be blank'); 69 | 70 | // Validate empty password 71 | UserSchema 72 | .path('hashedPassword') 73 | .validate(function(hashedPassword) { 74 | if (authTypes.indexOf(this.provider) !== -1) return true; 75 | return hashedPassword.length; 76 | }, 'Password cannot be blank'); 77 | 78 | // Validate email is not taken 79 | UserSchema 80 | .path('email') 81 | .validate(function(value, respond) { 82 | var self = this; 83 | this.constructor.findOne({email: value}, function(err, user) { 84 | if(err) throw err; 85 | if(user) { 86 | if(self.id === user.id) return respond(true); 87 | return respond(false); 88 | } 89 | respond(true); 90 | }); 91 | }, 'The specified email address is already in use.'); 92 | 93 | var validatePresenceOf = function(value) { 94 | return value && value.length; 95 | }; 96 | 97 | /** 98 | * Pre-save hook 99 | */ 100 | UserSchema 101 | .pre('save', function(next) { 102 | if (!this.isNew) return next(); 103 | 104 | if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1) 105 | next(new Error('Invalid password')); 106 | else 107 | next(); 108 | }); 109 | 110 | /** 111 | * Methods 112 | */ 113 | UserSchema.methods = { 114 | /** 115 | * Authenticate - check if the passwords are the same 116 | * 117 | * @param {String} plainText 118 | * @return {Boolean} 119 | * @api public 120 | */ 121 | authenticate: function(plainText) { 122 | return this.encryptPassword(plainText) === this.hashedPassword; 123 | }, 124 | 125 | /** 126 | * Make salt 127 | * 128 | * @return {String} 129 | * @api public 130 | */ 131 | makeSalt: function() { 132 | return crypto.randomBytes(16).toString('base64'); 133 | }, 134 | 135 | /** 136 | * Encrypt password 137 | * 138 | * @param {String} password 139 | * @return {String} 140 | * @api public 141 | */ 142 | encryptPassword: function(password) { 143 | if (!password || !this.salt) return ''; 144 | var salt = new Buffer(this.salt, 'base64'); 145 | return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); 146 | } 147 | }; 148 | 149 | module.exports = mongoose.model('User', UserSchema); 150 | -------------------------------------------------------------------------------- /dist/server/api/user/user.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | var crypto = require('crypto'); 6 | var authTypes = ['github', 'twitter', 'facebook', 'google']; 7 | 8 | var UserSchema = new Schema({ 9 | name: String, 10 | email: { type: String, lowercase: true }, 11 | role: { 12 | type: String, 13 | default: 'user' 14 | }, 15 | hashedPassword: String, 16 | provider: String, 17 | salt: String, 18 | facebook: {}, 19 | twitter: {}, 20 | google: {}, 21 | github: {} 22 | }); 23 | 24 | /** 25 | * Virtuals 26 | */ 27 | UserSchema 28 | .virtual('password') 29 | .set(function(password) { 30 | this._password = password; 31 | this.salt = this.makeSalt(); 32 | this.hashedPassword = this.encryptPassword(password); 33 | }) 34 | .get(function() { 35 | return this._password; 36 | }); 37 | 38 | // Public profile information 39 | UserSchema 40 | .virtual('profile') 41 | .get(function() { 42 | return { 43 | 'name': this.name, 44 | 'role': this.role 45 | }; 46 | }); 47 | 48 | // Non-sensitive info we'll be putting in the token 49 | UserSchema 50 | .virtual('token') 51 | .get(function() { 52 | return { 53 | '_id': this._id, 54 | 'role': this.role 55 | }; 56 | }); 57 | 58 | /** 59 | * Validations 60 | */ 61 | 62 | // Validate empty email 63 | UserSchema 64 | .path('email') 65 | .validate(function(email) { 66 | if (authTypes.indexOf(this.provider) !== -1) return true; 67 | return email.length; 68 | }, 'Email cannot be blank'); 69 | 70 | // Validate empty password 71 | UserSchema 72 | .path('hashedPassword') 73 | .validate(function(hashedPassword) { 74 | if (authTypes.indexOf(this.provider) !== -1) return true; 75 | return hashedPassword.length; 76 | }, 'Password cannot be blank'); 77 | 78 | // Validate email is not taken 79 | UserSchema 80 | .path('email') 81 | .validate(function(value, respond) { 82 | var self = this; 83 | this.constructor.findOne({email: value}, function(err, user) { 84 | if(err) throw err; 85 | if(user) { 86 | if(self.id === user.id) return respond(true); 87 | return respond(false); 88 | } 89 | respond(true); 90 | }); 91 | }, 'The specified email address is already in use.'); 92 | 93 | var validatePresenceOf = function(value) { 94 | return value && value.length; 95 | }; 96 | 97 | /** 98 | * Pre-save hook 99 | */ 100 | UserSchema 101 | .pre('save', function(next) { 102 | if (!this.isNew) return next(); 103 | 104 | if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1) 105 | next(new Error('Invalid password')); 106 | else 107 | next(); 108 | }); 109 | 110 | /** 111 | * Methods 112 | */ 113 | UserSchema.methods = { 114 | /** 115 | * Authenticate - check if the passwords are the same 116 | * 117 | * @param {String} plainText 118 | * @return {Boolean} 119 | * @api public 120 | */ 121 | authenticate: function(plainText) { 122 | return this.encryptPassword(plainText) === this.hashedPassword; 123 | }, 124 | 125 | /** 126 | * Make salt 127 | * 128 | * @return {String} 129 | * @api public 130 | */ 131 | makeSalt: function() { 132 | return crypto.randomBytes(16).toString('base64'); 133 | }, 134 | 135 | /** 136 | * Encrypt password 137 | * 138 | * @param {String} password 139 | * @return {String} 140 | * @api public 141 | */ 142 | encryptPassword: function(password) { 143 | if (!password || !this.salt) return ''; 144 | var salt = new Buffer(this.salt, 'base64'); 145 | return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); 146 | } 147 | }; 148 | 149 | module.exports = mongoose.model('User', UserSchema); 150 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- 1 |   �( @   -2Op"=p�Jt��Jt��b���������������������������������������������������b���Jt��Jt��"=p�Op-2O`O�O�O�O�O�O�O� $\�Jt��������������v���v���������������Jt�� $\�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O� ;n�s���>���>���>���>���s��� ;n�O�O�O�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O� $\�]���^n��^n��]��� $\�O�O�O�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O�O�n�*��*��n�O�O�O�O�O�O�O�O�O�O�O�  O�O�O�O�O�O�O�O�O�O�O�5>Y�5>Y�O�O�O�O�O�O�O�O�O�O�O�  -2O�O�O�O�O�O�O�O�O�O�&6e�&6e�O�O�O�O�O�O�O�O�O�O�-25r�4���E��� $\�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O� $\�E���4���5r�5r�E���M���M���v���0\��O�O�O�O�O�O�O� $\� $\�O�O�O�O�O�O�O�0\��v���M���M���E���5r�)��p&��p��&��������������b���Jt��Jt��Jt��0\��#i��.r��.r��#i��0\��Jt��Jt��Jt��b���������������&��p��&��)��p4���&��-���_������������������]���]�������7���p�����������p���7�������]���]�������������������_��-���-���4���qֈp��p��p����������������������p���7���#i��p�����������p���#i��7���p�����������������������p��&��-���qֈ8��(p��p��I���v���v���]���7���n���v���p���#i��]���v���v���]���#i��p���v���n���7���]���v���v���I���-���-���8��(;��`-���M���7���7���7���.r��R��E��R��E��7���7���7���7���E��R��E��R��.r��7���7���7���M���M���;��`���������������������������z��������������������������� 2 | �  ��� 3 | � 9� 9� 9� 9� 9� 9� 9� 9� 4 |  �n�n� 5 |  � 9� 9� 9� 9� 9� 9� 9� 9� 6 | ����*�x*��*��*��*��*��*��*��n�&��#��&��&��n�*��*��*��*��*��*��*��*�x*ݟ*��*��*��*��*��*��!��#��&��#��&��*��!��!��*��*��*��*��*��*��*ݟ*ݿ*��*��*��*��*��*��n�*��*�� 9� 9�*��*���*��*��*��*��*��*��*ݿ*��*��*��*��*��*��*��!��#��&��&��&��*��#��!��*��*��*��*��*��*��*��  ��������I�&��&��&��&��I���������  U��������� 7 |  �n�n� 8 |  ����������-2z����������������������z������������������������ 9 | ����������������������� 10 | ������������������������� 11 | ������������������������-2����������������������U�������������������z5r������������������-25r�U�����������z  ������������������������������?��� -------------------------------------------------------------------------------- /client/app/landing/js/headroom.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * headroom.js v0.7.0 - Give your page some headroom. Hide your header until you need it 3 | * Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/headroom.js 4 | * License: MIT 5 | */ 6 | 7 | !function(a,b){"use strict";function c(a){this.callback=a,this.ticking=!1}function d(b){return b&&"undefined"!=typeof a&&(b===a||b.nodeType)}function e(a){if(arguments.length<=0)throw new Error("Missing arguments in extend function");var b,c,f=a||{};for(c=1;ca,c=a+this.getViewportHeight()>this.getScrollerHeight();return b||c},toleranceExceeded:function(a,b){return Math.abs(a-this.lastKnownScrollY)>=this.tolerance[b]},shouldUnpin:function(a,b){var c=a>this.lastKnownScrollY,d=a>=this.offset;return c&&d&&b},shouldPin:function(a,b){var c=athis.lastKnownScrollY?"down":"up",c=this.toleranceExceeded(a,b);this.isOutOfBounds(a)||(a<=this.offset?this.top():this.notTop(),this.shouldUnpin(a,c)?this.unpin():this.shouldPin(a,c)&&this.pin(),this.lastKnownScrollY=a)}},g.options={tolerance:{up:0,down:0},offset:0,scroller:a,classes:{pinned:"headroom--pinned",unpinned:"headroom--unpinned",top:"headroom--top",notTop:"headroom--not-top",initial:"headroom"}},g.cutsTheMustard="undefined"!=typeof h&&h.rAF&&h.bind&&h.classList,a.Headroom=g}(window,document); -------------------------------------------------------------------------------- /client/components/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('meetMeInTheMiddleApp') 4 | .factory('Auth', function Auth($location, $rootScope, $http, User, $cookieStore, $q) { 5 | var currentUser = {}; 6 | if($cookieStore.get('token')) { 7 | currentUser = User.get(); 8 | } 9 | 10 | return { 11 | 12 | /** 13 | * Authenticate user and save token 14 | * 15 | * @param {Object} user - login info 16 | * @param {Function} callback - optional 17 | * @return {Promise} 18 | */ 19 | login: function(user, callback) { 20 | var cb = callback || angular.noop; 21 | var deferred = $q.defer(); 22 | 23 | $http.post('/auth/local', { 24 | email: user.email, 25 | password: user.password 26 | }). 27 | success(function(data) { 28 | $cookieStore.put('token', data.token); 29 | currentUser = User.get(); 30 | deferred.resolve(data); 31 | return cb(); 32 | }). 33 | error(function(err) { 34 | this.logout(); 35 | deferred.reject(err); 36 | return cb(err); 37 | }.bind(this)); 38 | 39 | return deferred.promise; 40 | }, 41 | 42 | /** 43 | * Delete access token and user info 44 | * 45 | * @param {Function} 46 | */ 47 | logout: function() { 48 | $cookieStore.remove('token'); 49 | currentUser = {}; 50 | }, 51 | 52 | /** 53 | * Create a new user 54 | * 55 | * @param {Object} user - user info 56 | * @param {Function} callback - optional 57 | * @return {Promise} 58 | */ 59 | createUser: function(user, callback) { 60 | var cb = callback || angular.noop; 61 | 62 | return User.save(user, 63 | function(data) { 64 | $cookieStore.put('token', data.token); 65 | currentUser = User.get(); 66 | return cb(user); 67 | }, 68 | function(err) { 69 | this.logout(); 70 | return cb(err); 71 | }.bind(this)).$promise; 72 | }, 73 | 74 | /** 75 | * Change password 76 | * 77 | * @param {String} oldPassword 78 | * @param {String} newPassword 79 | * @param {Function} callback - optional 80 | * @return {Promise} 81 | */ 82 | changePassword: function(oldPassword, newPassword, callback) { 83 | var cb = callback || angular.noop; 84 | 85 | return User.changePassword({ id: currentUser._id }, { 86 | oldPassword: oldPassword, 87 | newPassword: newPassword 88 | }, function(user) { 89 | return cb(user); 90 | }, function(err) { 91 | return cb(err); 92 | }).$promise; 93 | }, 94 | 95 | /** 96 | * Gets all available info on authenticated user 97 | * 98 | * @return {Object} user 99 | */ 100 | getCurrentUser: function() { 101 | return currentUser; 102 | }, 103 | 104 | /** 105 | * Check if a user is logged in 106 | * 107 | * @return {Boolean} 108 | */ 109 | isLoggedIn: function() { 110 | return currentUser.hasOwnProperty('role'); 111 | }, 112 | 113 | /** 114 | * Waits for currentUser to resolve before checking if user is logged in 115 | */ 116 | isLoggedInAsync: function(cb) { 117 | if(currentUser.hasOwnProperty('$promise')) { 118 | currentUser.$promise.then(function() { 119 | cb(true); 120 | }).catch(function() { 121 | cb(false); 122 | }); 123 | } else if(currentUser.hasOwnProperty('role')) { 124 | cb(true); 125 | } else { 126 | cb(false); 127 | } 128 | }, 129 | 130 | /** 131 | * Check if a user is an admin 132 | * 133 | * @return {Boolean} 134 | */ 135 | isAdmin: function() { 136 | return currentUser.role === 'admin'; 137 | }, 138 | 139 | /** 140 | * Get auth token 141 | */ 142 | getToken: function() { 143 | return $cookieStore.get('token'); 144 | } 145 | }; 146 | }); 147 | -------------------------------------------------------------------------------- /server/views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 |
    148 |
  • a mistyped address
  • 149 |
  • an out-of-date link
  • 150 |
151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /dist/server/views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 |
    148 |
  • a mistyped address
  • 149 |
  • an out-of-date link
  • 150 |
151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /client/app/google-maps/google-maps.html: -------------------------------------------------------------------------------- 1 |
2 | 31 | 32 | 59 | 60 | 61 |
62 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 |
75 |

{{marker.name}}

76 | {{marker.address}}
77 | {{"Place ID: " + marker.placeID}}
78 | {{"Latitude: " + marker.coords.latitude}}
79 | {{"Longitude: " + marker.coords.longitude}}
80 | Set New Position 81 |
82 |
83 |
84 | 85 | 91 | 92 | 93 |
94 |

95 |
96 | {{countTest}} 97 |
98 | -------------------------------------------------------------------------------- /client/components/navbar/navbar.html: -------------------------------------------------------------------------------- 1 | 31 | 32 | 87 | -------------------------------------------------------------------------------- /client/app/landing/js/jquery.countdown.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * The Final Countdown for jQuery v2.0.4 (http://hilios.github.io/jQuery.countdown/) 3 | * Copyright (c) 2014 Edson Hilios 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | * this software and associated documentation files (the "Software"), to deal in 7 | * the Software without restriction, including without limitation the rights to 8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | * the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | !function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"use strict";function b(a){if(a instanceof Date)return a;if(String(a).match(g))return String(a).match(/^[0-9]*$/)&&(a=Number(a)),String(a).match(/\-/)&&(a=String(a).replace(/\-/g,"/")),new Date(a);throw new Error("Couldn't cast `"+a+"` to a date object.")}function c(a){return function(b){var c=b.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);if(c)for(var e=0,f=c.length;f>e;++e){var g=c[e].match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/),i=new RegExp(g[0]),j=g[1]||"",k=g[3]||"",l=null;g=g[2],h.hasOwnProperty(g)&&(l=h[g],l=Number(a[l])),null!==l&&("!"===j&&(l=d(k,l)),""===j&&10>l&&(l="0"+l.toString()),b=b.replace(i,l.toString()))}return b=b.replace(/%%/,"%")}}function d(a,b){var c="s",d="";return a&&(a=a.replace(/(:|;|\s)/gi,"").split(/\,/),1===a.length?c=a[0]:(d=a[0],c=a[1])),1===Math.abs(b)?d:c}var e=100,f=[],g=[];g.push(/^[0-9]*$/.source),g.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g=new RegExp(g.join("|"));var h={Y:"years",m:"months",w:"weeks",d:"days",D:"totalDays",H:"hours",M:"minutes",S:"seconds"},i=function(b,c,d){this.el=b,this.$el=a(b),this.interval=null,this.offset={},this.instanceNumber=f.length,f.push(this),this.$el.data("countdown-instance",this.instanceNumber),d&&(this.$el.on("update.countdown",d),this.$el.on("stoped.countdown",d),this.$el.on("finish.countdown",d)),this.setFinalDate(c),this.start()};a.extend(i.prototype,{start:function(){null!==this.interval&&clearInterval(this.interval);var a=this;this.update(),this.interval=setInterval(function(){a.update.call(a)},e)},stop:function(){clearInterval(this.interval),this.interval=null,this.dispatchEvent("stoped")},pause:function(){this.stop.call(this)},resume:function(){this.start.call(this)},remove:function(){this.stop(),f[this.instanceNumber]=null,delete this.$el.data().countdownInstance},setFinalDate:function(a){this.finalDate=b(a)},update:function(){return 0===this.$el.closest("html").length?void this.remove():(this.totalSecsLeft=this.finalDate.getTime()-(new Date).getTime(),this.totalSecsLeft=Math.ceil(this.totalSecsLeft/1e3),this.totalSecsLeft=this.totalSecsLeft<0?0:this.totalSecsLeft,this.offset={seconds:this.totalSecsLeft%60,minutes:Math.floor(this.totalSecsLeft/60)%60,hours:Math.floor(this.totalSecsLeft/60/60)%24,days:Math.floor(this.totalSecsLeft/60/60/24)%7,totalDays:Math.floor(this.totalSecsLeft/60/60/24),weeks:Math.floor(this.totalSecsLeft/60/60/24/7),months:Math.floor(this.totalSecsLeft/60/60/24/30),years:Math.floor(this.totalSecsLeft/60/60/24/365)},void(0===this.totalSecsLeft?(this.stop(),this.dispatchEvent("finish")):this.dispatchEvent("update")))},dispatchEvent:function(b){var d=a.Event(b+".countdown");d.finalDate=this.finalDate,d.offset=a.extend({},this.offset),d.strftime=c(this.offset),this.$el.trigger(d)}}),a.fn.countdown=function(){var b=Array.prototype.slice.call(arguments,0);return this.each(function(){var c=a(this).data("countdown-instance");if(void 0!==c){var d=f[c],e=b[0];i.prototype.hasOwnProperty(e)?d[e].apply(d,b.slice(1)):null===String(e).match(/^[$A-Z_][0-9A-Z_$]*$/i)?(d.setFinalDate.call(d,e),d.start()):a.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi,e))}else new i(this,b[0],b[1])})}}); 23 | 24 | ///// HEADER - COUNTDOWN ///// 25 | //only for demo - gets days three weeks from now 26 | function getLastWeek(){ 27 | var today = new Date(); 28 | var lastWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 21); 29 | return lastWeek ; 30 | } 31 | 32 | var lastWeek = getLastWeek(); 33 | var lastWeekMonth = lastWeek.getMonth() + 1; 34 | var lastWeekDay = lastWeek.getDate(); 35 | var lastWeekYear = lastWeek.getFullYear(); 36 | 37 | var lastWeekDisplay = lastWeekMonth + "/" + lastWeekDay + "/" + lastWeekYear; 38 | var lastWeekDisplayPadded = ("00" + lastWeekYear .toString()).slice(-4) + "/" + ("00" + lastWeekMonth.toString()).slice(-2)+ "/" + ("00" + lastWeekDay .toString()).slice(-2); 39 | 40 | //coundown script - the date format is YYYY/MM/DD 41 | $("#countdown").countdown(lastWeekDisplayPadded).on('update.countdown', function(event) { 42 | var $this = $(this).html(event.strftime('' 43 | + '

%-D

day%!d
' 44 | + '

%H

hour%!H
' 45 | + '

%M

minute%!M
' 46 | + '

%S

second%!S
')); 47 | }); -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 57 |
58 |
59 |
60 | 61 | 62 | 63 |
64 | 65 | 66 | 75 | 76 | 77 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /_CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## General Workflow 4 | 5 | 1. Fork the repo 6 | 1. Cut a namespaced feature branch from master 7 | - bug/... 8 | - feat/... 9 | - test/... 10 | - doc/... 11 | - refactor/... 12 | 1. Make commits to your feature branch. Prefix each commit like so: 13 | - (feat) Added a new feature 14 | - (fix) Fixed inconsistent tests [Fixes #0] 15 | - (refactor) ... 16 | - (cleanup) ... 17 | - (test) ... 18 | - (doc) ... 19 | 1. When you've finished with your fix or feature, Rebase upstream changes into your branch. submit a [pull request][] 20 | directly to master. Include a description of your changes. 21 | 1. Your pull request will be reviewed by another maintainer. The point of code 22 | reviews is to help keep the codebase clean and of high quality and, equally 23 | as important, to help you grow as a programmer. If your code reviewer 24 | requests you make a change you don't understand, ask them why. 25 | 1. Fix any issues raised by your code reviwer, and push your fixes as a single 26 | new commit. 27 | 1. Once the pull request has been reviewed, it will be merged by another member of the team. Do not merge your own commits. 28 | 29 | ## Detailed Workflow 30 | 31 | ### Fork the repo 32 | 33 | Use github’s interface to make a fork of the repo, then add that repo as an upstream remote: 34 | 35 | ``` 36 | git remote add upstream https://github.com/hackreactor-labs/.git 37 | ``` 38 | 39 | ### Cut a namespaced feature branch from master 40 | 41 | Your branch should follow this naming convention: 42 | - bug/... 43 | - feat/... 44 | - test/... 45 | - doc/... 46 | - refactor/... 47 | 48 | These commands will help you do this: 49 | 50 | ``` bash 51 | 52 | # Creates your branch and brings you there 53 | git checkout -b `your-branch-name` 54 | ``` 55 | 56 | ### Make commits to your feature branch. 57 | 58 | Prefix each commit like so 59 | - (feat) Added a new feature 60 | - (fix) Fixed inconsistent tests [Fixes #0] 61 | - (refactor) ... 62 | - (cleanup) ... 63 | - (test) ... 64 | - (doc) ... 65 | 66 | Make changes and commits on your branch, and make sure that you 67 | only make changes that are relevant to this branch. If you find 68 | yourself making unrelated changes, make a new branch for those 69 | changes. 70 | 71 | #### Commit Message Guidelines 72 | 73 | - Commit messages should be written in the present tense; e.g. "Fix continuous 74 | integration script". 75 | - The first line of your commit message should be a brief summary of what the 76 | commit changes. Aim for about 70 characters max. Remember: This is a summary, 77 | not a detailed description of everything that changed. 78 | - If you want to explain the commit in more depth, following the first line should 79 | be a blank line and then a more detailed description of the commit. This can be 80 | as detailed as you want, so dig into details here and keep the first line short. 81 | 82 | ### Rebase upstream changes into your branch 83 | 84 | Once you are done making changes, you can begin the process of getting 85 | your code merged into the main repo. Step 1 is to rebase upstream 86 | changes to the master branch into yours by running this command 87 | from your branch: 88 | 89 | ```bash 90 | git pull --rebase upstream master 91 | ``` 92 | 93 | This will start the rebase process. You must commit all of your changes 94 | before doing this. If there are no conflicts, this should just roll all 95 | of your changes back on top of the changes from upstream, leading to a 96 | nice, clean, linear commit history. 97 | 98 | If there are conflicting changes, git will start yelling at you part way 99 | through the rebasing process. Git will pause rebasing to allow you to sort 100 | out the conflicts. You do this the same way you solve merge conflicts, 101 | by checking all of the files git says have been changed in both histories 102 | and picking the versions you want. Be aware that these changes will show 103 | up in your pull request, so try and incorporate upstream changes as much 104 | as possible. 105 | 106 | You pick a file by `git add`ing it - you do not make commits during a 107 | rebase. 108 | 109 | Once you are done fixing conflicts for a specific commit, run: 110 | 111 | ```bash 112 | git rebase --continue 113 | ``` 114 | 115 | This will continue the rebasing process. Once you are done fixing all 116 | conflicts you should run the existing tests to make sure you didn’t break 117 | anything, then run your new tests (there are new tests, right?) and 118 | make sure they work also. 119 | 120 | If rebasing broke anything, fix it, then repeat the above process until 121 | you get here again and nothing is broken and all the tests pass. 122 | 123 | ### Make a pull request 124 | 125 | Make a clear pull request from your fork and branch to the upstream master 126 | branch, detailing exactly what changes you made and what feature this 127 | should add. The clearer your pull request is the faster you can get 128 | your changes incorporated into this repo. 129 | 130 | At least one other person MUST give your changes a code review, and once 131 | they are satisfied they will merge your changes into upstream. Alternatively, 132 | they may have some requested changes. You should make more commits to your 133 | branch to fix these, then follow this process again from rebasing onwards. 134 | 135 | Once you get back here, make a comment requesting further review and 136 | someone will look at your code again. If they like it, it will get merged, 137 | else, just repeat again. 138 | 139 | Thanks for contributing! 140 | 141 | ### Guidelines 142 | 143 | 1. Uphold the current code standard: 144 | - Keep your code [DRY][]. 145 | - Apply the [boy scout rule][]. 146 | - Follow [STYLE-GUIDE.md](STYLE-GUIDE.md) 147 | 1. Run the [tests][] before submitting a pull request. 148 | 1. Tests are very, very important. Submit tests if your pull request contains 149 | new, testable behavior. 150 | 1. Your pull request is comprised of a single ([squashed][]) commit. 151 | 152 | ## Checklist: 153 | 154 | This is just to help you organize your process 155 | 156 | - [ ] Did I cut my work branch off of master (don't cut new branches from existing feature brances)? 157 | - [ ] Did I follow the correct naming convention for my branch? 158 | - [ ] Is my branch focused on a single main change? 159 | - [ ] Do all of my changes directly relate to this change? 160 | - [ ] Did I rebase the upstream master branch after I finished all my 161 | work? 162 | - [ ] Did I write a clear pull request message detailing what changes I made? 163 | - [ ] Did I get a code review? 164 | - [ ] Did I make any requested changes from that code review? 165 | 166 | If you follow all of these guidelines and make good changes, you should have 167 | no problem getting your changes merged in. 168 | 169 | 170 | 171 | [style guide]: https://github.com/hackreactor-labs/style-guide 172 | [n-queens]: https://github.com/hackreactor-labs/n-queens 173 | [Underbar]: https://github.com/hackreactor-labs/underbar 174 | [curriculum workflow diagram]: http://i.imgur.com/p0e4tQK.png 175 | [cons of merge]: https://f.cloud.github.com/assets/1577682/1458274/1391ac28-435e-11e3-88b6-69c85029c978.png 176 | [Bookstrap]: https://github.com/hackreactor/bookstrap 177 | [Taser]: https://github.com/hackreactor/bookstrap 178 | [tools workflow diagram]: http://i.imgur.com/kzlrDj7.png 179 | [Git Flow]: http://nvie.com/posts/a-successful-git-branching-model/ 180 | [GitHub Flow]: http://scottchacon.com/2011/08/31/github-flow.html 181 | [Squash]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html 182 | -------------------------------------------------------------------------------- /client/app/landing/js/validator.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap (plugin): validator.js v0.6.0 3 | * ======================================================================== 4 | * The MIT License (MIT) 5 | * 6 | * Copyright (c) 2013 Spiceworks, Inc. 7 | * Made by Cina Saffary (@1000hz) in the style of Bootstrap 3 era @fat 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * ======================================================================== */ 27 | 28 | 29 | +function ($) { 30 | 'use strict'; 31 | 32 | // VALIDATOR CLASS DEFINITION 33 | // ========================== 34 | 35 | var Validator = function (element, options) { 36 | this.$element = $(element) 37 | this.options = options 38 | 39 | this.$element.attr('novalidate', true) // disable automatic native validation 40 | this.toggleSubmit() 41 | 42 | this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', $.proxy(this.validateInput, this)) 43 | this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this)) 44 | 45 | this.$element.find('[data-match]').each(function () { 46 | var $this = $(this) 47 | var target = $this.data('match') 48 | 49 | $(target).on('input.bs.validator', function (e) { 50 | $this.val() && $this.trigger('input') 51 | }) 52 | }) 53 | } 54 | 55 | Validator.DEFAULTS = { 56 | delay: 500, 57 | html: false, 58 | disable: true, 59 | errors: { 60 | match: 'Does not match', 61 | minlength: 'Not long enough' 62 | } 63 | } 64 | 65 | Validator.VALIDATORS = { 66 | native: function ($el) { 67 | var el = $el[0] 68 | return el.checkValidity ? el.checkValidity() : true 69 | }, 70 | match: function ($el) { 71 | var target = $el.data('match') 72 | return !$el.val() || $el.val() === $(target).val() 73 | }, 74 | minlength: function ($el) { 75 | var minlength = $el.data('minlength') 76 | return !$el.val() || $el.val().length >= minlength 77 | } 78 | } 79 | 80 | Validator.prototype.validateInput = function (e) { 81 | var $el = $(e.target) 82 | var prevErrors = $el.data('bs.validator.errors') 83 | var errors 84 | 85 | if ($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]') 86 | 87 | this.$element.trigger(e = $.Event('validate.bs.validator', {relatedTarget: $el[0]})) 88 | 89 | if (e.isDefaultPrevented()) return 90 | 91 | var self = this 92 | 93 | this.runValidators($el).done(function (errors) { 94 | $el.data('bs.validator.errors', errors) 95 | 96 | errors.length ? self.showErrors($el) : self.clearErrors($el) 97 | 98 | if (!prevErrors || errors.toString() !== prevErrors.toString()) { 99 | e = errors.length 100 | ? $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors}) 101 | : $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors}) 102 | 103 | self.$element.trigger(e) 104 | } 105 | 106 | self.toggleSubmit() 107 | 108 | self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]})) 109 | }) 110 | } 111 | 112 | 113 | Validator.prototype.runValidators = function ($el) { 114 | var errors = [] 115 | var validators = [Validator.VALIDATORS.native] 116 | var deferred = $.Deferred() 117 | var options = this.options 118 | 119 | $el.data('bs.validator.deferred') && $el.data('bs.validator.deferred').reject() 120 | $el.data('bs.validator.deferred', deferred) 121 | 122 | function getErrorMessage(key) { 123 | return $el.data(key + '-error') 124 | || $el.data('error') 125 | || key == 'native' && $el[0].validationMessage 126 | || options.errors[key] 127 | } 128 | 129 | $.each(Validator.VALIDATORS, $.proxy(function (key, validator) { 130 | if (($el.data(key) || key == 'native') && !validator.call(this, $el)) { 131 | var error = getErrorMessage(key) 132 | !~errors.indexOf(error) && errors.push(error) 133 | } 134 | }, this)) 135 | 136 | if (!errors.length && $el.val() && $el.data('remote')) { 137 | this.defer($el, function () { 138 | $.get($el.data('remote'), [$el.attr('name'), $el.val()].join('=')) 139 | .fail(function (jqXHR, textStatus, error) { errors.push(getErrorMessage('remote') || error) }) 140 | .always(function () { deferred.resolve(errors)}) 141 | }) 142 | } else deferred.resolve(errors) 143 | 144 | return deferred.promise() 145 | } 146 | 147 | Validator.prototype.validate = function () { 148 | var delay = this.options.delay 149 | 150 | this.options.delay = 0 151 | this.$element.find(':input').trigger('input') 152 | this.options.delay = delay 153 | 154 | return this 155 | } 156 | 157 | Validator.prototype.showErrors = function ($el) { 158 | var method = this.options.html ? 'html' : 'text' 159 | 160 | this.defer($el, function () { 161 | var $group = $el.closest('.form-group') 162 | var $block = $group.find('.help-block.with-errors') 163 | var errors = $el.data('bs.validator.errors') 164 | 165 | if (!errors.length) return 166 | 167 | errors = $('
    ') 168 | .addClass('list-unstyled') 169 | .append($.map(errors, function (error) { return $('
  • ')[method](error) })) 170 | 171 | $block.data('bs.validator.originalContent') === undefined && $block.data('bs.validator.originalContent', $block.html()) 172 | $block.empty().append(errors) 173 | 174 | $group.addClass('has-error') 175 | }) 176 | } 177 | 178 | Validator.prototype.clearErrors = function ($el) { 179 | var $group = $el.closest('.form-group') 180 | var $block = $group.find('.help-block.with-errors') 181 | 182 | $block.html($block.data('bs.validator.originalContent')) 183 | $group.removeClass('has-error') 184 | } 185 | 186 | Validator.prototype.hasErrors = function () { 187 | function fieldErrors() { 188 | return !!($(this).data('bs.validator.errors') || []).length 189 | } 190 | 191 | return !!this.$element.find(':input:enabled').filter(fieldErrors).length 192 | } 193 | 194 | Validator.prototype.isIncomplete = function () { 195 | function fieldIncomplete() { 196 | return this.type === 'checkbox' ? !this.checked : 197 | this.type === 'radio' ? !$('[name="' + this.name + '"]:checked').length : 198 | $.trim(this.value) === '' 199 | } 200 | 201 | return !!this.$element.find(':input[required]:enabled').filter(fieldIncomplete).length 202 | } 203 | 204 | Validator.prototype.onSubmit = function (e) { 205 | this.validate() 206 | if (this.isIncomplete() || this.hasErrors()) e.preventDefault() 207 | } 208 | 209 | Validator.prototype.toggleSubmit = function () { 210 | if(!this.options.disable) return 211 | var $btn = this.$element.find('input[type="submit"], button[type="submit"]') 212 | $btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors()) 213 | .css({'pointer-events': 'all', 'cursor': 'pointer'}) 214 | } 215 | 216 | Validator.prototype.defer = function ($el, callback) { 217 | if (!this.options.delay) return callback() 218 | window.clearTimeout($el.data('bs.validator.timeout')) 219 | $el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay)) 220 | } 221 | 222 | Validator.prototype.destroy = function () { 223 | this.$element 224 | .removeAttr('novalidate') 225 | .removeData('bs.validator') 226 | .off('.bs.validator') 227 | 228 | this.$element.find(':input') 229 | .removeData(['bs.validator.errors', 'bs.validator.deferred', 'bs.validator.timeout']) 230 | .off('.bs.validator') 231 | 232 | this.$element.find('.help-block.with-errors').each(function () { 233 | var $this = $(this) 234 | var originalContent = $this.data('bs.validator.originalContent') 235 | 236 | $this 237 | .removeData('bs.validator.originalContent') 238 | .html(originalContent) 239 | }) 240 | 241 | this.$element.find('input[type="submit"], button[type="submit"]').removeClass('disabled') 242 | 243 | this.$element.find('.has-error').removeClass('has-error') 244 | 245 | return this 246 | } 247 | 248 | // VALIDATOR PLUGIN DEFINITION 249 | // =========================== 250 | 251 | 252 | function Plugin(option) { 253 | return this.each(function () { 254 | var $this = $(this) 255 | var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option) 256 | var data = $this.data('bs.validator') 257 | 258 | if (!data && option == 'destroy') return 259 | if (!data) $this.data('bs.validator', (data = new Validator(this, options))) 260 | if (typeof option == 'string') data[option]() 261 | }) 262 | } 263 | 264 | var old = $.fn.validator 265 | 266 | $.fn.validator = Plugin 267 | $.fn.validator.Constructor = Validator 268 | 269 | 270 | // VALIDATOR NO CONFLICT 271 | // ===================== 272 | 273 | $.fn.validator.noConflict = function () { 274 | $.fn.validator = old 275 | return this 276 | } 277 | 278 | 279 | // VALIDATOR DATA-API 280 | // ================== 281 | 282 | $(window).on('load', function () { 283 | $('form[data-toggle="validator"]').each(function () { 284 | var $form = $(this) 285 | Plugin.call($form, $form.data()) 286 | }) 287 | }) 288 | 289 | }(jQuery); 290 | --------------------------------------------------------------------------------