├── .slugignore ├── .bowerrc ├── Procfile ├── public ├── robots.txt ├── modules │ ├── core │ │ ├── css │ │ │ ├── navbar-static-top.css │ │ │ ├── angular-ui.css │ │ │ ├── justified-nav.css │ │ │ ├── sticky-footer.css │ │ │ ├── core.css │ │ │ └── generic.css │ │ ├── images │ │ │ ├── Map@2x.png │ │ │ ├── mean.png │ │ │ ├── Dude@2x.png │ │ │ ├── Girl@2x.png │ │ │ ├── Goal@2x.png │ │ │ ├── Graph@2x.png │ │ │ ├── Yelp-32.png │ │ │ ├── BreweryDB.png │ │ │ ├── Facebook-32.png │ │ │ ├── Pensils@2x.png │ │ │ ├── Search@2x.png │ │ │ ├── Settings@2x.png │ │ │ ├── Shield@2x.png │ │ │ ├── Twitter-32.png │ │ │ ├── meanjs_logo.png │ │ │ ├── Foursquare-32.png │ │ │ ├── GooglePlus-32.png │ │ │ ├── Instagram-32.png │ │ │ ├── Pinterest-32.png │ │ │ ├── YouTube_01-32.png │ │ │ ├── hack_reactor.png │ │ │ ├── predictionio.jpeg │ │ │ ├── BreweryDB_logo.png │ │ │ ├── Facebook_Like-32.png │ │ │ ├── no-beer-label-60.png │ │ │ └── on-tapp-165x165.png │ │ ├── img │ │ │ ├── brand │ │ │ │ ├── logo.png │ │ │ │ └── favicon.ico │ │ │ └── loaders │ │ │ │ └── loader.gif │ │ ├── core.client.module.js │ │ ├── controllers │ │ │ ├── home.client.controller.js │ │ │ ├── header.client.controller.js │ │ │ └── search.client.controller.js │ │ ├── services │ │ │ ├── search.client.service.js │ │ │ └── menus.client.service.js │ │ ├── config │ │ │ └── core.client.routes.js │ │ ├── tests │ │ │ ├── home.client.controller.test.js │ │ │ ├── header.client.controller.test.js │ │ │ └── search.client.controller.test.js │ │ └── views │ │ │ ├── search.client.view.html │ │ │ ├── home.client.view.html │ │ │ └── header.client.view.html │ ├── nearby │ │ ├── images │ │ │ └── beer-icon.png │ │ ├── nearby.client.module.js │ │ ├── config │ │ │ ├── nearby.client.config.js │ │ │ └── nearby.client.routes.js │ │ ├── services │ │ │ ├── brewery.client.service.js │ │ │ └── breweries.client.service.js │ │ ├── controllers │ │ │ ├── brewery.client.controller.js │ │ │ └── nearby.client.controller.js │ │ ├── css │ │ │ └── map.css │ │ ├── tests │ │ │ ├── brewery.client.controller.test.js │ │ │ └── nearby.client.controller.test.js │ │ └── views │ │ │ ├── nearby.client.view.html │ │ │ └── brewery.client.view.html │ ├── users │ │ ├── img │ │ │ └── buttons │ │ │ │ ├── github.png │ │ │ │ ├── google.png │ │ │ │ ├── twitter.png │ │ │ │ ├── facebook.png │ │ │ │ └── linkedin.png │ │ ├── users.client.module.js │ │ ├── views │ │ │ ├── password │ │ │ │ ├── reset-password-success.client.view.html │ │ │ │ ├── reset-password-invalid.client.view.html │ │ │ │ ├── forgot-password.client.view.html │ │ │ │ └── reset-password.client.view.html │ │ │ ├── settings │ │ │ │ ├── social-accounts.client.view.html │ │ │ │ ├── change-password.client.view.html │ │ │ │ └── edit-profile.client.view.html │ │ │ └── authentication │ │ │ │ ├── signin.client.view.html │ │ │ │ └── signup.client.view.html │ │ ├── css │ │ │ └── users.css │ │ ├── services │ │ │ ├── authentication.client.service.js │ │ │ └── users.client.service.js │ │ ├── config │ │ │ ├── users.client.config.js │ │ │ └── users.client.routes.js │ │ ├── controllers │ │ │ ├── authentication.client.controller.js │ │ │ ├── password.client.controller.js │ │ │ └── settings.client.controller.js │ │ └── tests │ │ │ └── authentication.client.controller.test.js │ ├── beers │ │ ├── beers.client.module.js │ │ ├── services │ │ │ ├── beer.client.service.js │ │ │ └── beers.client.service.js │ │ ├── controllers │ │ │ ├── beer.client.controller.js │ │ │ └── beers.client.controller.js │ │ ├── config │ │ │ └── beers.client.routes.js │ │ ├── views │ │ │ ├── beers.client.view.html │ │ │ └── beer.client.view.html │ │ └── tests │ │ │ ├── beer.client.controller.test.js │ │ │ └── beers.client.controller.test.js │ └── ratings │ │ ├── ratings.client.module.js │ │ ├── config │ │ ├── ratings.client.config.js │ │ └── ratings.client.routes.js │ │ ├── services │ │ ├── stylequery.client.service.js │ │ └── ratings.client.service.js │ │ ├── views │ │ ├── edit-rating.client.view.html │ │ ├── list-ratings.client.view.html │ │ └── view-rating.client.view.html │ │ ├── controllers │ │ └── ratings.client.controller.js │ │ └── tests │ │ └── ratings.client.controller.test.js ├── humans.txt ├── application.js ├── config.js └── dist │ └── application.min.css ├── api-key-example.js ├── api-key.js ├── .travis.yml ├── app ├── views │ ├── index.server.view.html │ ├── 500.server.view.html │ ├── 404.server.view.html │ ├── templates │ │ ├── reset-password-confirm-email.server.view.html │ │ └── reset-password-email.server.view.html │ └── layout.server.view.html ├── routes │ ├── core.server.routes.js │ ├── beer.server.routes.js │ ├── beers.server.routes.js │ ├── brewery.server.routes.js │ ├── breweries.server.routes.js │ ├── search.server.routes.js │ ├── ratings.server.routes.js │ └── users.server.routes.js ├── controllers │ ├── core.server.controller.js │ ├── users.server.controller.js │ ├── errors.server.controller.js │ ├── users │ │ ├── users.authorization.server.controller.js │ │ ├── users.profile.server.controller.js │ │ ├── users.authentication.server.controller.js │ │ └── users.password.server.controller.js │ └── ratings.server.controller.js ├── models │ ├── rating.server.model.js │ └── user.server.model.js └── tests │ ├── rating.server.model.test.js │ └── user.server.model.test.js ├── fig.yml ├── circle.yml ├── .csslintrc ├── config ├── logger.js ├── passport.js ├── init.js ├── strategies │ ├── local.js │ ├── twitter.js │ ├── google.js │ └── facebook.js ├── config.js ├── env │ ├── development.js │ ├── test.js │ ├── production.js │ ├── secure.js │ └── all.js └── express.js ├── newrelic.js ├── scripts └── generate-ssl-certs.sh ├── Dockerfile ├── .gitignore ├── .editorconfig ├── bower.json ├── LICENSE.md ├── params.json ├── README.md ├── PRESS-RELEASE.md ├── server.js ├── karma.conf.js ├── .jshintrc ├── package.json ├── Gruntfile.js └── CONTRIBUTING.md /.slugignore: -------------------------------------------------------------------------------- 1 | /app/tests 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/lib" 3 | } 4 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: ./node_modules/.bin/forever -m 5 server.js 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /api-key-example.js: -------------------------------------------------------------------------------- 1 | exports.keys = { 2 | brewerydb : ' PUT YOUR API KEY HERE' 3 | }; 4 | -------------------------------------------------------------------------------- /api-key.js: -------------------------------------------------------------------------------- 1 | exports.keys = { 2 | brewerydb : '87f92c3b32c0d8e031f828b0d03c2c2a' 3 | }; 4 | -------------------------------------------------------------------------------- /public/modules/core/css/navbar-static-top.css: -------------------------------------------------------------------------------- 1 | .navbar-static-top { 2 | margin-bottom: 0; 3 | } 4 | -------------------------------------------------------------------------------- /public/modules/core/css/angular-ui.css: -------------------------------------------------------------------------------- 1 | .nav, .pagination, .carousel, .panel-title a { cursor: pointer; } 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | env: 5 | - NODE_ENV=travis 6 | services: 7 | - mongodb 8 | -------------------------------------------------------------------------------- /public/modules/core/images/Map@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Map@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/mean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/mean.png -------------------------------------------------------------------------------- /public/modules/core/images/Dude@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Dude@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Girl@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Girl@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Goal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Goal@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Graph@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Graph@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Yelp-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Yelp-32.png -------------------------------------------------------------------------------- /public/modules/core/img/brand/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/img/brand/logo.png -------------------------------------------------------------------------------- /public/modules/core/images/BreweryDB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/BreweryDB.png -------------------------------------------------------------------------------- /public/modules/core/images/Facebook-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Facebook-32.png -------------------------------------------------------------------------------- /public/modules/core/images/Pensils@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Pensils@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Search@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Settings@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Shield@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Shield@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Twitter-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Twitter-32.png -------------------------------------------------------------------------------- /public/modules/core/images/meanjs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/meanjs_logo.png -------------------------------------------------------------------------------- /public/modules/core/img/brand/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/img/brand/favicon.ico -------------------------------------------------------------------------------- /public/modules/core/img/loaders/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/img/loaders/loader.gif -------------------------------------------------------------------------------- /public/modules/nearby/images/beer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/nearby/images/beer-icon.png -------------------------------------------------------------------------------- /public/modules/core/images/Foursquare-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Foursquare-32.png -------------------------------------------------------------------------------- /public/modules/core/images/GooglePlus-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/GooglePlus-32.png -------------------------------------------------------------------------------- /public/modules/core/images/Instagram-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Instagram-32.png -------------------------------------------------------------------------------- /public/modules/core/images/Pinterest-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Pinterest-32.png -------------------------------------------------------------------------------- /public/modules/core/images/YouTube_01-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/YouTube_01-32.png -------------------------------------------------------------------------------- /public/modules/core/images/hack_reactor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/hack_reactor.png -------------------------------------------------------------------------------- /public/modules/core/images/predictionio.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/predictionio.jpeg -------------------------------------------------------------------------------- /public/modules/users/img/buttons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/users/img/buttons/github.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/users/img/buttons/google.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/users/img/buttons/twitter.png -------------------------------------------------------------------------------- /public/modules/core/images/BreweryDB_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/BreweryDB_logo.png -------------------------------------------------------------------------------- /public/modules/core/images/Facebook_Like-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/Facebook_Like-32.png -------------------------------------------------------------------------------- /public/modules/core/images/no-beer-label-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/no-beer-label-60.png -------------------------------------------------------------------------------- /public/modules/core/images/on-tapp-165x165.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/core/images/on-tapp-165x165.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/users/img/buttons/facebook.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulieMarie/on-tapp/HEAD/public/modules/users/img/buttons/linkedin.png -------------------------------------------------------------------------------- /app/views/index.server.view.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.server.view.html' %} 2 | 3 | {% block content %} 4 |
5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /app/views/500.server.view.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.server.view.html' %} 2 | 3 | {% block content %} 4 |

Server Error

5 |
6 | 	{{error}}
7 | 
8 | {% endblock %} -------------------------------------------------------------------------------- /public/modules/core/core.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use Applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('core'); -------------------------------------------------------------------------------- /public/modules/core/css/justified-nav.css: -------------------------------------------------------------------------------- 1 | /* Main marketing message and sign up button */ 2 | .jumbotron { 3 | text-align: center; 4 | background-color: transparent; 5 | } 6 | -------------------------------------------------------------------------------- /public/modules/users/users.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use Application configuration module to register a new module 4 | ApplicationConfiguration.registerModule('users'); -------------------------------------------------------------------------------- /public/modules/beers/beers.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use application configuration module to register a new module 4 | ApplicationConfiguration.registerModule('beers'); 5 | -------------------------------------------------------------------------------- /public/modules/nearby/nearby.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use application configuration module to register a new module 4 | ApplicationConfiguration.registerModule('nearby'); 5 | -------------------------------------------------------------------------------- /public/modules/ratings/ratings.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('ratings'); -------------------------------------------------------------------------------- /app/views/404.server.view.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.server.view.html' %} 2 | 3 | {% block content %} 4 |

Page Not Found

5 |
6 | 	{{url}} is not a valid path.
7 | 
8 | {% endblock %} -------------------------------------------------------------------------------- /fig.yml: -------------------------------------------------------------------------------- 1 | web: 2 | build: . 3 | links: 4 | - db 5 | ports: 6 | - "3000:3000" 7 | environment: 8 | NODE_ENV: development 9 | db: 10 | image: mongo 11 | ports: 12 | - "27017:27017" -------------------------------------------------------------------------------- /app/routes/core.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(app) { 4 | // Root routing 5 | var core = require('../../app/controllers/core.server.controller'); 6 | app.route('/').get(core.index); 7 | }; -------------------------------------------------------------------------------- /app/controllers/core.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | exports.index = function(req, res) { 7 | res.render('index', { 8 | user: req.user || null, 9 | request: req 10 | }); 11 | }; -------------------------------------------------------------------------------- /public/modules/users/views/password/reset-password-success.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Password successfully reset

3 | Continue to home page 4 |
-------------------------------------------------------------------------------- /public/modules/users/views/password/reset-password-invalid.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Password reset is invalid

3 | Ask for a new password reset 4 |
-------------------------------------------------------------------------------- /public/modules/core/css/sticky-footer.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | } 5 | 6 | body { 7 | margin-bottom: 108px; 8 | } 9 | 10 | .footer { 11 | height: 108px; 12 | width: 100%; 13 | position: absolute; 14 | bottom: 0; 15 | } 16 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | pre: 3 | - npm install -g karma-cli bower 4 | - bower install 5 | cache_directories: 6 | - ~/nvm 7 | 8 | ## Customize deployment commandss 9 | deployment: 10 | staging: 11 | branch: master 12 | heroku: 13 | appname: on-tapp 14 | -------------------------------------------------------------------------------- /public/modules/core/controllers/home.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').controller('HomeController', ['$scope', 'Authentication', 4 | function($scope, Authentication) { 5 | // This provides Authentication context. 6 | $scope.authentication = Authentication; 7 | } 8 | ]); -------------------------------------------------------------------------------- /public/modules/nearby/config/nearby.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Configuring the Articles module 4 | angular.module('nearby').run(['Menus', 5 | function(Menus) { 6 | // Set top bar menu items 7 | Menus.addMenuItem('topbar', 'Nearby', 'nearby', '/nearby'); 8 | } 9 | ]); 10 | -------------------------------------------------------------------------------- /public/modules/users/css/users.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | .nav-users { 3 | position: fixed; 4 | } 5 | } 6 | .remove-account-container { 7 | display: inline-block; 8 | position: relative; 9 | } 10 | .btn-remove-account { 11 | top: 10px; 12 | right: 10px; 13 | position: absolute; 14 | } -------------------------------------------------------------------------------- /public/modules/beers/services/beer.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('beers').factory('Beer', ['$http', 4 | function($http) { 5 | // Public API 6 | return { 7 | getData: function(beerId){ 8 | return $http.get('/beer/' + beerId); 9 | } 10 | }; 11 | } 12 | ]); -------------------------------------------------------------------------------- /public/modules/ratings/config/ratings.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Configuring the Articles module 4 | angular.module('ratings').run(['Menus', 5 | function(Menus) { 6 | // Set top bar menu items 7 | Menus.addMenuItem('topbar', 'Ratings', 'ratings', '/ratings(/create)?'); 8 | } 9 | ]); 10 | -------------------------------------------------------------------------------- /public/modules/nearby/services/brewery.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('nearby').factory('Brewery', ['$http', 4 | function($http) { 5 | // Public API 6 | return { 7 | getData: function(breweryId){ 8 | return $http.get('/brewery/' + breweryId); 9 | } 10 | }; 11 | } 12 | ]); -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | Julie Knowles -- Product Owner 7 | Victor Leung -- Scrum Master -- @victorleungtw 8 | 9 | # THANKS 10 | 11 | Hack Reactor 12 | 13 | # TECHNOLOGY COLOPHON 14 | 15 | HTML5, CSS3 16 | jQuery, Modernizr 17 | -------------------------------------------------------------------------------- /public/modules/users/services/authentication.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Authentication service for user variables 4 | angular.module('users').factory('Authentication', [ 5 | function() { 6 | var _this = this; 7 | 8 | _this._data = { 9 | user: window.user 10 | }; 11 | 12 | return _this._data; 13 | } 14 | ]); -------------------------------------------------------------------------------- /public/modules/users/services/users.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Users service used for communicating with the users REST endpoint 4 | angular.module('users').factory('Users', ['$resource', 5 | function($resource) { 6 | return $resource('users', {}, { 7 | update: { 8 | method: 'PUT' 9 | } 10 | }); 11 | } 12 | ]); -------------------------------------------------------------------------------- /public/modules/beers/services/beers.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('beers').factory('Beers', ['$http', '$resource', 4 | function($http, $resource) { 5 | // Public API 6 | return { 7 | getData: function(breweryId){ 8 | return $http.get('/beers/' + breweryId); 9 | } 10 | }; 11 | } 12 | ]); 13 | -------------------------------------------------------------------------------- /public/modules/core/services/search.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').factory('Search', ['$http', 4 | function($http) { 5 | // Public API 6 | return { 7 | getData: function(keyword, page, searchtype){ 8 | return $http.get('/search/' + keyword + '/' + page + '/' + searchtype); 9 | } 10 | }; 11 | } 12 | ]); -------------------------------------------------------------------------------- /public/modules/ratings/services/stylequery.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('ratings').factory('StyleQuery', ['$http', 4 | function($http) { 5 | // Stylequery service logic 6 | // ... 7 | 8 | // Public API 9 | return { 10 | getStyle: function(styleName) { 11 | return $http.get('/style/' + styleName); 12 | } 13 | }; 14 | } 15 | ]); 16 | -------------------------------------------------------------------------------- /app/views/templates/reset-password-confirm-email.server.view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Dear {{name}},

7 |

8 |

This is a confirmation that the password for your account has just been changed

9 |
10 |
11 |

The {{appName}} Support Team

12 | 13 | -------------------------------------------------------------------------------- /public/modules/ratings/services/ratings.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Ratings service used to communicate Ratings REST endpoints 4 | angular.module('ratings').factory('Ratings', ['$resource', 5 | function($resource) { 6 | return $resource('ratings/:ratingId', { 7 | ratingId: '@_id' 8 | }, { 9 | update: { 10 | method: 'PUT' 11 | } 12 | }); 13 | } 14 | ]); 15 | -------------------------------------------------------------------------------- /public/modules/nearby/services/breweries.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('nearby').factory('Breweries', ['$http', 4 | function($http) { 5 | // Breweries service logic 6 | // ... 7 | 8 | // Public API 9 | return { 10 | getData: function(coords){ 11 | return $http.get('/breweries/' + coords.lat + '/' + coords.long); 12 | } 13 | }; 14 | } 15 | ]); 16 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-model": false, 4 | "box-sizing": false, 5 | "floats": false, 6 | "font-sizes": false, 7 | "important": false, 8 | "known-properties": false, 9 | "overqualified-elements": false, 10 | "qualified-headings": false, 11 | "regex-selectors": false, 12 | "unique-headings": false, 13 | "universal-selector": false, 14 | "unqualified-attributes": false 15 | } 16 | -------------------------------------------------------------------------------- /public/modules/beers/controllers/beer.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('beers').controller('BeerController', ['$scope', 'Beer', '$stateParams', 4 | function($scope, Beer, $stateParams) { 5 | // Beer controller logic 6 | $scope.beerId = $stateParams.beerId; 7 | 8 | Beer.getData($scope.beerId).success(function(results, status) { 9 | $scope.beer = results.data || 'Request failed'; 10 | }); 11 | } 12 | ]); -------------------------------------------------------------------------------- /app/controllers/users.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'); 7 | 8 | /** 9 | * Extend user's controller 10 | */ 11 | module.exports = _.extend( 12 | require('./users/users.authentication.server.controller'), 13 | require('./users/users.authorization.server.controller'), 14 | require('./users/users.password.server.controller'), 15 | require('./users/users.profile.server.controller') 16 | ); -------------------------------------------------------------------------------- /public/modules/beers/config/beers.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('beers').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Beers state routing 7 | $stateProvider. 8 | state('beer', { 9 | url: '/beer/:beerId', 10 | templateUrl: 'modules/beers/views/beer.client.view.html' 11 | }). 12 | state('beers', { 13 | url: '/beers/:breweryId', 14 | templateUrl: 'modules/beers/views/beers.client.view.html' 15 | }); 16 | } 17 | ]); 18 | -------------------------------------------------------------------------------- /app/views/templates/reset-password-email.server.view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Dear {{name}},

7 |
8 |

9 | You have requested to have your password reset for your account at {{appName}} 10 |

11 |

Please visit this url to reset your password:

12 |

{{url}}

13 | If you didn't make this request, you can ignore this email. 14 |
15 |
16 |

The {{appName}} Support Team

17 | 18 | -------------------------------------------------------------------------------- /app/routes/beer.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var secret = require('../../api-key'); 3 | var request = require('request'); 4 | 5 | module.exports = function(app) { 6 | // beerId contains the id of the beer 7 | app.get('/beer/:beerId', function(req, res){ 8 | request('https://api.brewerydb.com/v2/beer/' + req.params.beerId + '?withBreweries=Y&withSocialAccounts=Y&withIngredients=Y&key=' + secret.keys.brewerydb, function (error, response, body) { 9 | if (!error && response.statusCode === 200) { 10 | res.send(body); 11 | } 12 | }); 13 | }); 14 | }; -------------------------------------------------------------------------------- /app/routes/beers.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var secret = require('../../api-key'); 3 | var request = require('request'); 4 | 5 | module.exports = function(app) { 6 | // breweryId contains the id of the brewery to allow listing of beers 7 | app.get('/beers/:breweryId', function(req, res){ 8 | request('https://api.brewerydb.com/v2/brewery/' + req.params.breweryId + '/beers?key=' + secret.keys.brewerydb, function (error, response, body) { 9 | if (!error && response.statusCode === 200) { 10 | res.send(body); 11 | } 12 | }); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /app/routes/brewery.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var secret = require('../../api-key'); 3 | var request = require('request'); 4 | 5 | module.exports = function(app) { 6 | // breweryId contains the id of the beer 7 | app.get('/brewery/:breweryId', function(req, res){ 8 | request('https://api.brewerydb.com/v2/brewery/' + req.params.breweryId + '?withGuilds=Y&withSocialAccounts=Y&withLocations=Y&key=' + secret.keys.brewerydb, function (error, response, body) { 9 | if (!error && response.statusCode === 200) { 10 | res.send(body); 11 | } 12 | }); 13 | }); 14 | }; -------------------------------------------------------------------------------- /public/modules/core/config/core.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('core').config(['$stateProvider', '$urlRouterProvider', 5 | function($stateProvider, $urlRouterProvider) { 6 | // Redirect to home view when route not found 7 | $urlRouterProvider.otherwise('/'); 8 | 9 | // Home state routing 10 | $stateProvider. 11 | state('search', { 12 | url: '/search/:page/:keyword/:searchtype', 13 | templateUrl: 'modules/core/views/search.client.view.html' 14 | }). 15 | state('home', { 16 | url: '/', 17 | templateUrl: 'modules/core/views/home.client.view.html' 18 | }); 19 | } 20 | ]); -------------------------------------------------------------------------------- /app/routes/breweries.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var secret = require('../../api-key'); 3 | var request = require('request'); 4 | 5 | module.exports = function(app) { 6 | // lat and lng contain the current location latitude and longitude to allow listing 7 | // of nearby breweries 8 | app.get('/breweries/:lat/:lng', function(req, res){ 9 | request('https://api.brewerydb.com/v2/search/geo/point?lat=' + req.params.lat + '&lng=' + req.params.lng + '&key=' + secret.keys.brewerydb, function (error, response, body) { 10 | if (!error && response.statusCode === 200) { 11 | res.send(body); 12 | } 13 | }); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /public/modules/core/tests/home.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('HomeController', function() { 5 | //Initialize global variables 6 | var scope, 7 | HomeController; 8 | 9 | // Load the main application module 10 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 11 | 12 | beforeEach(inject(function($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | 15 | HomeController = $controller('HomeController', { 16 | $scope: scope 17 | }); 18 | })); 19 | 20 | it('should expose the authentication service', function() { 21 | expect(scope.authentication).toBeTruthy(); 22 | }); 23 | }); 24 | })(); -------------------------------------------------------------------------------- /public/modules/ratings/config/ratings.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('ratings').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Ratings state routing 7 | $stateProvider. 8 | state('listRatings', { 9 | url: '/ratings', 10 | templateUrl: 'modules/ratings/views/list-ratings.client.view.html' 11 | }). 12 | state('viewRating', { 13 | url: '/ratings/:ratingId', 14 | templateUrl: 'modules/ratings/views/view-rating.client.view.html' 15 | }). 16 | state('editRating', { 17 | url: '/ratings/:ratingId/edit', 18 | templateUrl: 'modules/ratings/views/edit-rating.client.view.html' 19 | }); 20 | } 21 | ]); 22 | -------------------------------------------------------------------------------- /public/modules/core/tests/header.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('HeaderController', function() { 5 | //Initialize global variables 6 | var scope, 7 | HeaderController; 8 | 9 | // Load the main application module 10 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 11 | 12 | beforeEach(inject(function($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | 15 | HeaderController = $controller('HeaderController', { 16 | $scope: scope 17 | }); 18 | })); 19 | 20 | it('should expose the authentication service', function() { 21 | expect(scope.authentication).toBeTruthy(); 22 | }); 23 | }); 24 | })(); -------------------------------------------------------------------------------- /config/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var morgan = require('morgan'); 8 | var config = require('./config'); 9 | var fs = require('fs'); 10 | 11 | /** 12 | * Module init function. 13 | */ 14 | module.exports = { 15 | 16 | getLogFormat: function() { 17 | return config.log.format; 18 | }, 19 | 20 | getLogOptions: function() { 21 | var options = {}; 22 | 23 | try { 24 | if ('stream' in config.log.options) { 25 | options = { 26 | stream: fs.createWriteStream(process.cwd() + '/' + config.log.options.stream, {flags: 'a'}) 27 | }; 28 | } 29 | } catch (e) { 30 | options = {}; 31 | } 32 | 33 | return options; 34 | } 35 | 36 | }; -------------------------------------------------------------------------------- /newrelic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * New Relic agent configuration. 3 | * 4 | * See lib/config.defaults.js in the agent distribution for a more complete 5 | * description of configuration variables and their potential values. 6 | */ 7 | exports.config = { 8 | /** 9 | * Array of application names. 10 | */ 11 | app_name : ['OnTapp'], 12 | /** 13 | * Your New Relic license key. 14 | */ 15 | license_key : '39a551845a4a8366155beef60b007d011186dd35', 16 | logging : { 17 | /** 18 | * Level at which to log. 'trace' is most useful to New Relic when diagnosing 19 | * issues with the agent, 'info' and higher will impose the least overhead on 20 | * production applications. 21 | */ 22 | level : 'info' 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /public/modules/nearby/config/nearby.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('nearby').config(['$stateProvider', 'uiGmapGoogleMapApiProvider', 5 | function($stateProvider, uiGmapGoogleMapApiProvider) { 6 | // Nearby state routing 7 | $stateProvider. 8 | state('brewery', { 9 | url: '/brewery/:breweryId', 10 | templateUrl: 'modules/nearby/views/brewery.client.view.html' 11 | }). 12 | state('nearby', { 13 | url: '/nearby', 14 | templateUrl: 'modules/nearby/views/nearby.client.view.html' 15 | }); 16 | 17 | uiGmapGoogleMapApiProvider.configure({ 18 | key: 'AIzaSyAQHm36O2gZr34HkBjElKYHox3LVWR8UWY', 19 | v: '3.17', 20 | libraries: 'weather,geometry,visualization' 21 | }); 22 | } 23 | ]); 24 | -------------------------------------------------------------------------------- /scripts/generate-ssl-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -e server.js ] 4 | then 5 | echo "Error: could not find main application server.js file" 6 | echo "You should run the generate-ssl-certs.sh script from the main MEAN application root directory" 7 | echo "i.e: bash scripts/generate-ssl-cers.sh" 8 | exit -1 9 | fi 10 | 11 | echo "Generating self-signed certificates..." 12 | mkdir -p ./config/sslcerts 13 | openssl genrsa -out ./config/sslcerts/key.pem -aes256 1024 14 | openssl req -new -key ./config/sslcerts/key.pem -out ./config/sslcerts/csr.pem 15 | openssl x509 -req -days 9999 -in ./config/sslcerts/csr.pem -signkey ./config/sslcerts/key.pem -out ./config/sslcerts/cert.pem 16 | rm ./config/sslcerts/csr.pem 17 | chmod 600 ./config/sslcerts/key.pem ./config/sslcerts/cert.pem 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dockerfile/nodejs 2 | 3 | MAINTAINER Matthias Luebken, matthias@catalyst-zero.com 4 | 5 | WORKDIR /home/mean 6 | 7 | # Install Mean.JS Prerequisites 8 | RUN npm install -g grunt-cli 9 | RUN npm install -g bower 10 | 11 | # Install Mean.JS packages 12 | ADD package.json /home/mean/package.json 13 | RUN npm install 14 | 15 | # Manually trigger bower. Why doesnt this work via npm install? 16 | ADD .bowerrc /home/mean/.bowerrc 17 | ADD bower.json /home/mean/bower.json 18 | RUN bower install --config.interactive=false --allow-root 19 | 20 | # Make everything available for start 21 | ADD . /home/mean 22 | 23 | # currently only works for development 24 | ENV NODE_ENV development 25 | 26 | # Port 3000 for server 27 | # Port 35729 for livereload 28 | EXPOSE 3000 35729 29 | CMD ["grunt"] -------------------------------------------------------------------------------- /public/application.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Start by defining the main module and adding the module dependencies 4 | angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies); 5 | 6 | // Setting HTML5 Location Mode 7 | angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider', 8 | function($locationProvider) { 9 | $locationProvider.hashPrefix('!'); 10 | } 11 | ]); 12 | 13 | //Then define the init function for starting up the application 14 | angular.element(document).ready(function() { 15 | //Fixing facebook bug with redirect 16 | if (window.location.hash === '#_=_') window.location.hash = '#!'; 17 | 18 | //Then init the app 19 | angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); 20 | }); -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | User = require('mongoose').model('User'), 8 | path = require('path'), 9 | config = require('./config'); 10 | 11 | /** 12 | * Module init function. 13 | */ 14 | module.exports = function() { 15 | // Serialize sessions 16 | passport.serializeUser(function(user, done) { 17 | done(null, user.id); 18 | }); 19 | 20 | // Deserialize sessions 21 | passport.deserializeUser(function(id, done) { 22 | User.findOne({ 23 | _id: id 24 | }, '-salt -password', function(err, user) { 25 | done(err, user); 26 | }); 27 | }); 28 | 29 | // Initialize strategies 30 | config.getGlobbedFiles('./config/strategies/**/*.js').forEach(function(strategy) { 31 | require(path.resolve(strategy))(); 32 | }); 33 | }; -------------------------------------------------------------------------------- /public/modules/core/css/core.css: -------------------------------------------------------------------------------- 1 | /*.content { 2 | margin-top: 50px; 3 | }*/ 4 | .undecorated-link:hover { 5 | text-decoration: none; 6 | } 7 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 8 | display: none !important; 9 | } 10 | .ng-invalid.ng-dirty { 11 | border-color: #FA787E; 12 | } 13 | .ng-valid.ng-dirty { 14 | border-color: #78FA89; 15 | } 16 | .browsehappy.jumbotron.hide, 17 | body.ng-cloak 18 | { 19 | display: block; 20 | } 21 | .grey-container { 22 | background-color: #f5f5f5; 23 | border: 1px solid #e3e3e3; 24 | margin: 40px 0; 25 | padding-top: 20px; 26 | padding-bottom: 20px; 27 | } 28 | .search-bar { 29 | width: 240px; 30 | display: inline; 31 | margin-top: 5px; 32 | } 33 | .search-form { 34 | margin-bottom: 5px; 35 | } 36 | 37 | .sponsor{ 38 | height: 150px; 39 | } 40 | -------------------------------------------------------------------------------- /public/modules/users/config/users.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Config HTTP Error Handling 4 | angular.module('users').config(['$httpProvider', 5 | function($httpProvider) { 6 | // Set the httpProvider "not authorized" interceptor 7 | $httpProvider.interceptors.push(['$q', '$location', 'Authentication', 8 | function($q, $location, Authentication) { 9 | return { 10 | responseError: function(rejection) { 11 | switch (rejection.status) { 12 | case 401: 13 | // Deauthenticate the global user 14 | Authentication.user = null; 15 | 16 | // Redirect to signin page 17 | $location.path('signin'); 18 | break; 19 | case 403: 20 | // Add unauthorized behaviour 21 | break; 22 | } 23 | 24 | return $q.reject(rejection); 25 | } 26 | }; 27 | } 28 | ]); 29 | } 30 | ]); -------------------------------------------------------------------------------- /config/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var glob = require('glob'), 7 | chalk = require('chalk'); 8 | 9 | /** 10 | * Module init function. 11 | */ 12 | module.exports = function() { 13 | /** 14 | * Before we begin, lets set the environment variable 15 | * We'll Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV 16 | */ 17 | glob('./config/env/' + process.env.NODE_ENV + '.js', { 18 | sync: true 19 | }, function(err, environmentFiles) { 20 | if (!environmentFiles.length) { 21 | if (process.env.NODE_ENV) { 22 | console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead')); 23 | } else { 24 | console.error(chalk.red('NODE_ENV is not defined! Using default development environment')); 25 | } 26 | 27 | process.env.NODE_ENV = 'development'; 28 | } 29 | }); 30 | 31 | }; -------------------------------------------------------------------------------- /public/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Init the application configuration module for AngularJS application 4 | var ApplicationConfiguration = (function() { 5 | // Init module configuration options 6 | var applicationModuleName = 'onTappApp'; 7 | var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils', 'uiGmapgoogle-maps', 'geolocation', 'angularSpinner']; 8 | 9 | // Add a new vertical module 10 | var registerModule = function(moduleName, dependencies) { 11 | // Create angular module 12 | angular.module(moduleName, dependencies || []); 13 | 14 | // Add the module to the AngularJS configuration file 15 | angular.module(applicationModuleName).requires.push(moduleName); 16 | }; 17 | 18 | return { 19 | applicationModuleName: applicationModuleName, 20 | applicationModuleVendorDependencies: applicationModuleVendorDependencies, 21 | registerModule: registerModule 22 | }; 23 | })(); 24 | -------------------------------------------------------------------------------- /public/modules/ratings/views/edit-rating.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 |
8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /config/strategies/local.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | LocalStrategy = require('passport-local').Strategy, 8 | User = require('mongoose').model('User'); 9 | 10 | module.exports = function() { 11 | // Use local strategy 12 | passport.use(new LocalStrategy({ 13 | usernameField: 'username', 14 | passwordField: 'password' 15 | }, 16 | function(username, password, done) { 17 | User.findOne({ 18 | username: username 19 | }, function(err, user) { 20 | if (err) { 21 | return done(err); 22 | } 23 | if (!user) { 24 | return done(null, false, { 25 | message: 'Unknown user or invalid password' 26 | }); 27 | } 28 | if (!user.authenticate(password)) { 29 | return done(null, false, { 30 | message: 'Unknown user or invalid password' 31 | }); 32 | } 33 | 34 | return done(null, user); 35 | }); 36 | } 37 | )); 38 | }; -------------------------------------------------------------------------------- /app/models/rating.server.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema; 8 | 9 | /** 10 | * Rating Schema 11 | */ 12 | var RatingSchema = new Schema({ 13 | beerId: { 14 | type: String, 15 | default: '', 16 | }, 17 | name: { 18 | type: String, 19 | default: '', 20 | required: 'Please fill Rating name', 21 | trim: true 22 | }, 23 | stars: { 24 | type: Number, 25 | default: 0 26 | }, 27 | styleName: { 28 | type: String, 29 | default: 0 30 | }, 31 | created: { 32 | type: Date, 33 | default: Date.now 34 | }, 35 | user: { 36 | type: Schema.ObjectId, 37 | ref: 'User' 38 | } 39 | }); 40 | 41 | // assign a function to the "statics" object of our RatingSchema 42 | RatingSchema.statics.findByBeerId = function (beerId, cb) { 43 | this.find({ beerId: new RegExp(beerId, 'i') }, cb); 44 | }; 45 | 46 | mongoose.model('Rating', RatingSchema); 47 | -------------------------------------------------------------------------------- /public/modules/core/controllers/header.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus', '$state', 4 | function($scope, Authentication, Menus, $state) { 5 | $scope.authentication = Authentication; 6 | $scope.isCollapsed = false; 7 | $scope.menu = Menus.getMenu('topbar'); 8 | 9 | $scope.toggleCollapsibleMenu = function() { 10 | $scope.isCollapsed = !$scope.isCollapsed; 11 | }; 12 | 13 | // Collapsing the menu after navigation 14 | $scope.$on('$stateChangeSuccess', function() { 15 | $scope.isCollapsed = false; 16 | }); 17 | 18 | // Search logic 19 | $scope.results = []; 20 | $scope.totalResults = undefined; 21 | 22 | $scope.search = function(currentPage) { 23 | currentPage = currentPage || 1; 24 | $state.go('search', {'page': currentPage, 'keyword': $scope.keyword, 'searchtype': $scope.searchType}); 25 | }; 26 | } 27 | ]); -------------------------------------------------------------------------------- /public/modules/nearby/controllers/brewery.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('nearby').controller('BreweryController', ['$scope', 'Brewery', '$stateParams', 4 | function($scope, Brewery, $stateParams) { 5 | // Brewery controller logic 6 | $scope.breweryId = $stateParams.breweryId; 7 | var holdSocial = []; 8 | 9 | Brewery.getData($scope.breweryId).success(function(results, status) { 10 | $scope.brewery = results.data || 'Request failed'; 11 | for (var i = 0; i < $scope.brewery.socialAccounts.length; i++) { 12 | // only save the social media sites that are FB, Twitter, 4Square, 13 | // Google+, YouTube, Instagram, Yelp or Pinterest 14 | var tempSocial = $scope.brewery.socialAccounts[i]; 15 | if ([1,2,3,8,10,14,15,16].indexOf(tempSocial.socialMediaId) > -1) { 16 | holdSocial.push(tempSocial); 17 | } 18 | } 19 | $scope.socialMedia = holdSocial; 20 | }); 21 | } 22 | ]); -------------------------------------------------------------------------------- /public/modules/nearby/css/map.css: -------------------------------------------------------------------------------- 1 | .stars{ 2 | font-size: 40px; 3 | } 4 | 5 | .angular-google-map, 6 | .angular-google-map-container { 7 | height: 100%; 8 | } 9 | 10 | .left-half-container{ 11 | position: absolute; 12 | left: 0; 13 | right: 60%; 14 | top: 0; 15 | bottom: 0; 16 | height: 100%; 17 | margin-top: 53px; 18 | } 19 | 20 | .right-half-container{ 21 | position: absolute; 22 | left: 40%; 23 | right: 0; 24 | top: 0; 25 | bottom: 0; 26 | margin-top: 53px; 27 | overflow: scroll; 28 | } 29 | 30 | @media (max-width: 750px) { 31 | 32 | .angular-google-map, 33 | .angular-google-map-container { 34 | height: 250px; 35 | } 36 | 37 | .left-half-container{ 38 | position: absolute; 39 | left: 0; 40 | right: 0; 41 | top: 0; 42 | bottom: 75%; 43 | height: 100%; 44 | margin-top: 53px; 45 | } 46 | 47 | .right-half-container{ 48 | position: absolute; 49 | left: 0; 50 | right: 0; 51 | top: 25%; 52 | bottom: 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/routes/search.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var secret = require('../../api-key'); 3 | var request = require('request'); 4 | 5 | module.exports = function(app) { 6 | // name contains the partial of what the user is searching for 7 | app.get('/search/:keyword/:page/:searchtype', function(req, res){ 8 | if (req.params.searchtype !== 'all') { 9 | request('https://api.brewerydb.com/v2/search?q=' + req.params.keyword + '&p=' + req.params.page + '&type=' + req.params.searchtype + '&key=' + secret.keys.brewerydb, function (error, response, body) { 10 | if (!error && response.statusCode === 200) { 11 | res.send(body); 12 | } 13 | }); 14 | } else { 15 | request('https://api.brewerydb.com/v2/search?q=' + req.params.keyword + '&p=' + req.params.page + '&key=' + secret.keys.brewerydb, function (error, response, body) { 16 | if (!error && response.statusCode === 200) { 17 | res.send(body); 18 | } 19 | }); 20 | } 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /public/modules/core/views/search.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 16 |
17 | Nothing found!! Please try again... 18 |
19 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # iOS / Apple 2 | # =========== 3 | .DS_Store 4 | ehthumbs.db 5 | Icon? 6 | Thumbs.db 7 | 8 | # Node and related ecosystem 9 | # ========================== 10 | .nodemonignore 11 | .sass-cache/ 12 | npm-debug.log 13 | node_modules/ 14 | public/lib 15 | app/tests/coverage/ 16 | .bower-*/ 17 | .idea/ 18 | 19 | # MEAN.js app and assets 20 | # ====================== 21 | config/sslcerts/*.pem 22 | access.log 23 | 24 | # Sublime editor 25 | # ============== 26 | .sublime-project 27 | *.sublime-project 28 | *.sublime-workspace 29 | 30 | # Eclipse project files 31 | # ===================== 32 | .project 33 | .settings/ 34 | .*.md.html 35 | .metadata 36 | *~.nib 37 | local.properties 38 | 39 | # IntelliJ 40 | # ======== 41 | *.iml 42 | 43 | # General 44 | # ======= 45 | *.log 46 | *.csv 47 | *.dat 48 | *.out 49 | *.pid 50 | *.gz 51 | *.tmp 52 | *.bak 53 | *.swp 54 | logs/ 55 | build/ 56 | 57 | # Misc 58 | # ==== 59 | .bash_profile 60 | mockups.pdf 61 | public/assets/ 62 | results/ 63 | *.publishsettings 64 | recommendation-engine/ 65 | -------------------------------------------------------------------------------- /app/controllers/errors.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | var getUniqueErrorMessage = function(err) { 7 | var output; 8 | 9 | try { 10 | var fieldName = err.err.substring(err.err.lastIndexOf('.$') + 2, err.err.lastIndexOf('_1')); 11 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists'; 12 | 13 | } catch (ex) { 14 | output = 'Unique field already exists'; 15 | } 16 | 17 | return output; 18 | }; 19 | 20 | /** 21 | * Get the error message from error object 22 | */ 23 | exports.getErrorMessage = function(err) { 24 | var message = ''; 25 | 26 | if (err.code) { 27 | switch (err.code) { 28 | case 11000: 29 | case 11001: 30 | message = getUniqueErrorMessage(err); 31 | break; 32 | default: 33 | message = 'Something went wrong'; 34 | } 35 | } else { 36 | for (var errName in err.errors) { 37 | if (err.errors[errName].message) message = err.errors[errName].message; 38 | } 39 | } 40 | 41 | return message; 42 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # Howto with your editor: 4 | # Sublime: https://github.com/sindresorhus/editorconfig-sublime 5 | 6 | # top-most EditorConfig file 7 | root = true 8 | 9 | # Unix-style newlines with a newline ending every file 10 | [**] 11 | end_of_line = lf 12 | insert_final_newline = true 13 | 14 | # Standard at: https://github.com/felixge/node-style-guide 15 | [**.js, **.json] 16 | trim_trailing_whitespace = true 17 | indent_style = tab 18 | quote_type = single 19 | curly_bracket_next_line = false 20 | spaces_around_operators = true 21 | space_after_control_statements = true 22 | space_after_anonymous_functions = false 23 | spaces_in_brackets = false 24 | 25 | # No Standard. Please document a standard if different from .js 26 | [**.yml, **.html, **.css] 27 | trim_trailing_whitespace = true 28 | indent_style = tab 29 | 30 | # No standard. Please document a standard if different from .js 31 | [**.md] 32 | indent_style = tab 33 | 34 | # Standard at: 35 | [Makefile] 36 | indent_style = tab 37 | -------------------------------------------------------------------------------- /public/modules/users/views/password/forgot-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Restore your password

3 |

Enter your account username.

4 |
5 | 21 |
22 |
-------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "on-tapp", 3 | "version": "0.3.2", 4 | "description": "HRR2 Thesis Project", 5 | "homepage": "https://github.com/green-brass-doberman/on-tapp", 6 | "main": "server.js", 7 | "keywords": [ 8 | "beer", 9 | "recommendations" 10 | ], 11 | "license": "MIT", 12 | "ignore": [ 13 | "**/.*", 14 | "node_modules", 15 | "bower_components", 16 | "public/lib", 17 | "test", 18 | "tests" 19 | ], 20 | "authors": [ 21 | "Julie", 22 | "Patrick", 23 | "Victor" 24 | ], 25 | "dependencies": { 26 | "bootstrap": "~3", 27 | "angular": "~1.2", 28 | "angular-resource": "~1.2", 29 | "angular-animate": "~1.2", 30 | "angular-mocks": "~1.2", 31 | "angular-bootstrap": "~0.12.0", 32 | "angular-ui-utils": "~0.1.1", 33 | "angular-ui-router": "~0.2.11", 34 | "fontawesome": "~4.2.0", 35 | "angular-google-maps": "~2.0.12", 36 | "angularjs-geolocation": "~0.1.1", 37 | "flat-ui": "~2.2.2", 38 | "angular-spinner": "~0.6.1", 39 | "spin.js": "~2.0.2", 40 | "fastclick": "~1.0.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## License 2 | (The MIT License) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | 'Software'), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/routes/ratings.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var secret = require('../../api-key'); 4 | var request = require('request'); 5 | 6 | module.exports = function(app) { 7 | var users = require('../../app/controllers/users.server.controller'); 8 | var ratings = require('../../app/controllers/ratings.server.controller'); 9 | 10 | // Ratings Routes 11 | app.route('/ratings') 12 | .get(ratings.list) 13 | .post(users.requiresLogin, ratings.create); 14 | 15 | app.route('/ratings/:ratingId') 16 | .get(ratings.read) 17 | .put(users.requiresLogin, ratings.hasAuthorization, ratings.update) 18 | .delete(users.requiresLogin, ratings.hasAuthorization, ratings.delete); 19 | 20 | // Finish by binding the Rating middleware 21 | app.param('ratingId', ratings.ratingByID); 22 | 23 | // search by style 24 | app.get('/style/:styleName', function(req, res){ 25 | request('https://api.brewerydb.com/v2/search/style?q=' + req.params.styleName + '&key=' + secret.keys.brewerydb, function (error, response, body) { 26 | if (!error && response.statusCode === 200) { 27 | res.send(body); 28 | } 29 | }); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /public/modules/ratings/views/list-ratings.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 | 18 |
19 | No Ratings yet, why don't you create one? 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /public/modules/core/css/generic.css: -------------------------------------------------------------------------------- 1 | p.center { 2 | text-align: center; 3 | margin: 0; 4 | line-height: 1.3em; 5 | } 6 | .brewery-info p { 7 | text-align: center; 8 | margin: 0; 9 | line-height: 1.8em; 10 | } 11 | p.content { 12 | white-space: pre-wrap; 13 | line-height: 1.3em; 14 | padding-right: 15px; 15 | } 16 | img.brewery-img { 17 | margin-bottom: 15px; 18 | } 19 | p.beer-btn { 20 | text-align: center; 21 | margin-top: 15px; 22 | } 23 | .beer { 24 | box-sizing: border-box; 25 | font-size: 0.8em; 26 | line-height: 1.3em; 27 | margin: 0 15px 15px 0; 28 | padding: 5px 15px 0 15px; 29 | border: 1px solid #5a5a5a; 30 | width: 232px; 31 | height: 420px; 32 | -webkit-border-radius: 5px; 33 | -moz-border-radius: 5px; 34 | border-radius: 5px; 35 | } 36 | .beer p { 37 | margin-top: 10px; 38 | } 39 | form.list-beers { 40 | display: block; 41 | position:absolute; 42 | bottom: 10px; 43 | padding-left: 18px; 44 | } 45 | .beer img { 46 | margin-bottom: 20px; 47 | } 48 | .ta-search { 49 | width: 240px; 50 | } 51 | .guilds { 52 | text-align: center; 53 | } 54 | .guild-image { 55 | display: inline-block; 56 | margin: 10px; 57 | } -------------------------------------------------------------------------------- /params.json: -------------------------------------------------------------------------------- 1 | {"name":"OnTapp","tagline":"\"pithy tagline here\"","body":"# On Tap\r\n\r\n> HRR2 Thesis Project\r\n\r\n## Team\r\n\r\n - __Product Owner__: [Julie Knowles](https://github.com/JulieMarie)\r\n - __Scrum Master__: [Pactrick McPike](https://github.com/mcpike)\r\n - __Development Team Members__: [Victor Leung](https://github.com/victorleungtw)\r\n\r\n## Table of Contents\r\n\r\n1. [Usage](#Usage)\r\n1. [Requirements](#requirements)\r\n1. [Development](#development)\r\n 1. [Installing Dependencies](#installing-dependencies)\r\n 1. [Tasks](#tasks)\r\n1. [Team](#team)\r\n1. [Contributing](#contributing)\r\n\r\n## Usage\r\n\r\n> For demo, please visit: [https://tbc.com/](https://tbc.com/)\r\n\r\n## Requirements\r\n\r\n- TBC\r\n\r\n## Development\r\n\r\n### Installing Dependencies\r\n\r\nFrom within the root directory:\r\n\r\n```sh\r\nbower install\r\nnpm install\r\nnpm start\r\n```\r\n\r\n### Roadmap\r\n\r\nView the project roadmap [here](https://github.com/green-brass-doberman/on-tapp/issues)\r\n\r\n\r\n## Contributing\r\n\r\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."} -------------------------------------------------------------------------------- /config/strategies/twitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | TwitterStrategy = require('passport-twitter').Strategy, 8 | config = require('../config'), 9 | users = require('../../app/controllers/users.server.controller'); 10 | 11 | module.exports = function() { 12 | // Use twitter strategy 13 | passport.use(new TwitterStrategy({ 14 | consumerKey: config.twitter.clientID, 15 | consumerSecret: config.twitter.clientSecret, 16 | callbackURL: config.twitter.callbackURL, 17 | passReqToCallback: true 18 | }, 19 | function(req, token, tokenSecret, profile, done) { 20 | // Set the provider data and include tokens 21 | var providerData = profile._json; 22 | providerData.token = token; 23 | providerData.tokenSecret = tokenSecret; 24 | 25 | // Create the user OAuth profile 26 | var providerUserProfile = { 27 | displayName: profile.displayName, 28 | username: profile.username, 29 | provider: 'twitter', 30 | providerIdentifierField: 'id_str', 31 | providerData: providerData 32 | }; 33 | 34 | // Save the user OAuth profile 35 | users.saveOAuthUserProfile(req, providerUserProfile, done); 36 | } 37 | )); 38 | }; -------------------------------------------------------------------------------- /public/modules/users/controllers/authentication.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('users').controller('AuthenticationController', ['$scope', '$http', '$location', 'Authentication', 4 | function($scope, $http, $location, Authentication) { 5 | $scope.authentication = Authentication; 6 | 7 | // If user is signed in then redirect back home 8 | if ($scope.authentication.user) $location.path('/'); 9 | 10 | $scope.signup = function() { 11 | $http.post('/auth/signup', $scope.credentials).success(function(response) { 12 | // If successful we assign the response to the global user model 13 | $scope.authentication.user = response; 14 | 15 | // And redirect to the index page 16 | $location.path('/'); 17 | }).error(function(response) { 18 | $scope.error = response.message; 19 | }); 20 | }; 21 | 22 | $scope.signin = function() { 23 | $http.post('/auth/signin', $scope.credentials).success(function(response) { 24 | // If successful we assign the response to the global user model 25 | $scope.authentication.user = response; 26 | 27 | // And redirect to the index page 28 | $location.path('/'); 29 | }).error(function(response) { 30 | $scope.error = response.message; 31 | }); 32 | }; 33 | } 34 | ]); -------------------------------------------------------------------------------- /app/controllers/users/users.authorization.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | mongoose = require('mongoose'), 8 | User = mongoose.model('User'); 9 | 10 | /** 11 | * User middleware 12 | */ 13 | exports.userByID = function(req, res, next, id) { 14 | User.findOne({ 15 | _id: id 16 | }).exec(function(err, user) { 17 | if (err) return next(err); 18 | if (!user) return next(new Error('Failed to load User ' + id)); 19 | req.profile = user; 20 | next(); 21 | }); 22 | }; 23 | 24 | /** 25 | * Require login routing middleware 26 | */ 27 | exports.requiresLogin = function(req, res, next) { 28 | if (!req.isAuthenticated()) { 29 | return res.status(401).send({ 30 | message: 'User is not logged in' 31 | }); 32 | } 33 | 34 | next(); 35 | }; 36 | 37 | /** 38 | * User authorizations routing middleware 39 | */ 40 | exports.hasAuthorization = function(roles) { 41 | var _this = this; 42 | 43 | return function(req, res, next) { 44 | _this.requiresLogin(req, res, function() { 45 | if (_.intersection(req.user.roles, roles).length) { 46 | return next(); 47 | } else { 48 | return res.status(403).send({ 49 | message: 'User is not authorized' 50 | }); 51 | } 52 | }); 53 | }; 54 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # On Tapp 2 | 3 | > HRR2 Thesis Project 4 | 5 | [![Circle CI](https://circleci.com/gh/green-brass-doberman/on-tapp.svg?style=svg)](https://circleci.com/gh/green-brass-doberman/on-tapp) 6 | 7 | ## Team 8 | 9 | - __Product Owner__: [Julie Knowles](https://github.com/JulieMarie) 10 | - __Scrum Master__: [Victor Leung](https://github.com/victorleungtw) 11 | 12 | ## Table of Contents 13 | 14 | 1. [Usage](#Usage) 15 | 1. [Requirements](#requirements) 16 | 1. [Development](#development) 17 | 1. [Installing Dependencies](#installing-dependencies) 18 | 1. [Tasks](#tasks) 19 | 1. [Team](#team) 20 | 1. [Contributing](#contributing) 21 | 22 | ## Usage 23 | 24 | > For demo, please visit: [https://on-tapp.herokuapp.com/](https://on-tapp.herokuapp.com/) or [http://ontappapp.azurewebsites.net/](http://ontappapp.azurewebsites.net/) 25 | 26 | ## Requirements 27 | 28 | - Node.js & npm 29 | - MongoDB 30 | - Bower 31 | - Grunt 32 | 33 | ## Development 34 | 35 | ### Installing Dependencies 36 | 37 | From within the root directory: 38 | 39 | ```sh 40 | npm install 41 | grunt 42 | ``` 43 | 44 | ### Roadmap 45 | 46 | View the project roadmap [here](https://github.com/green-brass-doberman/on-tapp/issues) 47 | 48 | 49 | ## Contributing 50 | 51 | See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. 52 | -------------------------------------------------------------------------------- /public/modules/users/views/settings/social-accounts.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Connected social accounts:

3 |
4 | 10 |
11 |

Connect other social accounts:

12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
-------------------------------------------------------------------------------- /public/modules/core/controllers/search.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').controller('SearchController', ['$scope', 'Search', '$stateParams', '$state', 4 | function($scope, Search, $stateParams, $state) { 5 | // Search controller logic 6 | $scope.results = []; 7 | 8 | Search.getData($stateParams.keyword, $stateParams.page, $stateParams.searchtype).success(function(response, status) { 9 | $scope.status = status; 10 | if ($scope.status === 200) { 11 | if (response.totalResults !== undefined) { 12 | $scope.numberOfPages = response.numberOfPages; 13 | $scope.totalResults = response.totalResults; 14 | $scope.keyword = $stateParams.keyword; 15 | $scope.currentPage = $stateParams.page; 16 | $scope.searchType = $stateParams.searchtype; 17 | $scope.results = response.data; 18 | } else { 19 | $scope.totalResults = 0; 20 | $scope.numberOfPages = 0; 21 | } 22 | } else { 23 | $scope.results = response || 'Request failed'; 24 | } 25 | }); 26 | 27 | $scope.search = function(currentPage) { 28 | currentPage = currentPage || 1; 29 | $state.go('search', {'page': currentPage, 'keyword': $scope.keyword, 'searchtype': $scope.searchType}); 30 | }; 31 | } 32 | ]); -------------------------------------------------------------------------------- /public/modules/users/views/password/reset-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Reset your password

3 |
4 | 25 |
26 |
-------------------------------------------------------------------------------- /config/strategies/google.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | GoogleStrategy = require('passport-google-oauth').OAuth2Strategy, 8 | config = require('../config'), 9 | users = require('../../app/controllers/users.server.controller'); 10 | 11 | module.exports = function() { 12 | // Use google strategy 13 | passport.use(new GoogleStrategy({ 14 | clientID: config.google.clientID, 15 | clientSecret: config.google.clientSecret, 16 | callbackURL: config.google.callbackURL, 17 | passReqToCallback: true 18 | }, 19 | function(req, accessToken, refreshToken, profile, done) { 20 | // Set the provider data and include tokens 21 | var providerData = profile._json; 22 | providerData.accessToken = accessToken; 23 | providerData.refreshToken = refreshToken; 24 | 25 | // Create the user OAuth profile 26 | var providerUserProfile = { 27 | firstName: profile.name.givenName, 28 | lastName: profile.name.familyName, 29 | displayName: profile.displayName, 30 | email: profile.emails[0].value, 31 | username: profile.username, 32 | provider: 'google', 33 | providerIdentifierField: 'id', 34 | providerData: providerData 35 | }; 36 | 37 | // Save the user OAuth profile 38 | users.saveOAuthUserProfile(req, providerUserProfile, done); 39 | } 40 | )); 41 | }; -------------------------------------------------------------------------------- /app/controllers/users/users.profile.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | errorHandler = require('../errors.server.controller.js'), 8 | mongoose = require('mongoose'), 9 | passport = require('passport'), 10 | User = mongoose.model('User'); 11 | 12 | /** 13 | * Update user details 14 | */ 15 | exports.update = function(req, res) { 16 | // Init Variables 17 | var user = req.user; 18 | var message = null; 19 | 20 | // For security measurement we remove the roles from the req.body object 21 | delete req.body.roles; 22 | 23 | if (user) { 24 | // Merge existing user 25 | user = _.extend(user, req.body); 26 | user.updated = Date.now(); 27 | user.displayName = user.firstName + ' ' + user.lastName; 28 | 29 | user.save(function(err) { 30 | if (err) { 31 | return res.status(400).send({ 32 | message: errorHandler.getErrorMessage(err) 33 | }); 34 | } else { 35 | req.login(user, function(err) { 36 | if (err) { 37 | res.status(400).send(err); 38 | } else { 39 | res.json(user); 40 | } 41 | }); 42 | } 43 | }); 44 | } else { 45 | res.status(400).send({ 46 | message: 'User is not signed in' 47 | }); 48 | } 49 | }; 50 | 51 | /** 52 | * Send User 53 | */ 54 | exports.me = function(req, res) { 55 | res.json(req.user || null); 56 | }; -------------------------------------------------------------------------------- /config/strategies/facebook.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | FacebookStrategy = require('passport-facebook').Strategy, 8 | config = require('../config'), 9 | users = require('../../app/controllers/users.server.controller'); 10 | 11 | module.exports = function() { 12 | // Use facebook strategy 13 | passport.use(new FacebookStrategy({ 14 | clientID: config.facebook.clientID, 15 | clientSecret: config.facebook.clientSecret, 16 | callbackURL: config.facebook.callbackURL, 17 | passReqToCallback: true 18 | }, 19 | function(req, accessToken, refreshToken, profile, done) { 20 | // Set the provider data and include tokens 21 | var providerData = profile._json; 22 | providerData.accessToken = accessToken; 23 | providerData.refreshToken = refreshToken; 24 | 25 | // Create the user OAuth profile 26 | var providerUserProfile = { 27 | firstName: profile.name.givenName, 28 | lastName: profile.name.familyName, 29 | displayName: profile.displayName, 30 | email: profile.emails[0].value, 31 | username: profile.username, 32 | provider: 'facebook', 33 | providerIdentifierField: 'id', 34 | providerData: providerData 35 | }; 36 | 37 | // Save the user OAuth profile 38 | users.saveOAuthUserProfile(req, providerUserProfile, done); 39 | } 40 | )); 41 | }; -------------------------------------------------------------------------------- /app/tests/rating.server.model.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var should = require('should'), 7 | mongoose = require('mongoose'), 8 | User = mongoose.model('User'), 9 | Rating = mongoose.model('Rating'); 10 | 11 | /** 12 | * Globals 13 | */ 14 | var user, rating; 15 | 16 | /** 17 | * Unit tests 18 | */ 19 | describe('Rating Model Unit Tests:', function() { 20 | beforeEach(function(done) { 21 | user = new User({ 22 | firstName: 'Full', 23 | lastName: 'Name', 24 | displayName: 'Full Name', 25 | email: 'test@test.com', 26 | username: 'username', 27 | password: 'password' 28 | }); 29 | 30 | user.save(function() { 31 | rating = new Rating({ 32 | name: 'Rating Name', 33 | user: user 34 | }); 35 | 36 | done(); 37 | }); 38 | }); 39 | 40 | describe('Method Save', function() { 41 | it('should be able to save without problems', function(done) { 42 | return rating.save(function(err) { 43 | should.not.exist(err); 44 | done(); 45 | }); 46 | }); 47 | 48 | it('should be able to show an error when try to save without name', function(done) { 49 | rating.name = ''; 50 | 51 | return rating.save(function(err) { 52 | should.exist(err); 53 | done(); 54 | }); 55 | }); 56 | }); 57 | 58 | afterEach(function(done) { 59 | Rating.remove().exec(); 60 | User.remove().exec(); 61 | 62 | done(); 63 | }); 64 | }); -------------------------------------------------------------------------------- /PRESS-RELEASE.md: -------------------------------------------------------------------------------- 1 | # OnTapp # 2 | 3 | ## Heading ## 4 | > Catalog your beer tastebuds and share recommendations with like tastebudded people. 5 | 6 | ## Sub-Heading ## 7 | > For all lovers of frothy goodness to expand the horizons of their enthusiasm. 8 | 9 | ## Summary ## 10 | > Mobile accessible web application to allow beer lovers to share and receive recommendations on their favorite form of frothy, liquid refreshment. Will also seek to provide the most immediate outlet for their desired libation. 11 | 12 | ## Problem ## 13 | > People like beer. People would like to know where to readily access the beers they like. In addition, they would like to discover new and interesting beers, based not on beer categories, but based on their personal taste preferences. OnTapp's goal, is to solve those problems. 14 | 15 | ## Solution ## 16 | > Create a user-friendly platform for sharing information about beer tastes and accessibility. 17 | 18 | ## Quote from You ## 19 | > "Coming from America's 'Craft Beer Capital', we have been lacking an app designed for the beer enthusists, by beer enthusists. This app fills that gap!" 20 | 21 | ## How to Get Started ## 22 | > npm start-tapp 23 | 24 | ## Customer Quote ## 25 | > "This is what I would want out of a beer app. Untapped so far hasn't done that for me." 26 | 27 | ## Closing and Call to Action ## 28 | > Open a frothy libation and sign in to discover more at [OnTappApp.com](http://ontappapp.com) 29 | -------------------------------------------------------------------------------- /public/modules/ratings/views/view-rating.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 12 |
13 | 14 | 15 | Lasted voted on 16 | 17 | by 18 | 19 | 20 | 21 |
22 | 23 | 24 |

You may also likedislike:

25 | 26 |
27 |
28 |

{{recommendation.name}}

29 | 30 | {{recommendation.category.name}} 31 | 32 |
33 |
34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('newrelic'); 4 | /** 5 | * Module dependencies. 6 | */ 7 | var init = require('./config/init')(), 8 | config = require('./config/config'), 9 | mongoose = require('mongoose'), 10 | chalk = require('chalk'); 11 | 12 | /** 13 | * Main application entry file. 14 | * Please note that the order of loading is important. 15 | */ 16 | 17 | // Bootstrap db connection 18 | var db = mongoose.connect(config.db.uri, config.db.options, function(err) { 19 | if (err) { 20 | console.error(chalk.red('Could not connect to MongoDB!')); 21 | console.log(chalk.red(err)); 22 | } 23 | }); 24 | mongoose.connection.on('error', function(err) { 25 | console.error(chalk.red('MongoDB connection error: ' + err)); 26 | process.exit(-1); 27 | } 28 | ); 29 | 30 | // Init the express application 31 | var app = require('./config/express')(db); 32 | 33 | // Bootstrap passport config 34 | require('./config/passport')(); 35 | 36 | // Start the app by listening on 37 | app.listen(config.port); 38 | 39 | // Expose app 40 | exports = module.exports = app; 41 | 42 | // Logging initialization 43 | console.log('--'); 44 | console.log(chalk.green(config.app.title + ' application started')); 45 | console.log(chalk.green('Environment:\t\t\t' + process.env.NODE_ENV)); 46 | console.log(chalk.green('Port:\t\t\t\t' + config.port)); 47 | console.log(chalk.green('Database:\t\t\t' + config.db.uri)); 48 | if (process.env.NODE_ENV === 'secure') { 49 | console.log(chalk.green('HTTPs:\t\t\t\ton')); 50 | } 51 | console.log('--'); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var applicationConfiguration = require('./config/config'); 7 | 8 | // Karma configuration 9 | module.exports = function(config) { 10 | config.set({ 11 | // Frameworks to use 12 | frameworks: ['jasmine', 'jasmine-matchers'], 13 | 14 | // List of files / patterns to load in the browser 15 | files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests), 16 | 17 | // Test results reporter to use 18 | // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 19 | reporters: ['progress'], 20 | 21 | // Web server port 22 | port: 9876, 23 | 24 | // Enable / disable colors in the output (reporters and logs) 25 | colors: true, 26 | 27 | // Level of logging 28 | // Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 29 | logLevel: config.LOG_INFO, 30 | 31 | // Enable / disable watching file and executing tests whenever any file changes 32 | autoWatch: true, 33 | 34 | // Start these browsers, currently available: 35 | // - Chrome 36 | // - ChromeCanary 37 | // - Firefox 38 | // - Opera 39 | // - Safari (only Mac) 40 | // - PhantomJS 41 | // - IE (only Windows) 42 | browsers: ['PhantomJS'], 43 | 44 | // If browser does not capture in given timeout [ms], kill it 45 | captureTimeout: 60000, 46 | 47 | // Continuous Integration mode 48 | // If true, it capture browsers, run tests and exit 49 | singleRun: true 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /public/modules/users/controllers/password.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$http', '$location', 'Authentication', 4 | function($scope, $stateParams, $http, $location, Authentication) { 5 | $scope.authentication = Authentication; 6 | 7 | //If user is signed in then redirect back home 8 | if ($scope.authentication.user) $location.path('/'); 9 | 10 | // Submit forgotten password account id 11 | $scope.askForPasswordReset = function() { 12 | $scope.success = $scope.error = null; 13 | 14 | $http.post('/auth/forgot', $scope.credentials).success(function(response) { 15 | // Show user success message and clear form 16 | $scope.credentials = null; 17 | $scope.success = response.message; 18 | 19 | }).error(function(response) { 20 | // Show user error message and clear form 21 | $scope.credentials = null; 22 | $scope.error = response.message; 23 | }); 24 | }; 25 | 26 | // Change user password 27 | $scope.resetUserPassword = function() { 28 | $scope.success = $scope.error = null; 29 | 30 | $http.post('/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) { 31 | // If successful show success message and clear form 32 | $scope.passwordDetails = null; 33 | 34 | // Attach user profile 35 | Authentication.user = response; 36 | 37 | // And redirect to the index page 38 | $location.path('/password/reset/success'); 39 | }).error(function(response) { 40 | $scope.error = response.message; 41 | }); 42 | }; 43 | } 44 | ]); -------------------------------------------------------------------------------- /public/modules/users/config/users.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('users').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Users state routing 7 | $stateProvider. 8 | state('profile', { 9 | url: '/settings/profile', 10 | templateUrl: 'modules/users/views/settings/edit-profile.client.view.html' 11 | }). 12 | state('password', { 13 | url: '/settings/password', 14 | templateUrl: 'modules/users/views/settings/change-password.client.view.html' 15 | }). 16 | state('accounts', { 17 | url: '/settings/accounts', 18 | templateUrl: 'modules/users/views/settings/social-accounts.client.view.html' 19 | }). 20 | state('signup', { 21 | url: '/signup', 22 | templateUrl: 'modules/users/views/authentication/signup.client.view.html' 23 | }). 24 | state('signin', { 25 | url: '/signin', 26 | templateUrl: 'modules/users/views/authentication/signin.client.view.html' 27 | }). 28 | state('forgot', { 29 | url: '/password/forgot', 30 | templateUrl: 'modules/users/views/password/forgot-password.client.view.html' 31 | }). 32 | state('reset-invalid', { 33 | url: '/password/reset/invalid', 34 | templateUrl: 'modules/users/views/password/reset-password-invalid.client.view.html' 35 | }). 36 | state('reset-success', { 37 | url: '/password/reset/success', 38 | templateUrl: 'modules/users/views/password/reset-password-success.client.view.html' 39 | }). 40 | state('reset', { 41 | url: '/password/reset/:token', 42 | templateUrl: 'modules/users/views/password/reset-password.client.view.html' 43 | }); 44 | } 45 | ]); -------------------------------------------------------------------------------- /public/modules/users/views/settings/change-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Change your password

3 |
4 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /app/tests/user.server.model.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var should = require('should'), 7 | mongoose = require('mongoose'), 8 | User = mongoose.model('User'); 9 | 10 | /** 11 | * Globals 12 | */ 13 | var user, user2; 14 | 15 | /** 16 | * Unit tests 17 | */ 18 | describe('User Model Unit Tests:', function() { 19 | before(function(done) { 20 | user = new User({ 21 | firstName: 'Full', 22 | lastName: 'Name', 23 | displayName: 'Full Name', 24 | email: 'test@test.com', 25 | username: 'username', 26 | password: 'password', 27 | provider: 'local' 28 | }); 29 | user2 = new User({ 30 | firstName: 'Full', 31 | lastName: 'Name', 32 | displayName: 'Full Name', 33 | email: 'test@test.com', 34 | username: 'username', 35 | password: 'password', 36 | provider: 'local' 37 | }); 38 | 39 | done(); 40 | }); 41 | 42 | describe('Method Save', function() { 43 | it('should begin with no users', function(done) { 44 | User.find({}, function(err, users) { 45 | users.should.have.length(0); 46 | done(); 47 | }); 48 | }); 49 | 50 | it('should be able to save without problems', function(done) { 51 | user.save(done); 52 | }); 53 | 54 | it('should fail to save an existing user again', function(done) { 55 | user.save(); 56 | return user2.save(function(err) { 57 | should.exist(err); 58 | done(); 59 | }); 60 | }); 61 | 62 | it('should be able to show an error when try to save without first name', function(done) { 63 | user.firstName = ''; 64 | return user.save(function(err) { 65 | should.exist(err); 66 | done(); 67 | }); 68 | }); 69 | }); 70 | 71 | after(function(done) { 72 | User.remove().exec(); 73 | done(); 74 | }); 75 | }); -------------------------------------------------------------------------------- /public/modules/users/views/settings/edit-profile.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Edit your profile

3 | 4 |
5 | 34 |
35 |
36 | -------------------------------------------------------------------------------- /public/modules/users/views/authentication/signin.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Sign in using your social accounts

3 | 14 |

Or with your account

15 |
16 | 38 |
39 |
-------------------------------------------------------------------------------- /public/modules/beers/views/beers.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{group.beers[0].available.name}}

5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 | 13 |
14 | {{beer.name}} 15 | 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 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment. 3 | "browser": true, // Standard browser globals e.g. `window`, `document`. 4 | "esnext": true, // Allow ES.next specific features such as `const` and `let`. 5 | "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.). 6 | "camelcase": false, // Permit only camelcase for `var` and `object indexes`. 7 | "curly": false, // Require {} for every new block or scope. 8 | "eqeqeq": true, // Require triple equals i.e. `===`. 9 | "immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 10 | "latedef": true, // Prohibit variable use before definition. 11 | "newcap": true, // Require capitalization of all constructor functions e.g. `new F()`. 12 | "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`. 13 | "quotmark": "single", // Define quotes to string values. 14 | "regexp": true, // Prohibit `.` and `[^...]` in regular expressions. 15 | "undef": true, // Require all non-global variables be declared before they are used. 16 | "unused": false, // Warn unused variables. 17 | "strict": true, // Require `use strict` pragma in every file. 18 | "trailing": true, // Prohibit trailing whitespaces. 19 | "smarttabs": false, // Suppresses warnings about mixed tabs and spaces 20 | "globals": { // Globals variables. 21 | "jasmine": true, 22 | "angular": true, 23 | "ApplicationConfiguration": true 24 | }, 25 | "predef": [ // Extra globals. 26 | "define", 27 | "require", 28 | "exports", 29 | "module", 30 | "describe", 31 | "before", 32 | "beforeEach", 33 | "after", 34 | "afterEach", 35 | "it", 36 | "inject", 37 | "expect" 38 | ], 39 | "indent": 4, // Specify indentation spacing 40 | "devel": true, // Allow development statements e.g. `console.log();`. 41 | "noempty": true // Prohibit use of empty blocks. 42 | } 43 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | glob = require('glob'); 8 | 9 | /** 10 | * Load app configurations 11 | */ 12 | module.exports = _.extend( 13 | require('./env/all'), 14 | require('./env/' + process.env.NODE_ENV) || {} 15 | ); 16 | 17 | /** 18 | * Get files by glob patterns 19 | */ 20 | module.exports.getGlobbedFiles = function(globPatterns, removeRoot) { 21 | // For context switching 22 | var _this = this; 23 | 24 | // URL paths regex 25 | var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i'); 26 | 27 | // The output array 28 | var output = []; 29 | 30 | // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob 31 | if (_.isArray(globPatterns)) { 32 | globPatterns.forEach(function(globPattern) { 33 | output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot)); 34 | }); 35 | } else if (_.isString(globPatterns)) { 36 | if (urlRegex.test(globPatterns)) { 37 | output.push(globPatterns); 38 | } else { 39 | glob(globPatterns, { 40 | sync: true 41 | }, function(err, files) { 42 | if (removeRoot) { 43 | files = files.map(function(file) { 44 | return file.replace(removeRoot, ''); 45 | }); 46 | } 47 | 48 | output = _.union(output, files); 49 | }); 50 | } 51 | } 52 | 53 | return output; 54 | }; 55 | 56 | /** 57 | * Get the modules JavaScript files 58 | */ 59 | module.exports.getJavaScriptAssets = function(includeTests) { 60 | var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js), 'public/'); 61 | 62 | // To include tests 63 | if (includeTests) { 64 | output = _.union(output, this.getGlobbedFiles(this.assets.tests)); 65 | } 66 | 67 | return output; 68 | }; 69 | 70 | /** 71 | * Get the modules CSS files 72 | */ 73 | module.exports.getCSSAssets = function() { 74 | var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/'); 75 | return output; 76 | }; -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: { 5 | uri: 'mongodb://localhost/mean-dev', 6 | options: { 7 | user: '', 8 | pass: '' 9 | } 10 | }, 11 | log: { 12 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 13 | format: 'dev', 14 | // Stream defaults to process.stdout 15 | // Uncomment to enable logging to a log on the file system 16 | options: { 17 | //stream: 'access.log' 18 | } 19 | }, 20 | app: { 21 | title: 'OnTapp - Hack Reactor Thesis Project' 22 | }, 23 | facebook: { 24 | clientID: process.env.FACEBOOK_ID || '968149419880980', 25 | clientSecret: process.env.FACEBOOK_SECRET || 'fc60727d78e8fcd2e975822c8eef619e', 26 | callbackURL: '/auth/facebook/callback' 27 | }, 28 | twitter: { 29 | clientID: process.env.TWITTER_KEY || 'k186DeKdHcNN4Vlk8lB9MmjBW', 30 | clientSecret: process.env.TWITTER_SECRET || '9V0X34pyjBuixI8bRwuGf639spcOWs91Q1Fb9Iq4qjDPRgsCf7', 31 | callbackURL: '/auth/twitter/callback' 32 | }, 33 | google: { 34 | clientID: process.env.GOOGLE_ID || '768801944338-ms3g3ic84auevq51a2t4cld7ept7ffos.apps.googleusercontent.com', 35 | clientSecret: process.env.GOOGLE_SECRET || '5xxSzR-ltreqF7EloyCt9JVC', 36 | callbackURL: '/auth/google/callback' 37 | }, 38 | // linkedin: { 39 | // clientID: process.env.LINKEDIN_ID || 'APP_ID', 40 | // clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 41 | // callbackURL: '/auth/linkedin/callback' 42 | // }, 43 | // github: { 44 | // clientID: process.env.GITHUB_ID || 'APP_ID', 45 | // clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', 46 | // callbackURL: '/auth/github/callback' 47 | // }, 48 | mailer: { 49 | from: process.env.MAILER_FROM || 'ontappapp@gmail.com', 50 | options: { 51 | service: process.env.MAILER_SERVICE_PROVIDER || 'Gmail', 52 | auth: { 53 | user: process.env.MAILER_EMAIL_ID || 'ontappapp@gmail.com', 54 | pass: process.env.MAILER_PASSWORD || 'Cs0cs5#BVfrBR5a&ZK@Z' 55 | } 56 | } 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /config/env/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: { 5 | uri: 'mongodb://localhost/mean-test', 6 | options: { 7 | user: '', 8 | pass: '' 9 | } 10 | }, 11 | port: 3001, 12 | log: { 13 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 14 | format: 'dev', 15 | // Stream defaults to process.stdout 16 | // Uncomment to enable logging to a log on the file system 17 | options: { 18 | //stream: 'access.log' 19 | } 20 | }, 21 | app: { 22 | title: 'OnTapp - Test Environment' 23 | }, 24 | facebook: { 25 | clientID: process.env.FACEBOOK_ID || '968149419880980', 26 | clientSecret: process.env.FACEBOOK_SECRET || 'fc60727d78e8fcd2e975822c8eef619e', 27 | callbackURL: '/auth/facebook/callback' 28 | }, 29 | twitter: { 30 | clientID: process.env.TWITTER_KEY || 'k186DeKdHcNN4Vlk8lB9MmjBW', 31 | clientSecret: process.env.TWITTER_SECRET || '9V0X34pyjBuixI8bRwuGf639spcOWs91Q1Fb9Iq4qjDPRgsCf7', 32 | callbackURL: '/auth/twitter/callback' 33 | }, 34 | google: { 35 | clientID: process.env.GOOGLE_ID || '768801944338-ms3g3ic84auevq51a2t4cld7ept7ffos.apps.googleusercontent.com', 36 | clientSecret: process.env.GOOGLE_SECRET || '5xxSzR-ltreqF7EloyCt9JVC', 37 | callbackURL: '/auth/google/callback' 38 | }, 39 | // linkedin: { 40 | // clientID: process.env.LINKEDIN_ID || 'APP_ID', 41 | // clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 42 | // callbackURL: '/auth/linkedin/callback' 43 | // }, 44 | // github: { 45 | // clientID: process.env.GITHUB_ID || 'APP_ID', 46 | // clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', 47 | // callbackURL: '/auth/github/callback' 48 | // }, 49 | mailer: { 50 | from: process.env.MAILER_FROM || 'MAILER_FROM', 51 | options: { 52 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 53 | auth: { 54 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 55 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 56 | } 57 | } 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /public/modules/beers/tests/beer.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | // Beer Controller Spec 5 | describe('Beer Controller Tests', function() { 6 | // Initialize global variables 7 | var BeerController, 8 | scope, 9 | $httpBackend, 10 | $stateParams, 11 | $location; 12 | 13 | // The $resource service augments the response object with methods for updating and deleting the resource. 14 | // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match 15 | // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. 16 | // When the toEqualData matcher compares two objects, it takes only object properties into 17 | // account and ignores methods. 18 | beforeEach(function() { 19 | jasmine.addMatchers({ 20 | toEqualData: function(util, customEqualityTesters) { 21 | return { 22 | compare: function(actual, expected) { 23 | return { 24 | pass: angular.equals(actual, expected) 25 | }; 26 | } 27 | }; 28 | } 29 | }); 30 | }); 31 | 32 | // Then we can start by loading the main application module 33 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 34 | 35 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 36 | // This allows us to inject a service but then attach it to a variable 37 | // with the same name as the service. 38 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { 39 | // Set a new global scope 40 | scope = $rootScope.$new(); 41 | 42 | // Point global variables to injected services 43 | $stateParams = _$stateParams_; 44 | $httpBackend = _$httpBackend_; 45 | $location = _$location_; 46 | 47 | // Initialize the Beer controller. 48 | BeerController = $controller('BeerController', { 49 | $scope: scope 50 | }); 51 | })); 52 | 53 | it('Should do some controller test', inject(function() { 54 | // The test logic 55 | // ... 56 | })); 57 | }); 58 | }()); -------------------------------------------------------------------------------- /public/modules/beers/tests/beers.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | // Beers Controller Spec 5 | describe('Beers Controller Tests', function() { 6 | // Initialize global variables 7 | var BeersController, 8 | scope, 9 | $httpBackend, 10 | $stateParams, 11 | $location; 12 | 13 | // The $resource service augments the response object with methods for updating and deleting the resource. 14 | // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match 15 | // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. 16 | // When the toEqualData matcher compares two objects, it takes only object properties into 17 | // account and ignores methods. 18 | beforeEach(function() { 19 | jasmine.addMatchers({ 20 | toEqualData: function(util, customEqualityTesters) { 21 | return { 22 | compare: function(actual, expected) { 23 | return { 24 | pass: angular.equals(actual, expected) 25 | }; 26 | } 27 | }; 28 | } 29 | }); 30 | }); 31 | 32 | // Then we can start by loading the main application module 33 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 34 | 35 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 36 | // This allows us to inject a service but then attach it to a variable 37 | // with the same name as the service. 38 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { 39 | // Set a new global scope 40 | scope = $rootScope.$new(); 41 | 42 | // Point global variables to injected services 43 | $stateParams = _$stateParams_; 44 | $httpBackend = _$httpBackend_; 45 | $location = _$location_; 46 | 47 | // Initialize the Beers controller. 48 | BeersController = $controller('BeersController', { 49 | $scope: scope 50 | }); 51 | })); 52 | 53 | it('Should do some controller test', inject(function() { 54 | // The test logic 55 | // ... 56 | })); 57 | }); 58 | }()); 59 | -------------------------------------------------------------------------------- /public/modules/core/tests/search.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | // Search Controller Spec 5 | describe('Search Controller Tests', function() { 6 | // Initialize global variables 7 | var SearchController, 8 | scope, 9 | $httpBackend, 10 | $stateParams, 11 | $location; 12 | 13 | // The $resource service augments the response object with methods for updating and deleting the resource. 14 | // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match 15 | // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. 16 | // When the toEqualData matcher compares two objects, it takes only object properties into 17 | // account and ignores methods. 18 | beforeEach(function() { 19 | jasmine.addMatchers({ 20 | toEqualData: function(util, customEqualityTesters) { 21 | return { 22 | compare: function(actual, expected) { 23 | return { 24 | pass: angular.equals(actual, expected) 25 | }; 26 | } 27 | }; 28 | } 29 | }); 30 | }); 31 | 32 | // Then we can start by loading the main application module 33 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 34 | 35 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 36 | // This allows us to inject a service but then attach it to a variable 37 | // with the same name as the service. 38 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { 39 | // Set a new global scope 40 | scope = $rootScope.$new(); 41 | 42 | // Point global variables to injected services 43 | $stateParams = _$stateParams_; 44 | $httpBackend = _$httpBackend_; 45 | $location = _$location_; 46 | 47 | // Initialize the Search controller. 48 | SearchController = $controller('SearchController', { 49 | $scope: scope 50 | }); 51 | })); 52 | 53 | it('Should do some controller test', inject(function() { 54 | // The test logic 55 | // ... 56 | })); 57 | }); 58 | }()); -------------------------------------------------------------------------------- /public/modules/nearby/tests/brewery.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | // Brewery Controller Spec 5 | describe('Brewery Controller Tests', function() { 6 | // Initialize global variables 7 | var BreweryController, 8 | scope, 9 | $httpBackend, 10 | $stateParams, 11 | $location; 12 | 13 | // The $resource service augments the response object with methods for updating and deleting the resource. 14 | // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match 15 | // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. 16 | // When the toEqualData matcher compares two objects, it takes only object properties into 17 | // account and ignores methods. 18 | beforeEach(function() { 19 | jasmine.addMatchers({ 20 | toEqualData: function(util, customEqualityTesters) { 21 | return { 22 | compare: function(actual, expected) { 23 | return { 24 | pass: angular.equals(actual, expected) 25 | }; 26 | } 27 | }; 28 | } 29 | }); 30 | }); 31 | 32 | // Then we can start by loading the main application module 33 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 34 | 35 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 36 | // This allows us to inject a service but then attach it to a variable 37 | // with the same name as the service. 38 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { 39 | // Set a new global scope 40 | scope = $rootScope.$new(); 41 | 42 | // Point global variables to injected services 43 | $stateParams = _$stateParams_; 44 | $httpBackend = _$httpBackend_; 45 | $location = _$location_; 46 | 47 | // Initialize the Brewery controller. 48 | BreweryController = $controller('BreweryController', { 49 | $scope: scope 50 | }); 51 | })); 52 | 53 | it('Should do some controller test', inject(function() { 54 | // The test logic 55 | // ... 56 | })); 57 | }); 58 | }()); -------------------------------------------------------------------------------- /public/dist/application.min.css: -------------------------------------------------------------------------------- 1 | .carousel,.nav,.pagination,.panel-title a{cursor:pointer}.undecorated-link:hover{text-decoration:none}.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}.ng-invalid.ng-dirty{border-color:#FA787E}.ng-valid.ng-dirty{border-color:#78FA89}.browsehappy.jumbotron.hide,body.ng-cloak{display:block}.grey-container{background-color:#f5f5f5;border:1px solid #e3e3e3;margin:40px 0;padding-top:20px;padding-bottom:20px}.search-bar{width:240px;display:inline;margin-top:5px}.search-form{margin-bottom:5px}p.center{text-align:center;margin:0;line-height:1.3em}.brewery-info p{text-align:center;margin:0;line-height:1.8em}p.content{white-space:pre-wrap;line-height:1.3em;padding-right:15px}img.brewery-img{margin-bottom:15px}p.beer-btn{text-align:center;margin-top:15px}.beer{box-sizing:border-box;font-size:.8em;line-height:1.3em;margin:0 15px 15px 0;padding:5px 15px 0;border:1px solid #5a5a5a;width:232px;height:420px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.beer p{margin-top:10px}form.list-beers{display:block;position:absolute;bottom:10px;padding-left:18px}.beer img{margin-bottom:20px}.ta-search{width:240px}.guilds{text-align:center}.guild-image{display:inline-block;margin:10px}.jumbotron{text-align:center;background-color:transparent}.navbar-static-top{margin-bottom:0}html{position:relative;min-height:100%}body{margin-bottom:108px}.footer{height:108px;width:100%;position:absolute;bottom:0}.stars{font-size:40px}.angular-google-map,.angular-google-map-container{height:100%}.left-half-container{position:absolute;left:0;right:60%;top:0;bottom:0;height:100%;margin-top:53px}.right-half-container{position:absolute;left:40%;right:0;top:0;bottom:0;margin-top:53px;overflow:scroll}@media (max-width:750px){.angular-google-map,.angular-google-map-container{height:250px}.left-half-container{position:absolute;left:0;right:0;top:0;bottom:75%;height:100%;margin-top:53px}.right-half-container{position:absolute;left:0;right:0;top:25%;bottom:0}}@media (min-width:992px){.nav-users{position:fixed}}.remove-account-container{display:inline-block;position:relative}.btn-remove-account{top:10px;right:10px;position:absolute} -------------------------------------------------------------------------------- /app/routes/users.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'); 7 | 8 | module.exports = function(app) { 9 | // User Routes 10 | var users = require('../../app/controllers/users.server.controller'); 11 | 12 | // Setting up the users profile api 13 | app.route('/users/me').get(users.me); 14 | app.route('/users').put(users.update); 15 | app.route('/users/accounts').delete(users.removeOAuthProvider); 16 | 17 | // Setting up the users password api 18 | app.route('/users/password').post(users.changePassword); 19 | app.route('/auth/forgot').post(users.forgot); 20 | app.route('/auth/reset/:token').get(users.validateResetToken); 21 | app.route('/auth/reset/:token').post(users.reset); 22 | 23 | // Setting up the users authentication api 24 | app.route('/auth/signup').post(users.signup); 25 | app.route('/auth/signin').post(users.signin); 26 | app.route('/auth/signout').get(users.signout); 27 | 28 | // Setting the facebook oauth routes 29 | app.route('/auth/facebook').get(passport.authenticate('facebook', { 30 | scope: ['email'] 31 | })); 32 | app.route('/auth/facebook/callback').get(users.oauthCallback('facebook')); 33 | 34 | // Setting the twitter oauth routes 35 | app.route('/auth/twitter').get(passport.authenticate('twitter')); 36 | app.route('/auth/twitter/callback').get(users.oauthCallback('twitter')); 37 | 38 | // Setting the google oauth routes 39 | app.route('/auth/google').get(passport.authenticate('google', { 40 | scope: [ 41 | 'https://www.googleapis.com/auth/userinfo.profile', 42 | 'https://www.googleapis.com/auth/userinfo.email' 43 | ] 44 | })); 45 | app.route('/auth/google/callback').get(users.oauthCallback('google')); 46 | 47 | // // Setting the linkedin oauth routes 48 | // app.route('/auth/linkedin').get(passport.authenticate('linkedin')); 49 | // app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin')); 50 | 51 | // // Setting the github oauth routes 52 | // app.route('/auth/github').get(passport.authenticate('github')); 53 | // app.route('/auth/github/callback').get(users.oauthCallback('github')); 54 | 55 | // Finish by binding the user middleware 56 | app.param('userId', users.userByID); 57 | }; -------------------------------------------------------------------------------- /public/modules/ratings/controllers/ratings.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Ratings controller 4 | angular.module('ratings').controller('RatingsController', ['$scope', '$stateParams', '$location', 'Authentication', 'Ratings', 'StyleQuery', 5 | function($scope, $stateParams, $location, Authentication, Ratings, StyleQuery) { 6 | $scope.authentication = Authentication; 7 | 8 | // Remove existing Rating 9 | $scope.remove = function(rating) { 10 | if ( rating ) { 11 | rating.$remove(); 12 | 13 | for (var i in $scope.ratings) { 14 | if ($scope.ratings [i] === rating) { 15 | $scope.ratings.splice(i, 1); 16 | } 17 | } 18 | } else { 19 | $scope.rating.$remove(function() { 20 | $location.path('ratings'); 21 | }); 22 | } 23 | }; 24 | 25 | // Update existing Rating 26 | $scope.update = function() { 27 | var rating = $scope.rating; 28 | 29 | rating.$update(function() { 30 | $location.path('ratings/' + rating._id); 31 | }, function(errorResponse) { 32 | $scope.error = errorResponse.data.message; 33 | }); 34 | }; 35 | 36 | // Find a list of Ratings 37 | $scope.find = function() { 38 | $scope.ratings = Ratings.query(); 39 | }; 40 | 41 | // Find existing Rating 42 | $scope.findOne = function() { 43 | $scope.rating = Ratings.get({ 44 | ratingId: $stateParams.ratingId 45 | }); 46 | 47 | $scope.rating.$promise.then(function(data) { 48 | getStars(data.stars); 49 | getRecommendations(data.styleName); 50 | }); 51 | }; 52 | 53 | //an array to store number of stars 54 | $scope.stars = []; 55 | 56 | // get the number of stars 57 | var getStars = function(noOfStars){ 58 | for (var i = 0; i < noOfStars; i++) { 59 | $scope.stars.push(i); 60 | } 61 | }; 62 | 63 | // Find the beers in the same category 64 | var getRecommendations = function(styleName){ 65 | StyleQuery.getStyle(styleName).success(handleSuccess); 66 | }; 67 | 68 | // an array to store recommendations 69 | $scope.recommendations = []; 70 | 71 | // pushing recommendations data from $http request 72 | var handleSuccess = function(data, status){ 73 | $scope.recommendations = data.data; 74 | }; 75 | } 76 | ]); 77 | -------------------------------------------------------------------------------- /public/modules/nearby/tests/nearby.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | // Nearby Controller Spec 5 | describe('Nearby Controller Tests', function() { 6 | // Initialize global variables 7 | var NearbyController, 8 | scope, 9 | $httpBackend, 10 | $stateParams, 11 | $location; 12 | 13 | // The $resource service augments the response object with methods for updating and deleting the resource. 14 | // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match 15 | // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. 16 | // When the toEqualData matcher compares two objects, it takes only object properties into 17 | // account and ignores methods. 18 | beforeEach(function() { 19 | jasmine.addMatchers({ 20 | toEqualData: function(util, customEqualityTesters) { 21 | return { 22 | compare: function(actual, expected) { 23 | return { 24 | pass: angular.equals(actual, expected) 25 | }; 26 | } 27 | }; 28 | } 29 | }); 30 | }); 31 | 32 | // Then we can start by loading the main application module 33 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 34 | 35 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 36 | // This allows us to inject a service but then attach it to a variable 37 | // with the same name as the service. 38 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { 39 | // Set a new global scope 40 | scope = $rootScope.$new(); 41 | 42 | // Point global variables to injected services 43 | $stateParams = _$stateParams_; 44 | $httpBackend = _$httpBackend_; 45 | $location = _$location_; 46 | 47 | // Initialize the Nearby controller. 48 | NearbyController = $controller('NearbyController', { 49 | $scope: scope 50 | }); 51 | })); 52 | 53 | it('Should have an array to store breweries data', inject(function() { 54 | expect(scope.breweries).toBeArray(); 55 | })); 56 | 57 | it('Should have an array to store markers', inject(function() { 58 | expect(scope.allMarkers).toBeArray(); 59 | })); 60 | 61 | }); 62 | }()); 63 | -------------------------------------------------------------------------------- /public/modules/users/controllers/settings.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('users').controller('SettingsController', ['$scope', '$http', '$location', 'Users', 'Authentication', 4 | function($scope, $http, $location, Users, Authentication) { 5 | $scope.user = Authentication.user; 6 | 7 | // If user is not signed in then redirect back home 8 | if (!$scope.user) $location.path('/'); 9 | 10 | // Check if there are additional accounts 11 | $scope.hasConnectedAdditionalSocialAccounts = function(provider) { 12 | for (var i in $scope.user.additionalProvidersData) { 13 | return true; 14 | } 15 | 16 | return false; 17 | }; 18 | 19 | // Check if provider is already in use with current user 20 | $scope.isConnectedSocialAccount = function(provider) { 21 | return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); 22 | }; 23 | 24 | // Remove a user social account 25 | $scope.removeUserSocialAccount = function(provider) { 26 | $scope.success = $scope.error = null; 27 | 28 | $http.delete('/users/accounts', { 29 | params: { 30 | provider: provider 31 | } 32 | }).success(function(response) { 33 | // If successful show success message and clear form 34 | $scope.success = true; 35 | $scope.user = Authentication.user = response; 36 | }).error(function(response) { 37 | $scope.error = response.message; 38 | }); 39 | }; 40 | 41 | // Update a user profile 42 | $scope.updateUserProfile = function(isValid) { 43 | if (isValid) { 44 | $scope.success = $scope.error = null; 45 | var user = new Users($scope.user); 46 | 47 | user.$update(function(response) { 48 | $scope.success = true; 49 | Authentication.user = response; 50 | }, function(response) { 51 | $scope.error = response.data.message; 52 | }); 53 | } else { 54 | $scope.submitted = true; 55 | } 56 | }; 57 | 58 | // Change user password 59 | $scope.changeUserPassword = function() { 60 | $scope.success = $scope.error = null; 61 | 62 | $http.post('/users/password', $scope.passwordDetails).success(function(response) { 63 | // If successful show success message and clear form 64 | $scope.success = true; 65 | $scope.passwordDetails = null; 66 | }).error(function(response) { 67 | $scope.error = response.message; 68 | }); 69 | }; 70 | } 71 | ]); -------------------------------------------------------------------------------- /public/modules/users/views/authentication/signup.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Sign up using your social accounts

3 | 14 |

Or with your email

15 |
16 | 47 |
48 |
-------------------------------------------------------------------------------- /public/modules/beers/views/beer.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 |
8 | 9 |
10 |
    11 |
  • 12 | {{beer.abv}}% 13 | abv 14 |
  • 15 |
  • 16 | {{beer.ibu}} 17 | ibu 18 |
  • 19 | 20 |
  • 21 | {{beer.srmId}} 22 | N/A 23 | srm 24 |
  • 25 | 26 |
  • 27 | {{beer.og}} 28 | N/A 29 | og 30 |
  • 31 |
32 |
33 |
34 |
35 |
Name:
36 |
{{beer.name}}
37 |
38 |
39 |
Brewery:
40 | 43 |
44 |
45 |
Style:
46 | 49 |
50 |
51 |
Availability:
52 |
{{beer.available.description}}
53 |
54 |
55 |
Serving:
56 |
{{beer.glass.name}}
57 |
58 |
59 |

{{beer.description}}

60 |
61 |
62 | Food Pairings:
63 |

{{beer.foodPairings}}

64 |
65 |
66 |
67 |
68 |
-------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: { 5 | uri: process.env.MONGOHQ_URL || process.env.CUSTOMCONNSTR_MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean', 6 | options: { 7 | user: '', 8 | pass: '' 9 | } 10 | }, 11 | log: { 12 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 13 | format: 'combined', 14 | // Stream defaults to process.stdout 15 | // Uncomment to enable logging to a log on the file system 16 | options: { 17 | stream: 'access.log' 18 | } 19 | }, 20 | assets: { 21 | lib: { 22 | css: [ 23 | 'public/lib/bootstrap/dist/css/bootstrap.min.css', 24 | 'public/lib/fontawesome/css/font-awesome.min.css', 25 | 'public/lib/flat-ui/dist/css/flat-ui.min.css' 26 | ], 27 | js: [ 28 | 'public/lib/angular/angular.min.js', 29 | 'public/lib/angular-resource/angular-resource.min.js', 30 | 'public/lib/angular-animate/angular-animate.min.js', 31 | 'public/lib/angular-ui-router/release/angular-ui-router.min.js', 32 | 'public/lib/angular-ui-utils/ui-utils.min.js', 33 | 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js', 34 | 'public/lib/lodash/dist/lodash.min.js', 35 | 'public/lib/angular-google-maps/dist/angular-google-maps.min.js', 36 | 'public/lib/angularjs-geolocation/src/geolocation.js', 37 | 'public/lib/spin.js/spin.js', 38 | 'public/lib/angular-spinner/angular-spinner.min.js', 39 | 'public/lib/fastclick/lib/fastclick.js' 40 | ] 41 | }, 42 | css: 'public/dist/application.min.css', 43 | js: 'public/dist/application.min.js' 44 | }, 45 | facebook: { 46 | clientID: process.env.FACEBOOK_ID || '968149419880980', 47 | clientSecret: process.env.FACEBOOK_SECRET || 'fc60727d78e8fcd2e975822c8eef619e', 48 | callbackURL: '/auth/facebook/callback' 49 | }, 50 | twitter: { 51 | clientID: process.env.TWITTER_KEY || 'k186DeKdHcNN4Vlk8lB9MmjBW', 52 | clientSecret: process.env.TWITTER_SECRET || '9V0X34pyjBuixI8bRwuGf639spcOWs91Q1Fb9Iq4qjDPRgsCf7', 53 | callbackURL: '/auth/twitter/callback' 54 | }, 55 | google: { 56 | clientID: process.env.GOOGLE_ID || '768801944338-ms3g3ic84auevq51a2t4cld7ept7ffos.apps.googleusercontent.com', 57 | clientSecret: process.env.GOOGLE_SECRET || '5xxSzR-ltreqF7EloyCt9JVC', 58 | callbackURL: '/auth/google/callback' 59 | }, 60 | mailer: { 61 | from: process.env.MAILER_FROM || 'MAILER_FROM', 62 | options: { 63 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 64 | auth: { 65 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 66 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 67 | } 68 | } 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /config/env/secure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | port: 8443, 5 | db: { 6 | uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean', 7 | options: { 8 | user: '', 9 | pass: '' 10 | } 11 | }, 12 | log: { 13 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 14 | format: 'combined', 15 | // Stream defaults to process.stdout 16 | // Uncomment to enable logging to a log on the file system 17 | options: { 18 | stream: 'access.log' 19 | } 20 | }, 21 | assets: { 22 | lib: { 23 | css: [ 24 | 'public/lib/bootstrap/dist/css/bootstrap.min.css', 25 | 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css', 26 | ], 27 | js: [ 28 | 'public/lib/angular/angular.min.js', 29 | 'public/lib/angular-resource/angular-resource.min.js', 30 | 'public/lib/angular-animate/angular-animate.min.js', 31 | 'public/lib/angular-ui-router/release/angular-ui-router.min.js', 32 | 'public/lib/angular-ui-utils/ui-utils.min.js', 33 | 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js' 34 | ] 35 | }, 36 | css: 'public/dist/application.min.css', 37 | js: 'public/dist/application.min.js' 38 | }, 39 | facebook: { 40 | clientID: process.env.FACEBOOK_ID || '968149419880980', 41 | clientSecret: process.env.FACEBOOK_SECRET || 'fc60727d78e8fcd2e975822c8eef619e', 42 | callbackURL: 'https://localhost:443/auth/facebook/callback' 43 | }, 44 | twitter: { 45 | clientID: process.env.TWITTER_KEY || 'k186DeKdHcNN4Vlk8lB9MmjBW', 46 | clientSecret: process.env.TWITTER_SECRET || '9V0X34pyjBuixI8bRwuGf639spcOWs91Q1Fb9Iq4qjDPRgsCf7', 47 | callbackURL: 'https://localhost:443/auth/twitter/callback' 48 | }, 49 | google: { 50 | clientID: process.env.GOOGLE_ID || '768801944338-ms3g3ic84auevq51a2t4cld7ept7ffos.apps.googleusercontent.com', 51 | clientSecret: process.env.GOOGLE_SECRET || '5xxSzR-ltreqF7EloyCt9JVC', 52 | callbackURL: 'https://localhost:443/auth/google/callback' 53 | }, 54 | // linkedin: { 55 | // clientID: process.env.LINKEDIN_ID || 'APP_ID', 56 | // clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 57 | // callbackURL: 'https://localhost:443/auth/linkedin/callback' 58 | // }, 59 | // github: { 60 | // clientID: process.env.GITHUB_ID || 'APP_ID', 61 | // clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', 62 | // callbackURL: 'https://localhost:443/auth/github/callback' 63 | // }, 64 | mailer: { 65 | from: process.env.MAILER_FROM || 'MAILER_FROM', 66 | options: { 67 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 68 | auth: { 69 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 70 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 71 | } 72 | } 73 | } 74 | }; -------------------------------------------------------------------------------- /config/env/all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | app: { 5 | title: 'OnTapp', 6 | description: 'Hack Reactor Thesis Project', 7 | keywords: 'mongodb, express, angularjs, node.js, mongoose, passport' 8 | }, 9 | port: process.env.PORT || 3000, 10 | templateEngine: 'swig', 11 | // The secret should be set to a non-guessable string that 12 | // is used to compute a session hash 13 | sessionSecret: 'MEAN', 14 | // The name of the MongoDB collection to store sessions in 15 | sessionCollection: 'sessions', 16 | // The session cookie settings 17 | sessionCookie: { 18 | path: '/', 19 | httpOnly: true, 20 | // If secure is set to true then it will cause the cookie to be set 21 | // only when SSL-enabled (HTTPS) is used, and otherwise it won't 22 | // set a cookie. 'true' is recommended yet it requires the above 23 | // mentioned pre-requisite. 24 | secure: false, 25 | // Only set the maxAge to null if the cookie shouldn't be expired 26 | // at all. The cookie will expunge when the browser is closed. 27 | maxAge: null, 28 | // To set the cookie in a specific domain uncomment the following 29 | // setting: 30 | // domain: 'yourdomain.com' 31 | }, 32 | // The session cookie name 33 | sessionName: 'connect.sid', 34 | log: { 35 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 36 | format: 'combined', 37 | // Stream defaults to process.stdout 38 | // Uncomment to enable logging to a log on the file system 39 | options: { 40 | stream: 'access.log' 41 | } 42 | }, 43 | assets: { 44 | lib: { 45 | css: [ 46 | 'public/lib/bootstrap/dist/css/bootstrap.css', 47 | 'public/lib/fontawesome/css/font-awesome.css', 48 | 'public/lib/flat-ui/dist/css/flat-ui.css' 49 | ], 50 | js: [ 51 | 'public/lib/angular/angular.js', 52 | 'public/lib/angular-resource/angular-resource.js', 53 | 'public/lib/angular-animate/angular-animate.js', 54 | 'public/lib/angular-ui-router/release/angular-ui-router.js', 55 | 'public/lib/angular-ui-utils/ui-utils.js', 56 | 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js', 57 | 'public/lib/lodash/dist/lodash.js', 58 | 'public/lib/angular-google-maps/dist/angular-google-maps.js', 59 | 'public/lib/angularjs-geolocation/src/geolocation.js', 60 | 'public/lib/spin.js/spin.js', 61 | 'public/lib/angular-spinner/angular-spinner.js', 62 | 'public/lib/fastclick/lib/fastclick.js' 63 | ] 64 | }, 65 | css: [ 66 | 'public/modules/**/css/*.css' 67 | ], 68 | js: [ 69 | 'public/config.js', 70 | 'public/application.js', 71 | 'public/modules/*/*.js', 72 | 'public/modules/*/*[!tests]*/*.js' 73 | ], 74 | tests: [ 75 | 'public/lib/angular-mocks/angular-mocks.js', 76 | 'public/modules/*/tests/*.js' 77 | ] 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /app/controllers/ratings.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | errorHandler = require('./errors.server.controller'), 8 | Rating = mongoose.model('Rating'), 9 | _ = require('lodash'); 10 | 11 | /** 12 | * Create a Rating 13 | */ 14 | exports.create = function(req, res) { 15 | var rating = new Rating(req.body); 16 | rating.user = req.user; 17 | 18 | Rating.findByBeerId(rating.beerId, function (err, beer) { 19 | if (beer.length){ 20 | rating = beer[0]; 21 | rating.stars += beer[0].stars; 22 | } 23 | 24 | rating.save(function(err) { 25 | if (err) { 26 | return res.status(400).send({ 27 | message: errorHandler.getErrorMessage(err) 28 | }); 29 | } else { 30 | res.jsonp(rating); 31 | } 32 | }); 33 | 34 | }); 35 | }; 36 | 37 | /** 38 | * Show the current Rating 39 | */ 40 | exports.read = function(req, res) { 41 | res.jsonp(req.rating); 42 | }; 43 | 44 | /** 45 | * Update a Rating 46 | */ 47 | exports.update = function(req, res) { 48 | var rating = req.rating ; 49 | 50 | rating = _.extend(rating , req.body); 51 | 52 | rating.save(function(err) { 53 | if (err) { 54 | return res.status(400).send({ 55 | message: errorHandler.getErrorMessage(err) 56 | }); 57 | } else { 58 | res.jsonp(rating); 59 | } 60 | }); 61 | }; 62 | 63 | /** 64 | * Delete an Rating 65 | */ 66 | exports.delete = function(req, res) { 67 | var rating = req.rating ; 68 | 69 | rating.remove(function(err) { 70 | if (err) { 71 | return res.status(400).send({ 72 | message: errorHandler.getErrorMessage(err) 73 | }); 74 | } else { 75 | res.jsonp(rating); 76 | } 77 | }); 78 | }; 79 | 80 | /** 81 | * List of Ratings 82 | */ 83 | exports.list = function(req, res) { 84 | Rating.find().sort('-created').populate('user', 'displayName').exec(function(err, ratings) { 85 | if (err) { 86 | return res.status(400).send({ 87 | message: errorHandler.getErrorMessage(err) 88 | }); 89 | } else { 90 | res.jsonp(ratings); 91 | } 92 | }); 93 | }; 94 | 95 | /** 96 | * Rating middleware 97 | */ 98 | exports.ratingByID = function(req, res, next, id) { 99 | Rating.findById(id).populate('user', 'displayName').exec(function(err, rating) { 100 | if (err) return next(err); 101 | if (! rating) return next(new Error('Failed to load Rating ' + id)); 102 | req.rating = rating ; 103 | next(); 104 | }); 105 | }; 106 | 107 | /** 108 | * Rating authorization middleware 109 | */ 110 | exports.hasAuthorization = function(req, res, next) { 111 | if (req.rating.user.id !== req.user.id) { 112 | return res.status(403).send('User is not authorized'); 113 | } 114 | next(); 115 | }; 116 | -------------------------------------------------------------------------------- /public/modules/nearby/views/nearby.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 |
{{title}}
13 |
14 |
15 | 16 | 17 | 18 | 19 |
{{title}}
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | {{brewery.brewery.name}} {{brewery.distance}} miles away 30 |
31 |
32 |
33 |
34 |

{{brewery.brewery.description}}

35 |

No description

36 |
37 |
38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |

Established: {{brewery.brewery.established}}

46 |
47 | 50 |
51 |

Certified Organic

52 |
53 |

List their beers »

54 |
55 |
56 |
57 |
58 |
59 |
60 | 61 |
62 | -------------------------------------------------------------------------------- /app/views/layout.server.view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{title}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for cssFile in cssFiles %}{% endfor %} 39 | 40 | 41 | 44 | 45 | 52 | 53 | 54 | 55 | 56 |
57 | {% block content %}{% endblock %} 58 |
59 | 60 | 61 | 64 | 65 | 66 | {% for jsFile in jsFiles %}{% endfor %} 67 | 68 | {% if process.env.NODE_ENV === 'development' %} 69 | 70 | 71 | {% endif %} 72 | 73 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "on-tapp", 3 | "description": "HRR2 Thesis Project", 4 | "version": "0.0.2", 5 | "private": false, 6 | "author": "Julie, Patrick, Victor", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/green-brass-doberman/on-tapp.git" 10 | }, 11 | "keywords": [ 12 | "beer", 13 | "recommendations" 14 | ], 15 | "engines": { 16 | "node": "0.10.x", 17 | "npm": "1.4.x" 18 | }, 19 | "scripts": { 20 | "start": "grunt", 21 | "test": "grunt test", 22 | "postinstall": "bower install --config.interactive=false" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/green-brass-doberman/on-tapp/issues" 26 | }, 27 | "homepage": "https://github.com/green-brass-doberman/on-tapp", 28 | "dependencies": { 29 | "async": "~0.9.0", 30 | "body-parser": "^1.9.3", 31 | "bower": "~1.3.8", 32 | "chalk": "~0.5", 33 | "compression": "~1.2.0", 34 | "connect-flash": "~0.1.1", 35 | "connect-mongo": "^0.4.2", 36 | "consolidate": "~0.10.0", 37 | "cookie-parser": "~1.3.2", 38 | "express": "^4.10.4", 39 | "express-session": "~1.9.1", 40 | "forever": "^0.11.1", 41 | "glob": "~4.0.6", 42 | "grunt-cli": "~0.1.13", 43 | "helmet": "~0.5.0", 44 | "lodash": "~2.4.1", 45 | "mean-seo": "0.0.8", 46 | "method-override": "~2.3.0", 47 | "mongoose": "~3.8.8", 48 | "morgan": "^1.4.1", 49 | "newrelic": "^1.14.5", 50 | "nodemailer": "~1.3.0", 51 | "passport": "~0.2.0", 52 | "passport-facebook": "~1.0.2", 53 | "passport-github": "~0.1.5", 54 | "passport-google-oauth": "~0.1.5", 55 | "passport-linkedin": "~0.1.3", 56 | "passport-local": "~1.0.0", 57 | "passport-twitter": "~1.0.2", 58 | "request": "^2.51.0", 59 | "swig": "~1.4.1" 60 | }, 61 | "devDependencies": { 62 | "chai": "^1.10.0", 63 | "grunt": "^0.4.5", 64 | "grunt-casperjs": "^2.1.0", 65 | "grunt-cli": "^0.1.13", 66 | "grunt-concurrent": "~1.0.0", 67 | "grunt-contrib-clean": "^0.6.0", 68 | "grunt-contrib-concat": "^0.5.0", 69 | "grunt-contrib-copy": "^0.7.0", 70 | "grunt-contrib-csslint": "^0.3.1", 71 | "grunt-contrib-cssmin": "~0.10.0", 72 | "grunt-contrib-jshint": "~0.10.0", 73 | "grunt-contrib-less": "^1.0.0", 74 | "grunt-contrib-uglify": "~0.6.0", 75 | "grunt-contrib-watch": "~0.6.1", 76 | "grunt-env": "~0.4.1", 77 | "grunt-express-server": "^0.4.19", 78 | "grunt-karma": "~0.9.0", 79 | "grunt-mocha": "^0.4.11", 80 | "grunt-mocha-test": "~0.12.1", 81 | "grunt-ng-annotate": "~0.4.0", 82 | "grunt-node-inspector": "~0.1.3", 83 | "grunt-nodemon": "~0.3.0", 84 | "jshint-stylish": "^1.0.0", 85 | "karma": "~0.12.0", 86 | "karma-chai": "^0.1.0", 87 | "karma-chrome-launcher": "~0.1.2", 88 | "karma-coverage": "~0.2.0", 89 | "karma-firefox-launcher": "~0.1.3", 90 | "karma-jasmine": "~0.2.1", 91 | "karma-jasmine-matchers": "^0.1.3", 92 | "karma-mocha": "^0.1.10", 93 | "karma-phantomjs-launcher": "~0.1.2", 94 | "karma-sinon": "^1.0.4", 95 | "load-grunt-tasks": "~1.0.0", 96 | "mocha": "^2.0.1", 97 | "should": "~4.1.0", 98 | "sinon": "^1.12.2", 99 | "supertest": "~0.14.0" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /public/modules/beers/controllers/beers.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('beers').controller('BeersController', ['$scope', 'Beers', '$stateParams', 'Ratings', '$location', 4 | function($scope, Beers, $stateParams, Ratings, $location) { 5 | 6 | // sort the given collection on the given property 7 | function sortOn(collection, name) { 8 | collection.sort( 9 | function(a, b) { 10 | if (a[name] <= b[name]) { 11 | return(-1); 12 | } 13 | return(1); 14 | } 15 | ); 16 | } 17 | 18 | // group the beers list on the given property 19 | $scope.groupBy = function(attribute) { 20 | // First, reset the groups. 21 | $scope.groups = []; 22 | 23 | // Now, sort the collection of beers on the grouping-property. 24 | // This just makes it easier to split the collection. 25 | sortOn($scope.beers, attribute); 26 | 27 | // I determine which group we are currently in. 28 | var groupValue = '_INVALID_GROUP_VALUE_'; 29 | 30 | // As we loop over each beer, add it to the current group - 31 | // we'll create a NEW group every time we come across a new attribute value. 32 | for (var i = 0; i < $scope.beers.length; i++) { 33 | var beer = $scope.beers[i]; 34 | 35 | var group; 36 | 37 | // Should we create a new group? 38 | if (beer[attribute] !== groupValue) { 39 | group = { 40 | label: beer[attribute], 41 | beers: [] 42 | }; 43 | groupValue = group.label; 44 | $scope.groups.push(group); 45 | } 46 | 47 | // Add the friend to the currently active grouping. 48 | group.beers.push(beer); 49 | } 50 | }; 51 | 52 | $scope.beers = []; 53 | $scope.groups = []; 54 | $scope.breweryId = $stateParams.breweryId; 55 | 56 | var handleSuccess = function(data, status){ 57 | $scope.beers = data.data; 58 | $scope.groupBy('availableId'); 59 | }; 60 | 61 | // send the brewery id 62 | Beers.getData($scope.breweryId).success(handleSuccess); 63 | 64 | // handle the stars rating 65 | $scope.rate = 0; 66 | $scope.max = 5; 67 | $scope.isReadonly = false; 68 | 69 | // hoveing over on ratings stars 70 | $scope.hoveringOver = function(value) { 71 | $scope.overStar = value; 72 | $scope.percent = 100 * (value / $scope.max); 73 | }; 74 | 75 | // set the ratings stars 76 | $scope.ratingStates = [ 77 | {stateOn: 'glyphicon-star', stateOff: 'glyphicon-star-empty'}, 78 | ]; 79 | 80 | // Create new Rating 81 | $scope.create = function(index) { 82 | 83 | // Create new Rating object 84 | var rating = new Ratings ({ 85 | beerId: $scope.beers[index].id, 86 | name: $scope.beers[index].name, 87 | stars: this.rate, 88 | styleName: $scope.beers[index].style.name 89 | }); 90 | 91 | // Redirect after save 92 | rating.$save(function(response) { 93 | $location.path('ratings/' + response._id); 94 | 95 | // Clear form fields 96 | $scope.name = ''; 97 | }, function(errorResponse) { 98 | $scope.error = errorResponse.data.message; 99 | }); 100 | }; 101 | } 102 | ]); 103 | -------------------------------------------------------------------------------- /public/modules/core/views/home.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
9 |

Catalog your beer tastebuds and share recommendations with like tastebudded people.

10 |
11 |
12 |

For all lovers of frothy goodness to expand the horizons of their enthusiasm.

13 |

14 | Discover Breweries Nearby » 15 |

16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 |
24 | Goal 25 |

Summary

26 |

Mobile accessible web application to allow beer lovers to share and receive recommendations on their favorite form of frothy, liquid refreshment. Will also seek to provide the most immediate outlet for their desired libation.

27 |
28 |
29 | Search 30 |

Problem

31 |

People like beer. People would like to know where to readily access the beers they like. In addition, they would like to discover new and interesting beers, based not on beer categories, but based on their personal taste preferences. OnTapp's goal, is to solve those problems.

32 |
33 |
34 | Ratings 35 |

Solution

36 |

Create a user-friendly platform for sharing information about beer tastes and accessibility.

37 |
38 |
39 |
40 |
41 | 42 |
43 | 44 |
45 |
46 |

"Coming from America's 'Craft Beer Capital', we have been lacking an app designed for the beer enthusists, by beer enthusists. This app fills that gap!"

47 |
48 |
49 |
50 | Ratings 51 |
52 | 53 |
54 | 55 |
56 | Ratings 57 |
58 |
59 |
60 |

"This is what I would want out of a beer app. Untapped so far hasn't done that for me."

61 |
62 |
63 | 64 |
65 |
66 | 67 |
68 |

Powered by

69 | 70 | 71 | 72 | 73 | 74 |
75 |
76 |
77 | -------------------------------------------------------------------------------- /public/modules/core/views/header.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 11 | 12 | 74 |
75 | -------------------------------------------------------------------------------- /app/models/user.server.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | crypto = require('crypto'); 9 | 10 | /** 11 | * A Validation function for local strategy properties 12 | */ 13 | var validateLocalStrategyProperty = function(property) { 14 | return ((this.provider !== 'local' && !this.updated) || property.length); 15 | }; 16 | 17 | /** 18 | * A Validation function for local strategy password 19 | */ 20 | var validateLocalStrategyPassword = function(password) { 21 | return (this.provider !== 'local' || (password && password.length > 6)); 22 | }; 23 | 24 | /** 25 | * User Schema 26 | */ 27 | var UserSchema = new Schema({ 28 | firstName: { 29 | type: String, 30 | trim: true, 31 | default: '', 32 | validate: [validateLocalStrategyProperty, 'Please fill in your first name'] 33 | }, 34 | lastName: { 35 | type: String, 36 | trim: true, 37 | default: '', 38 | validate: [validateLocalStrategyProperty, 'Please fill in your last name'] 39 | }, 40 | displayName: { 41 | type: String, 42 | trim: true 43 | }, 44 | email: { 45 | type: String, 46 | trim: true, 47 | default: '', 48 | validate: [validateLocalStrategyProperty, 'Please fill in your email'], 49 | match: [/.+\@.+\..+/, 'Please fill a valid email address'] 50 | }, 51 | username: { 52 | type: String, 53 | unique: 'Username already exists', 54 | required: 'Please fill in a username', 55 | trim: true 56 | }, 57 | password: { 58 | type: String, 59 | default: '', 60 | validate: [validateLocalStrategyPassword, 'Password should be longer'] 61 | }, 62 | salt: { 63 | type: String 64 | }, 65 | provider: { 66 | type: String, 67 | required: 'Provider is required' 68 | }, 69 | providerData: {}, 70 | additionalProvidersData: {}, 71 | roles: { 72 | type: [{ 73 | type: String, 74 | enum: ['user', 'admin'] 75 | }], 76 | default: ['user'] 77 | }, 78 | updated: { 79 | type: Date 80 | }, 81 | created: { 82 | type: Date, 83 | default: Date.now 84 | }, 85 | /* For reset password */ 86 | resetPasswordToken: { 87 | type: String 88 | }, 89 | resetPasswordExpires: { 90 | type: Date 91 | } 92 | }); 93 | 94 | /** 95 | * Hook a pre save method to hash the password 96 | */ 97 | UserSchema.pre('save', function(next) { 98 | if (this.password && this.password.length > 6) { 99 | this.salt = new Buffer(crypto.randomBytes(16).toString('base64'), 'base64'); 100 | this.password = this.hashPassword(this.password); 101 | } 102 | 103 | next(); 104 | }); 105 | 106 | /** 107 | * Create instance method for hashing a password 108 | */ 109 | UserSchema.methods.hashPassword = function(password) { 110 | if (this.salt && password) { 111 | return crypto.pbkdf2Sync(password, this.salt, 10000, 64).toString('base64'); 112 | } else { 113 | return password; 114 | } 115 | }; 116 | 117 | /** 118 | * Create instance method for authenticating user 119 | */ 120 | UserSchema.methods.authenticate = function(password) { 121 | return this.password === this.hashPassword(password); 122 | }; 123 | 124 | /** 125 | * Find possible not used username 126 | */ 127 | UserSchema.statics.findUniqueUsername = function(username, suffix, callback) { 128 | var _this = this; 129 | var possibleUsername = username + (suffix || ''); 130 | 131 | _this.findOne({ 132 | username: possibleUsername 133 | }, function(err, user) { 134 | if (!err) { 135 | if (!user) { 136 | callback(possibleUsername); 137 | } else { 138 | return _this.findUniqueUsername(username, (suffix || 0) + 1, callback); 139 | } 140 | } else { 141 | callback(null); 142 | } 143 | }); 144 | }; 145 | 146 | mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /public/modules/users/tests/authentication.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | // Authentication controller Spec 5 | describe('AuthenticationController', function() { 6 | // Initialize global variables 7 | var AuthenticationController, 8 | scope, 9 | $httpBackend, 10 | $stateParams, 11 | $location; 12 | 13 | beforeEach(function() { 14 | jasmine.addMatchers({ 15 | toEqualData: function(util, customEqualityTesters) { 16 | return { 17 | compare: function(actual, expected) { 18 | return { 19 | pass: angular.equals(actual, expected) 20 | }; 21 | } 22 | }; 23 | } 24 | }); 25 | }); 26 | 27 | // Load the main application module 28 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 29 | 30 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 31 | // This allows us to inject a service but then attach it to a variable 32 | // with the same name as the service. 33 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { 34 | // Set a new global scope 35 | scope = $rootScope.$new(); 36 | 37 | // Point global variables to injected services 38 | $stateParams = _$stateParams_; 39 | $httpBackend = _$httpBackend_; 40 | $location = _$location_; 41 | 42 | // Initialize the Authentication controller 43 | AuthenticationController = $controller('AuthenticationController', { 44 | $scope: scope 45 | }); 46 | })); 47 | 48 | 49 | it('$scope.signin() should login with a correct user and password', function() { 50 | // Test expected GET request 51 | $httpBackend.when('POST', '/auth/signin').respond(200, 'Fred'); 52 | 53 | scope.signin(); 54 | $httpBackend.flush(); 55 | 56 | // Test scope value 57 | expect(scope.authentication.user).toEqual('Fred'); 58 | expect($location.url()).toEqual('/'); 59 | }); 60 | 61 | it('$scope.signin() should fail to log in with nothing', function() { 62 | // Test expected POST request 63 | $httpBackend.expectPOST('/auth/signin').respond(400, { 64 | 'message': 'Missing credentials' 65 | }); 66 | 67 | scope.signin(); 68 | $httpBackend.flush(); 69 | 70 | // Test scope value 71 | expect(scope.error).toEqual('Missing credentials'); 72 | }); 73 | 74 | it('$scope.signin() should fail to log in with wrong credentials', function() { 75 | // Foo/Bar combo assumed to not exist 76 | scope.authentication.user = 'Foo'; 77 | scope.credentials = 'Bar'; 78 | 79 | // Test expected POST request 80 | $httpBackend.expectPOST('/auth/signin').respond(400, { 81 | 'message': 'Unknown user' 82 | }); 83 | 84 | scope.signin(); 85 | $httpBackend.flush(); 86 | 87 | // Test scope value 88 | expect(scope.error).toEqual('Unknown user'); 89 | }); 90 | 91 | it('$scope.signup() should register with correct data', function() { 92 | // Test expected GET request 93 | scope.authentication.user = 'Fred'; 94 | $httpBackend.when('POST', '/auth/signup').respond(200, 'Fred'); 95 | 96 | scope.signup(); 97 | $httpBackend.flush(); 98 | 99 | // test scope value 100 | expect(scope.authentication.user).toBe('Fred'); 101 | expect(scope.error).toEqual(undefined); 102 | expect($location.url()).toBe('/'); 103 | }); 104 | 105 | it('$scope.signup() should fail to register with duplicate Username', function() { 106 | // Test expected POST request 107 | $httpBackend.when('POST', '/auth/signup').respond(400, { 108 | 'message': 'Username already exists' 109 | }); 110 | 111 | scope.signup(); 112 | $httpBackend.flush(); 113 | 114 | // Test scope value 115 | expect(scope.error).toBe('Username already exists'); 116 | }); 117 | }); 118 | }()); -------------------------------------------------------------------------------- /public/modules/ratings/tests/ratings.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | // Ratings Controller Spec 5 | describe('Ratings Controller Tests', function() { 6 | // Initialize global variables 7 | var RatingsController, 8 | scope, 9 | $httpBackend, 10 | $stateParams, 11 | $location; 12 | 13 | // The $resource service augments the response object with methods for updating and deleting the resource. 14 | // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match 15 | // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. 16 | // When the toEqualData matcher compares two objects, it takes only object properties into 17 | // account and ignores methods. 18 | beforeEach(function() { 19 | jasmine.addMatchers({ 20 | toEqualData: function(util, customEqualityTesters) { 21 | return { 22 | compare: function(actual, expected) { 23 | return { 24 | pass: angular.equals(actual, expected) 25 | }; 26 | } 27 | }; 28 | } 29 | }); 30 | }); 31 | 32 | // Then we can start by loading the main application module 33 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 34 | 35 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 36 | // This allows us to inject a service but then attach it to a variable 37 | // with the same name as the service. 38 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) { 39 | // Set a new global scope 40 | scope = $rootScope.$new(); 41 | 42 | // Point global variables to injected services 43 | $stateParams = _$stateParams_; 44 | $httpBackend = _$httpBackend_; 45 | $location = _$location_; 46 | 47 | // Initialize the Ratings controller. 48 | RatingsController = $controller('RatingsController', { 49 | $scope: scope 50 | }); 51 | })); 52 | 53 | it('$scope.find() should create an array with at least one Rating object fetched from XHR', inject(function(Ratings) { 54 | // Create sample Rating using the Ratings service 55 | var sampleRating = new Ratings({ 56 | name: 'New Rating' 57 | }); 58 | 59 | // Create a sample Ratings array that includes the new Rating 60 | var sampleRatings = [sampleRating]; 61 | 62 | // Set GET response 63 | $httpBackend.expectGET('ratings').respond(sampleRatings); 64 | 65 | // Run controller functionality 66 | scope.find(); 67 | $httpBackend.flush(); 68 | 69 | // Test scope value 70 | expect(scope.ratings).toEqualData(sampleRatings); 71 | })); 72 | 73 | it('$scope.update() should update a valid Rating', inject(function(Ratings) { 74 | // Define a sample Rating put data 75 | var sampleRatingPutData = new Ratings({ 76 | _id: '525cf20451979dea2c000001', 77 | name: 'New Rating' 78 | }); 79 | 80 | // Mock Rating in scope 81 | scope.rating = sampleRatingPutData; 82 | 83 | // Set PUT response 84 | $httpBackend.expectPUT(/ratings\/([0-9a-fA-F]{24})$/).respond(); 85 | 86 | // Run controller functionality 87 | scope.update(); 88 | $httpBackend.flush(); 89 | 90 | // Test URL location to new object 91 | expect($location.path()).toBe('/ratings/' + sampleRatingPutData._id); 92 | })); 93 | 94 | it('$scope.remove() should send a DELETE request with a valid ratingId and remove the Rating from the scope', inject(function(Ratings) { 95 | // Create new Rating object 96 | var sampleRating = new Ratings({ 97 | _id: '525a8422f6d0f87f0e407a33' 98 | }); 99 | 100 | // Create new Ratings array and include the Rating 101 | scope.ratings = [sampleRating]; 102 | 103 | // Set expected DELETE response 104 | $httpBackend.expectDELETE(/ratings\/([0-9a-fA-F]{24})$/).respond(204); 105 | 106 | // Run controller functionality 107 | scope.remove(sampleRating); 108 | $httpBackend.flush(); 109 | 110 | // Test array after successful delete 111 | expect(scope.ratings.length).toBe(0); 112 | })); 113 | }); 114 | }()); 115 | -------------------------------------------------------------------------------- /public/modules/nearby/views/brewery.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 |
8 | 9 |
10 |
11 |

Established: {{brewery.established}}

12 |
13 |
14 |

{{brewery.website}}

15 |
16 |
17 |

Certified Organic

18 |
19 |
20 |

Members of:

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 |
52 |
53 |
54 |
55 |

{{brewery.description}}

56 |

Locations:

57 |
    58 |
  • 59 | {{location.name}}
    60 | {{location.locationTypeDisplay}}
    61 | {{location.streetAddress}}
    62 | {{location.region}} {{location.postalCode}}
    63 | {{location.phone}}
    64 |
    Year Opened: {{location.yearOpened}}
    65 |
  • 66 |
67 |
68 |
69 |
-------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | // Unified Watch Object 5 | var watchFiles = { 6 | serverViews: ['app/views/**/*.*'], 7 | serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js'], 8 | clientViews: ['public/modules/**/views/**/*.html'], 9 | clientJS: ['public/js/*.js', 'public/modules/**/*.js'], 10 | clientCSS: ['public/modules/**/*.css'], 11 | mochaTests: ['app/tests/**/*.js'] 12 | }; 13 | 14 | // Project Configuration 15 | grunt.initConfig({ 16 | pkg: grunt.file.readJSON('package.json'), 17 | watch: { 18 | serverViews: { 19 | files: watchFiles.serverViews, 20 | options: { 21 | livereload: true 22 | } 23 | }, 24 | serverJS: { 25 | files: watchFiles.serverJS, 26 | tasks: ['jshint'], 27 | options: { 28 | livereload: true 29 | } 30 | }, 31 | clientViews: { 32 | files: watchFiles.clientViews, 33 | options: { 34 | livereload: true 35 | } 36 | }, 37 | clientJS: { 38 | files: watchFiles.clientJS, 39 | tasks: ['jshint'], 40 | options: { 41 | livereload: true 42 | } 43 | }, 44 | clientCSS: { 45 | files: watchFiles.clientCSS, 46 | tasks: ['csslint'], 47 | options: { 48 | livereload: true 49 | } 50 | } 51 | }, 52 | jshint: { 53 | all: { 54 | src: watchFiles.clientJS.concat(watchFiles.serverJS), 55 | options: { 56 | jshintrc: true 57 | } 58 | } 59 | }, 60 | csslint: { 61 | options: { 62 | csslintrc: '.csslintrc' 63 | }, 64 | all: { 65 | src: watchFiles.clientCSS 66 | } 67 | }, 68 | uglify: { 69 | production: { 70 | options: { 71 | mangle: false 72 | }, 73 | files: { 74 | 'public/dist/application.min.js': 'public/dist/application.js' 75 | } 76 | } 77 | }, 78 | cssmin: { 79 | combine: { 80 | files: { 81 | 'public/dist/application.min.css': '<%= applicationCSSFiles %>' 82 | } 83 | } 84 | }, 85 | nodemon: { 86 | dev: { 87 | script: 'server.js', 88 | options: { 89 | nodeArgs: ['--debug'], 90 | ext: 'js,html', 91 | watch: watchFiles.serverViews.concat(watchFiles.serverJS) 92 | } 93 | } 94 | }, 95 | 'node-inspector': { 96 | custom: { 97 | options: { 98 | 'web-port': 1337, 99 | 'web-host': 'localhost', 100 | 'debug-port': 5858, 101 | 'save-live-edit': true, 102 | 'no-preload': true, 103 | 'stack-trace-limit': 50, 104 | 'hidden': [] 105 | } 106 | } 107 | }, 108 | ngAnnotate: { 109 | production: { 110 | files: { 111 | 'public/dist/application.js': '<%= applicationJavaScriptFiles %>' 112 | } 113 | } 114 | }, 115 | concurrent: { 116 | default: ['nodemon', 'watch'], 117 | debug: ['nodemon', 'watch', 'node-inspector'], 118 | options: { 119 | logConcurrentOutput: true, 120 | limit: 10 121 | } 122 | }, 123 | env: { 124 | test: { 125 | NODE_ENV: 'test' 126 | }, 127 | secure: { 128 | NODE_ENV: 'secure' 129 | } 130 | }, 131 | mochaTest: { 132 | src: watchFiles.mochaTests, 133 | options: { 134 | reporter: 'spec', 135 | require: 'server.js' 136 | } 137 | }, 138 | karma: { 139 | unit: { 140 | configFile: 'karma.conf.js' 141 | } 142 | } 143 | }); 144 | 145 | // Load NPM tasks 146 | require('load-grunt-tasks')(grunt); 147 | 148 | // Making grunt default to force in order not to break the project. 149 | grunt.option('force', true); 150 | 151 | // A Task for loading the configuration object 152 | grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() { 153 | var init = require('./config/init')(); 154 | var config = require('./config/config'); 155 | 156 | grunt.config.set('applicationJavaScriptFiles', config.assets.js); 157 | grunt.config.set('applicationCSSFiles', config.assets.css); 158 | }); 159 | 160 | // Default task(s). 161 | grunt.registerTask('default', ['lint', 'concurrent:default']); 162 | 163 | // Debug task. 164 | grunt.registerTask('debug', ['lint', 'concurrent:debug']); 165 | 166 | // Secure task(s). 167 | grunt.registerTask('secure', ['env:secure', 'lint', 'concurrent:default']); 168 | 169 | // Lint task(s). 170 | grunt.registerTask('lint', ['jshint', 'csslint']); 171 | 172 | // Build task(s). 173 | grunt.registerTask('build', ['lint', 'loadConfig', 'ngAnnotate', 'uglify', 'cssmin']); 174 | 175 | // Test task. 176 | grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']); 177 | }; -------------------------------------------------------------------------------- /config/express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var fs = require('fs'), 7 | http = require('http'), 8 | https = require('https'), 9 | express = require('express'), 10 | morgan = require('morgan'), 11 | logger = require('./logger'), 12 | bodyParser = require('body-parser'), 13 | session = require('express-session'), 14 | compress = require('compression'), 15 | methodOverride = require('method-override'), 16 | cookieParser = require('cookie-parser'), 17 | helmet = require('helmet'), 18 | passport = require('passport'), 19 | mongoStore = require('connect-mongo')({ 20 | session: session 21 | }), 22 | flash = require('connect-flash'), 23 | config = require('./config'), 24 | consolidate = require('consolidate'), 25 | path = require('path'); 26 | 27 | module.exports = function(db) { 28 | // Initialize express app 29 | var app = express(); 30 | 31 | // Globbing model files 32 | config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) { 33 | require(path.resolve(modelPath)); 34 | }); 35 | 36 | // Setting application local variables 37 | app.locals.title = config.app.title; 38 | app.locals.description = config.app.description; 39 | app.locals.keywords = config.app.keywords; 40 | app.locals.facebookAppId = config.facebook.clientID; 41 | app.locals.jsFiles = config.getJavaScriptAssets(); 42 | app.locals.cssFiles = config.getCSSAssets(); 43 | 44 | // Passing the request url to environment locals 45 | app.use(function(req, res, next) { 46 | res.locals.url = req.protocol + '://' + req.headers.host + req.url; 47 | next(); 48 | }); 49 | 50 | // Should be placed before express.static 51 | app.use(compress({ 52 | filter: function(req, res) { 53 | return (/json|text|javascript|css/).test(res.getHeader('Content-Type')); 54 | }, 55 | level: 9 56 | })); 57 | 58 | // Showing stack errors 59 | app.set('showStackError', true); 60 | 61 | // Set swig as the template engine 62 | app.engine('server.view.html', consolidate[config.templateEngine]); 63 | 64 | // Set views path and view engine 65 | app.set('view engine', 'server.view.html'); 66 | app.set('views', './app/views'); 67 | 68 | // Enable logger (morgan) 69 | app.use(morgan(logger.getLogFormat(), logger.getLogOptions())); 70 | 71 | // Environment dependent middleware 72 | if (process.env.NODE_ENV === 'development') { 73 | // Disable views cache 74 | app.set('view cache', false); 75 | } else if (process.env.NODE_ENV === 'production') { 76 | app.locals.cache = 'memory'; 77 | } 78 | 79 | // Request body parsing middleware should be above methodOverride 80 | app.use(bodyParser.urlencoded({ 81 | extended: true 82 | })); 83 | app.use(bodyParser.json()); 84 | app.use(methodOverride()); 85 | 86 | // CookieParser should be above session 87 | app.use(cookieParser()); 88 | 89 | // Express MongoDB session storage 90 | app.use(session({ 91 | saveUninitialized: true, 92 | resave: true, 93 | secret: config.sessionSecret, 94 | store: new mongoStore({ 95 | db: db.connection.db, 96 | collection: config.sessionCollection 97 | }), 98 | cookie: config.sessionCookie, 99 | name: config.sessionName 100 | })); 101 | 102 | // use passport session 103 | app.use(passport.initialize()); 104 | app.use(passport.session()); 105 | 106 | // connect flash for flash messages 107 | app.use(flash()); 108 | 109 | // Use helmet to secure Express headers 110 | app.use(helmet.xframe()); 111 | app.use(helmet.xssFilter()); 112 | app.use(helmet.nosniff()); 113 | app.use(helmet.ienoopen()); 114 | app.disable('x-powered-by'); 115 | 116 | // Setting the app router and static folder 117 | app.use(express.static(path.resolve('./public'))); 118 | 119 | // Globbing routing files 120 | config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) { 121 | require(path.resolve(routePath))(app); 122 | }); 123 | 124 | // Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc. 125 | app.use(function(err, req, res, next) { 126 | // If the error object doesn't exists 127 | if (!err) return next(); 128 | 129 | // Log it 130 | console.error(err.stack); 131 | 132 | // Error page 133 | res.status(500).render('500', { 134 | error: err.stack 135 | }); 136 | }); 137 | 138 | // Assume 404 since no middleware responded 139 | app.use(function(req, res) { 140 | res.status(404).render('404', { 141 | url: req.originalUrl, 142 | error: 'Not Found' 143 | }); 144 | }); 145 | 146 | if (process.env.NODE_ENV === 'secure') { 147 | // Load SSL key and certificate 148 | var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8'); 149 | var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8'); 150 | 151 | // Create HTTPS Server 152 | var httpsServer = https.createServer({ 153 | key: privateKey, 154 | cert: certificate 155 | }, app); 156 | 157 | // Return HTTPS server instance 158 | return httpsServer; 159 | } 160 | 161 | // Return Express server instance 162 | return app; 163 | }; -------------------------------------------------------------------------------- /public/modules/nearby/controllers/nearby.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('nearby').controller('NearbyController', ['$scope', 'Breweries', 'geolocation', '$stateParams', 'uiGmapLogger', '$anchorScroll', '$location', 'usSpinnerService', 4 | function($scope, Breweries, geolocation, $stateParams, uiGmapLogger, $anchorScroll, $location, usSpinnerService) { 5 | 6 | // enable logging of google map info and error 7 | uiGmapLogger.doLog = true; 8 | 9 | // this array would be used to fetch data from brewerydb factory 10 | $scope.breweries = []; 11 | 12 | // an object to story user's current coordinate, 13 | $scope.coords = {}; 14 | 15 | // pushing breweries data from $http request and place markers 16 | var handleSuccess = function(data, status){ 17 | 18 | if (data.data){ 19 | 20 | $scope.breweries = data.data; 21 | 22 | placeMarker(); 23 | 24 | } else { 25 | 26 | $scope.breweries = [{ 27 | brewery: { 28 | name: 'Sorry', 29 | description: 'No breweries nearby' 30 | } 31 | }]; 32 | 33 | } 34 | 35 | // stop the spinner 36 | usSpinnerService.stop('spinner-1'); 37 | }; 38 | 39 | // function to access users geolocation coordinates 40 | geolocation.getLocation().then(function(data){ 41 | // get user coordinates 42 | $scope.coords = {lat:data.coords.latitude, long:data.coords.longitude}; 43 | 44 | // set to san francisco by Default for Victor 45 | // $scope.coords = {lat:37.7833, long:-122.4167}; 46 | 47 | // initialise the Google map 48 | $scope.map = { center: { latitude: $scope.coords.lat, longitude: $scope.coords.long }, zoom: 12}; 49 | 50 | // allow scroll to zoom 51 | $scope.windowOptions = { 52 | visible: true 53 | }; 54 | 55 | // add maker for current location 56 | curLocationMarker(); 57 | 58 | // get Breweries data from factory 59 | Breweries.getData($scope.coords).success(handleSuccess); 60 | 61 | }); 62 | 63 | // marker for current coordinate 64 | var curLocationMarker = function(){ 65 | $scope.marker = { 66 | id: 'curLoc', 67 | coords: { 68 | latitude: $scope.coords.lat, 69 | longitude: $scope.coords.long, 70 | }, 71 | options: { 72 | animation: 'DROP' 73 | } 74 | }; 75 | }; 76 | 77 | $scope.windowOptions = { 78 | visible: true 79 | }; 80 | 81 | $scope.onClick = function() { 82 | $scope.windowOptions.visible = !$scope.windowOptions.visible; 83 | }; 84 | 85 | $scope.closeClick = function() { 86 | $scope.windowOptions.visible = false; 87 | }; 88 | 89 | $scope.title = 'You are here!'; 90 | 91 | $scope.clickEventsObject = { 92 | mouseover: function(marker, e, model) { 93 | model.mouseOver(); 94 | }, 95 | mouseout: function(marker, e, model) { 96 | model.mouseOut(); 97 | } 98 | }; 99 | 100 | // an array to store all breweries marker 101 | $scope.allMarkers = []; 102 | 103 | // create markers for all breweries 104 | var createMarker = function (i, lat, lng, name, breweryId) { 105 | var ret = { 106 | id: i, 107 | breweryId: breweryId, 108 | latitude: lat, 109 | longitude: lng, 110 | title: name, 111 | icon: '/modules/nearby/images/beer-icon.png', 112 | show: false 113 | }; 114 | ret.onClick = function() { 115 | ret.show = !ret.show; 116 | }; 117 | ret.mouseOver = function(){ 118 | ret.show = !ret.show; 119 | $scope.gotoAnchor(breweryId); 120 | }; 121 | ret.mouseOut = function(){ 122 | ret.show = !ret.show; 123 | }; 124 | 125 | return ret; 126 | }; 127 | 128 | // a function to place all breweries markers 129 | var placeMarker = function(){ 130 | 131 | var markers = []; 132 | for (var i = 0; i < $scope.breweries.length; i++) { 133 | var lat = $scope.breweries[i].latitude; 134 | var lng = $scope.breweries[i].longitude; 135 | var name = $scope.breweries[i].brewery.name; 136 | var breweryId = $scope.breweries[i].brewery.id; 137 | 138 | markers.push(createMarker(i, lat, lng, name, breweryId)); 139 | } 140 | 141 | $scope.allMarkers = markers; 142 | }; 143 | 144 | $scope.gotoAnchor = function(x) { 145 | var newHash = 'anchor' + x; 146 | if ($location.hash() !== newHash) { 147 | // set the $location.hash to `newHash` and 148 | // $anchorScroll will automatically scroll to it 149 | $location.hash('anchor' + x); 150 | } else { 151 | // call $anchorScroll() explicitly, 152 | // since $location.hash hasn't changed 153 | $anchorScroll(); 154 | } 155 | }; 156 | } 157 | ]); 158 | 159 | angular.module('nearby').run(['$anchorScroll', function($anchorScroll) { 160 | $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 161 | }]); 162 | -------------------------------------------------------------------------------- /public/modules/core/services/menus.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Menu service used for managing menus 4 | angular.module('core').service('Menus', [ 5 | 6 | function() { 7 | // Define a set of default roles 8 | this.defaultRoles = ['*']; 9 | 10 | // Define the menus object 11 | this.menus = {}; 12 | 13 | // A private function for rendering decision 14 | var shouldRender = function(user) { 15 | if (user) { 16 | if (!!~this.roles.indexOf('*')) { 17 | return true; 18 | } else { 19 | for (var userRoleIndex in user.roles) { 20 | for (var roleIndex in this.roles) { 21 | if (this.roles[roleIndex] === user.roles[userRoleIndex]) { 22 | return true; 23 | } 24 | } 25 | } 26 | } 27 | } else { 28 | return this.isPublic; 29 | } 30 | 31 | return false; 32 | }; 33 | 34 | // Validate menu existance 35 | this.validateMenuExistance = function(menuId) { 36 | if (menuId && menuId.length) { 37 | if (this.menus[menuId]) { 38 | return true; 39 | } else { 40 | throw new Error('Menu does not exists'); 41 | } 42 | } else { 43 | throw new Error('MenuId was not provided'); 44 | } 45 | 46 | return false; 47 | }; 48 | 49 | // Get the menu object by menu id 50 | this.getMenu = function(menuId) { 51 | // Validate that the menu exists 52 | this.validateMenuExistance(menuId); 53 | 54 | // Return the menu object 55 | return this.menus[menuId]; 56 | }; 57 | 58 | // Add new menu object by menu id 59 | this.addMenu = function(menuId, isPublic, roles) { 60 | // Create the new menu 61 | this.menus[menuId] = { 62 | isPublic: isPublic || false, 63 | roles: roles || this.defaultRoles, 64 | items: [], 65 | shouldRender: shouldRender 66 | }; 67 | 68 | // Return the menu object 69 | return this.menus[menuId]; 70 | }; 71 | 72 | // Remove existing menu object by menu id 73 | this.removeMenu = function(menuId) { 74 | // Validate that the menu exists 75 | this.validateMenuExistance(menuId); 76 | 77 | // Return the menu object 78 | delete this.menus[menuId]; 79 | }; 80 | 81 | // Add menu item object 82 | this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position) { 83 | // Validate that the menu exists 84 | this.validateMenuExistance(menuId); 85 | 86 | // Push new menu item 87 | this.menus[menuId].items.push({ 88 | title: menuItemTitle, 89 | link: menuItemURL, 90 | menuItemType: menuItemType || 'item', 91 | menuItemClass: menuItemType, 92 | uiRoute: menuItemUIRoute || ('/' + menuItemURL), 93 | isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].isPublic : isPublic), 94 | roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].roles : roles), 95 | position: position || 0, 96 | items: [], 97 | shouldRender: shouldRender 98 | }); 99 | 100 | // Return the menu object 101 | return this.menus[menuId]; 102 | }; 103 | 104 | // Add submenu item object 105 | this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position) { 106 | // Validate that the menu exists 107 | this.validateMenuExistance(menuId); 108 | 109 | // Search for menu item 110 | for (var itemIndex in this.menus[menuId].items) { 111 | if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) { 112 | // Push new submenu item 113 | this.menus[menuId].items[itemIndex].items.push({ 114 | title: menuItemTitle, 115 | link: menuItemURL, 116 | uiRoute: menuItemUIRoute || ('/' + menuItemURL), 117 | isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : isPublic), 118 | roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles), 119 | position: position || 0, 120 | shouldRender: shouldRender 121 | }); 122 | } 123 | } 124 | 125 | // Return the menu object 126 | return this.menus[menuId]; 127 | }; 128 | 129 | // Remove existing menu object by menu id 130 | this.removeMenuItem = function(menuId, menuItemURL) { 131 | // Validate that the menu exists 132 | this.validateMenuExistance(menuId); 133 | 134 | // Search for menu item to remove 135 | for (var itemIndex in this.menus[menuId].items) { 136 | if (this.menus[menuId].items[itemIndex].link === menuItemURL) { 137 | this.menus[menuId].items.splice(itemIndex, 1); 138 | } 139 | } 140 | 141 | // Return the menu object 142 | return this.menus[menuId]; 143 | }; 144 | 145 | // Remove existing menu object by menu id 146 | this.removeSubMenuItem = function(menuId, submenuItemURL) { 147 | // Validate that the menu exists 148 | this.validateMenuExistance(menuId); 149 | 150 | // Search for menu item to remove 151 | for (var itemIndex in this.menus[menuId].items) { 152 | for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { 153 | if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { 154 | this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); 155 | } 156 | } 157 | } 158 | 159 | // Return the menu object 160 | return this.menus[menuId]; 161 | }; 162 | 163 | //Adding the topbar menu 164 | this.addMenu('topbar'); 165 | } 166 | ]); 167 | -------------------------------------------------------------------------------- /app/controllers/users/users.authentication.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | errorHandler = require('../errors.server.controller'), 8 | mongoose = require('mongoose'), 9 | passport = require('passport'), 10 | User = mongoose.model('User'); 11 | 12 | /** 13 | * Signup 14 | */ 15 | exports.signup = function(req, res) { 16 | // For security measurement we remove the roles from the req.body object 17 | delete req.body.roles; 18 | 19 | // Init Variables 20 | var user = new User(req.body); 21 | var message = null; 22 | 23 | // Add missing user fields 24 | user.provider = 'local'; 25 | user.displayName = user.firstName + ' ' + user.lastName; 26 | 27 | // Then save the user 28 | user.save(function(err) { 29 | if (err) { 30 | return res.status(400).send({ 31 | message: errorHandler.getErrorMessage(err) 32 | }); 33 | } else { 34 | // Remove sensitive data before login 35 | user.password = undefined; 36 | user.salt = undefined; 37 | 38 | req.login(user, function(err) { 39 | if (err) { 40 | res.status(400).send(err); 41 | } else { 42 | res.json(user); 43 | } 44 | }); 45 | } 46 | }); 47 | }; 48 | 49 | /** 50 | * Signin after passport authentication 51 | */ 52 | exports.signin = function(req, res, next) { 53 | passport.authenticate('local', function(err, user, info) { 54 | if (err || !user) { 55 | res.status(400).send(info); 56 | } else { 57 | // Remove sensitive data before login 58 | user.password = undefined; 59 | user.salt = undefined; 60 | 61 | req.login(user, function(err) { 62 | if (err) { 63 | res.status(400).send(err); 64 | } else { 65 | res.json(user); 66 | } 67 | }); 68 | } 69 | })(req, res, next); 70 | }; 71 | 72 | /** 73 | * Signout 74 | */ 75 | exports.signout = function(req, res) { 76 | req.logout(); 77 | res.redirect('/'); 78 | }; 79 | 80 | /** 81 | * OAuth callback 82 | */ 83 | exports.oauthCallback = function(strategy) { 84 | return function(req, res, next) { 85 | passport.authenticate(strategy, function(err, user, redirectURL) { 86 | if (err || !user) { 87 | return res.redirect('/#!/signin'); 88 | } 89 | req.login(user, function(err) { 90 | if (err) { 91 | return res.redirect('/#!/signin'); 92 | } 93 | 94 | return res.redirect(redirectURL || '/'); 95 | }); 96 | })(req, res, next); 97 | }; 98 | }; 99 | 100 | /** 101 | * Helper function to save or update a OAuth user profile 102 | */ 103 | exports.saveOAuthUserProfile = function(req, providerUserProfile, done) { 104 | if (!req.user) { 105 | // Define a search query fields 106 | var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField; 107 | var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField; 108 | 109 | // Define main provider search query 110 | var mainProviderSearchQuery = {}; 111 | mainProviderSearchQuery.provider = providerUserProfile.provider; 112 | mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; 113 | 114 | // Define additional provider search query 115 | var additionalProviderSearchQuery = {}; 116 | additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; 117 | 118 | // Define a search query to find existing user with current provider profile 119 | var searchQuery = { 120 | $or: [mainProviderSearchQuery, additionalProviderSearchQuery] 121 | }; 122 | 123 | User.findOne(searchQuery, function(err, user) { 124 | if (err) { 125 | return done(err); 126 | } else { 127 | if (!user) { 128 | var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : ''); 129 | 130 | User.findUniqueUsername(possibleUsername, null, function(availableUsername) { 131 | user = new User({ 132 | firstName: providerUserProfile.firstName, 133 | lastName: providerUserProfile.lastName, 134 | username: availableUsername, 135 | displayName: providerUserProfile.displayName, 136 | email: providerUserProfile.email, 137 | provider: providerUserProfile.provider, 138 | providerData: providerUserProfile.providerData 139 | }); 140 | 141 | // And save the user 142 | user.save(function(err) { 143 | return done(err, user); 144 | }); 145 | }); 146 | } else { 147 | return done(err, user); 148 | } 149 | } 150 | }); 151 | } else { 152 | // User is already logged in, join the provider data to the existing user 153 | var user = req.user; 154 | 155 | // Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured 156 | if (user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) { 157 | // Add the provider data to the additional provider data field 158 | if (!user.additionalProvidersData) user.additionalProvidersData = {}; 159 | user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData; 160 | 161 | // Then tell mongoose that we've updated the additionalProvidersData field 162 | user.markModified('additionalProvidersData'); 163 | 164 | // And save the user 165 | user.save(function(err) { 166 | return done(err, user, '/#!/settings/accounts'); 167 | }); 168 | } else { 169 | return done(new Error('User is already connected using this provider'), user); 170 | } 171 | } 172 | }; 173 | 174 | /** 175 | * Remove OAuth provider 176 | */ 177 | exports.removeOAuthProvider = function(req, res, next) { 178 | var user = req.user; 179 | var provider = req.param('provider'); 180 | 181 | if (user && provider) { 182 | // Delete the additional provider 183 | if (user.additionalProvidersData[provider]) { 184 | delete user.additionalProvidersData[provider]; 185 | 186 | // Then tell mongoose that we've updated the additionalProvidersData field 187 | user.markModified('additionalProvidersData'); 188 | } 189 | 190 | user.save(function(err) { 191 | if (err) { 192 | return res.status(400).send({ 193 | message: errorHandler.getErrorMessage(err) 194 | }); 195 | } else { 196 | req.login(user, function(err) { 197 | if (err) { 198 | res.status(400).send(err); 199 | } else { 200 | res.json(user); 201 | } 202 | }); 203 | } 204 | }); 205 | } 206 | }; -------------------------------------------------------------------------------- /app/controllers/users/users.password.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | errorHandler = require('../errors.server.controller'), 8 | mongoose = require('mongoose'), 9 | passport = require('passport'), 10 | User = mongoose.model('User'), 11 | config = require('../../../config/config'), 12 | nodemailer = require('nodemailer'), 13 | async = require('async'), 14 | crypto = require('crypto'); 15 | 16 | var smtpTransport = nodemailer.createTransport(config.mailer.options); 17 | 18 | /** 19 | * Forgot for reset password (forgot POST) 20 | */ 21 | exports.forgot = function(req, res, next) { 22 | async.waterfall([ 23 | // Generate random token 24 | function(done) { 25 | crypto.randomBytes(20, function(err, buffer) { 26 | var token = buffer.toString('hex'); 27 | done(err, token); 28 | }); 29 | }, 30 | // Lookup user by username 31 | function(token, done) { 32 | if (req.body.username) { 33 | User.findOne({ 34 | username: req.body.username 35 | }, '-salt -password', function(err, user) { 36 | if (!user) { 37 | return res.status(400).send({ 38 | message: 'No account with that username has been found' 39 | }); 40 | } else if (user.provider !== 'local') { 41 | return res.status(400).send({ 42 | message: 'It seems like you signed up using your ' + user.provider + ' account' 43 | }); 44 | } else { 45 | user.resetPasswordToken = token; 46 | user.resetPasswordExpires = Date.now() + 3600000; // 1 hour 47 | 48 | user.save(function(err) { 49 | done(err, token, user); 50 | }); 51 | } 52 | }); 53 | } else { 54 | return res.status(400).send({ 55 | message: 'Username field must not be blank' 56 | }); 57 | } 58 | }, 59 | function(token, user, done) { 60 | res.render('templates/reset-password-email', { 61 | name: user.displayName, 62 | appName: config.app.title, 63 | url: 'http://' + req.headers.host + '/auth/reset/' + token 64 | }, function(err, emailHTML) { 65 | done(err, emailHTML, user); 66 | }); 67 | }, 68 | // If valid email, send reset email using service 69 | function(emailHTML, user, done) { 70 | var mailOptions = { 71 | to: user.email, 72 | from: config.mailer.from, 73 | subject: 'Password Reset', 74 | html: emailHTML 75 | }; 76 | smtpTransport.sendMail(mailOptions, function(err) { 77 | if (!err) { 78 | res.send({ 79 | message: 'An email has been sent to ' + user.email + ' with further instructions.' 80 | }); 81 | } else { 82 | return res.status(400).send({ 83 | message: 'Failure sending email' 84 | }); 85 | } 86 | 87 | done(err); 88 | }); 89 | } 90 | ], function(err) { 91 | if (err) return next(err); 92 | }); 93 | }; 94 | 95 | /** 96 | * Reset password GET from email token 97 | */ 98 | exports.validateResetToken = function(req, res) { 99 | User.findOne({ 100 | resetPasswordToken: req.params.token, 101 | resetPasswordExpires: { 102 | $gt: Date.now() 103 | } 104 | }, function(err, user) { 105 | if (!user) { 106 | return res.redirect('/#!/password/reset/invalid'); 107 | } 108 | 109 | res.redirect('/#!/password/reset/' + req.params.token); 110 | }); 111 | }; 112 | 113 | /** 114 | * Reset password POST from email token 115 | */ 116 | exports.reset = function(req, res, next) { 117 | // Init Variables 118 | var passwordDetails = req.body; 119 | 120 | async.waterfall([ 121 | 122 | function(done) { 123 | User.findOne({ 124 | resetPasswordToken: req.params.token, 125 | resetPasswordExpires: { 126 | $gt: Date.now() 127 | } 128 | }, function(err, user) { 129 | if (!err && user) { 130 | if (passwordDetails.newPassword === passwordDetails.verifyPassword) { 131 | user.password = passwordDetails.newPassword; 132 | user.resetPasswordToken = undefined; 133 | user.resetPasswordExpires = undefined; 134 | 135 | user.save(function(err) { 136 | if (err) { 137 | return res.status(400).send({ 138 | message: errorHandler.getErrorMessage(err) 139 | }); 140 | } else { 141 | req.login(user, function(err) { 142 | if (err) { 143 | res.status(400).send(err); 144 | } else { 145 | // Return authenticated user 146 | res.json(user); 147 | 148 | done(err, user); 149 | } 150 | }); 151 | } 152 | }); 153 | } else { 154 | return res.status(400).send({ 155 | message: 'Passwords do not match' 156 | }); 157 | } 158 | } else { 159 | return res.status(400).send({ 160 | message: 'Password reset token is invalid or has expired.' 161 | }); 162 | } 163 | }); 164 | }, 165 | function(user, done) { 166 | res.render('templates/reset-password-confirm-email', { 167 | name: user.displayName, 168 | appName: config.app.title 169 | }, function(err, emailHTML) { 170 | done(err, emailHTML, user); 171 | }); 172 | }, 173 | // If valid email, send reset email using service 174 | function(emailHTML, user, done) { 175 | var mailOptions = { 176 | to: user.email, 177 | from: config.mailer.from, 178 | subject: 'Your password has been changed', 179 | html: emailHTML 180 | }; 181 | 182 | smtpTransport.sendMail(mailOptions, function(err) { 183 | done(err, 'done'); 184 | }); 185 | } 186 | ], function(err) { 187 | if (err) return next(err); 188 | }); 189 | }; 190 | 191 | /** 192 | * Change Password 193 | */ 194 | exports.changePassword = function(req, res) { 195 | // Init Variables 196 | var passwordDetails = req.body; 197 | 198 | if (req.user) { 199 | if (passwordDetails.newPassword) { 200 | User.findById(req.user.id, function(err, user) { 201 | if (!err && user) { 202 | if (user.authenticate(passwordDetails.currentPassword)) { 203 | if (passwordDetails.newPassword === passwordDetails.verifyPassword) { 204 | user.password = passwordDetails.newPassword; 205 | 206 | user.save(function(err) { 207 | if (err) { 208 | return res.status(400).send({ 209 | message: errorHandler.getErrorMessage(err) 210 | }); 211 | } else { 212 | req.login(user, function(err) { 213 | if (err) { 214 | res.status(400).send(err); 215 | } else { 216 | res.send({ 217 | message: 'Password changed successfully' 218 | }); 219 | } 220 | }); 221 | } 222 | }); 223 | } else { 224 | res.status(400).send({ 225 | message: 'Passwords do not match' 226 | }); 227 | } 228 | } else { 229 | res.status(400).send({ 230 | message: 'Current password is incorrect' 231 | }); 232 | } 233 | } else { 234 | res.status(400).send({ 235 | message: 'User is not found' 236 | }); 237 | } 238 | }); 239 | } else { 240 | res.status(400).send({ 241 | message: 'Please provide a new password' 242 | }); 243 | } 244 | } else { 245 | res.status(400).send({ 246 | message: 'User is not signed in' 247 | }); 248 | } 249 | }; 250 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------