├── .slugignore ├── .bowerrc ├── Procfile ├── public ├── robots.txt ├── modules │ ├── core │ │ ├── css │ │ │ ├── navbar-static-top.less │ │ │ ├── angular-ui.less │ │ │ ├── justified-nav.less │ │ │ ├── sticky-footer.less │ │ │ ├── core.less │ │ │ └── generic.less │ │ ├── images │ │ │ ├── mean.png │ │ │ ├── Dude@2x.png │ │ │ ├── Girl@2x.png │ │ │ ├── Goal@2x.png │ │ │ ├── Map@2x.png │ │ │ ├── Yelp-32.png │ │ │ ├── meanjs.png │ │ │ ├── BreweryDB.png │ │ │ ├── Graph@2x.png │ │ │ ├── Search@2x.png │ │ │ ├── Shield@2x.png │ │ │ ├── Facebook-32.png │ │ │ ├── Instagram-32.png │ │ │ ├── Pensils@2x.png │ │ │ ├── Pinterest-32.png │ │ │ ├── Settings@2x.png │ │ │ ├── Twitter-32.png │ │ │ ├── hack_reactor.png │ │ │ ├── hrhq-logo@2x.png │ │ │ ├── meanjs_logo.png │ │ │ ├── BreweryDB_logo.png │ │ │ ├── Foursquare-32.png │ │ │ ├── GooglePlus-32.png │ │ │ ├── YouTube_01-32.png │ │ │ ├── predictionio.jpeg │ │ │ ├── Facebook_Like-32.png │ │ │ ├── PredictionIO-logo.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 │ │ ├── services │ │ │ ├── search.client.service.js │ │ │ ├── core.client.service.js │ │ │ └── menus.client.service.js │ │ ├── config │ │ │ └── core.client.routes.js │ │ ├── tests │ │ │ ├── home.client.controller.test.js │ │ │ └── header.client.controller.test.js │ │ └── views │ │ │ ├── header.client.view.html │ │ │ └── home.client.view.html │ ├── nearby │ │ ├── images │ │ │ └── beer-icon.png │ │ ├── nearby.client.module.js │ │ ├── controllers │ │ │ ├── info.client.controller.js │ │ │ └── nearby.client.controller.js │ │ ├── config │ │ │ ├── nearby.client.config.js │ │ │ └── nearby.client.routes.js │ │ ├── services │ │ │ └── breweries.client.service.js │ │ ├── views │ │ │ ├── info.client.view.html │ │ │ └── nearby.client.view.html │ │ ├── css │ │ │ └── map.less │ │ └── tests │ │ │ └── nearby.client.controller.test.js │ ├── users │ │ ├── img │ │ │ └── buttons │ │ │ │ ├── github.png │ │ │ │ ├── google.png │ │ │ │ ├── facebook.png │ │ │ │ ├── linkedin.png │ │ │ │ └── twitter.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.less │ │ ├── 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 │ ├── beer │ │ ├── beer.client.module.js │ │ ├── services │ │ │ ├── beer.client.service.js │ │ │ └── stylequery.client.service.js │ │ ├── config │ │ │ └── beer.client.routes.js │ │ ├── controllers │ │ │ └── beer.client.controller.js │ │ ├── tests │ │ │ └── beer.client.controller.test.js │ │ └── views │ │ │ └── beer.client.view.html │ ├── ratings │ │ ├── ratings.client.module.js │ │ ├── services │ │ │ ├── beer.client.service.js │ │ │ ├── stylequery.client.service.js │ │ │ ├── ratings.client.service.js │ │ │ └── predictionio.client.service.js │ │ ├── config │ │ │ ├── ratings.client.config.js │ │ │ └── ratings.client.routes.js │ │ ├── views │ │ │ ├── edit-rating.client.view.html │ │ │ ├── view-rating.client.view.html │ │ │ └── list-ratings.client.view.html │ │ ├── controllers │ │ │ └── ratings.client.controller.js │ │ └── tests │ │ │ └── ratings.client.controller.test.js │ ├── search │ │ ├── search.client.module.js │ │ ├── config │ │ │ └── search.client.routes.js │ │ ├── views │ │ │ └── search.client.view.html │ │ ├── controllers │ │ │ └── search.client.controller.js │ │ └── tests │ │ │ └── search.client.controller.test.js │ └── brewery │ │ ├── brewery.client.module.js │ │ ├── config │ │ └── brewery.client.routes.js │ │ ├── services │ │ └── brewery.client.service.js │ │ ├── tests │ │ └── brewery.client.controller.test.js │ │ └── controllers │ │ └── brewery.client.controller.js ├── humans.txt ├── application.js ├── config.js └── less │ ├── mixins.less │ └── application.less ├── .travis.yml ├── .env ├── 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 │ ├── beers.server.routes.js │ ├── brewery.server.routes.js │ ├── search.server.routes.js │ ├── beer.server.routes.js │ ├── breweries.server.routes.js │ ├── recommedation.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 │ └── 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 ├── newrelic.js ├── config ├── logger.js ├── passport.js ├── init.js ├── strategies │ ├── local.js │ ├── twitter.js │ ├── google.js │ └── facebook.js ├── env │ ├── test.js │ ├── development.js │ ├── secure.js │ ├── all.js │ └── production.js ├── config.js └── express.js ├── scripts └── generate-ssl-certs.sh ├── Dockerfile ├── .gitignore ├── bower.json ├── .editorconfig ├── LICENSE.md ├── params.json ├── README.md ├── PRESS-RELEASE.md ├── server.js ├── karma.conf.js ├── .jshintrc ├── package.json └── Gruntfile.js /.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 | -------------------------------------------------------------------------------- /public/modules/core/css/navbar-static-top.less: -------------------------------------------------------------------------------- 1 | .navbar-static-top { 2 | margin-bottom: 0; 3 | } 4 | -------------------------------------------------------------------------------- /public/modules/core/css/angular-ui.less: -------------------------------------------------------------------------------- 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/mean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/mean.png -------------------------------------------------------------------------------- /public/modules/core/images/Dude@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Dude@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Girl@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Girl@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Goal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Goal@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Map@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Map@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Yelp-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Yelp-32.png -------------------------------------------------------------------------------- /public/modules/core/images/meanjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/meanjs.png -------------------------------------------------------------------------------- /public/modules/core/img/brand/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/img/brand/logo.png -------------------------------------------------------------------------------- /public/modules/core/images/BreweryDB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/BreweryDB.png -------------------------------------------------------------------------------- /public/modules/core/images/Graph@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Graph@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Search@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Shield@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Shield@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Facebook-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Facebook-32.png -------------------------------------------------------------------------------- /public/modules/core/images/Instagram-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Instagram-32.png -------------------------------------------------------------------------------- /public/modules/core/images/Pensils@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Pensils@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Pinterest-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Pinterest-32.png -------------------------------------------------------------------------------- /public/modules/core/images/Settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Settings@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/Twitter-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Twitter-32.png -------------------------------------------------------------------------------- /public/modules/core/images/hack_reactor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/hack_reactor.png -------------------------------------------------------------------------------- /public/modules/core/images/hrhq-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/hrhq-logo@2x.png -------------------------------------------------------------------------------- /public/modules/core/images/meanjs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/meanjs_logo.png -------------------------------------------------------------------------------- /public/modules/core/img/brand/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/img/brand/favicon.ico -------------------------------------------------------------------------------- /public/modules/core/img/loaders/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/img/loaders/loader.gif -------------------------------------------------------------------------------- /public/modules/nearby/images/beer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/nearby/images/beer-icon.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/users/img/buttons/github.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/users/img/buttons/google.png -------------------------------------------------------------------------------- /public/modules/core/images/BreweryDB_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/BreweryDB_logo.png -------------------------------------------------------------------------------- /public/modules/core/images/Foursquare-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Foursquare-32.png -------------------------------------------------------------------------------- /public/modules/core/images/GooglePlus-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/GooglePlus-32.png -------------------------------------------------------------------------------- /public/modules/core/images/YouTube_01-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/YouTube_01-32.png -------------------------------------------------------------------------------- /public/modules/core/images/predictionio.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/predictionio.jpeg -------------------------------------------------------------------------------- /public/modules/users/img/buttons/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/users/img/buttons/facebook.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/users/img/buttons/linkedin.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/users/img/buttons/twitter.png -------------------------------------------------------------------------------- /public/modules/core/images/Facebook_Like-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/Facebook_Like-32.png -------------------------------------------------------------------------------- /public/modules/core/images/PredictionIO-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/PredictionIO-logo.png -------------------------------------------------------------------------------- /public/modules/core/images/no-beer-label-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/no-beer-label-60.png -------------------------------------------------------------------------------- /public/modules/core/images/on-tapp-165x165.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorleungtw/on-tapp/HEAD/public/modules/core/images/on-tapp-165x165.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MONGOLAB_URI="mongodb://MongoLab-i:8Lu9GZSXvdiVCqOTDGRZL7CHOS4XN.FzDpCqHCeTKhc-@ds052827.mongolab.com:52827/MongoLab-i" 2 | NODE_ENV="production" 3 | -------------------------------------------------------------------------------- /app/views/index.server.view.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.server.view.html' %} 2 | 3 | {% block content %} 4 |
5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /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.less: -------------------------------------------------------------------------------- 1 | /* Main marketing message and sign up button */ 2 | .jumbotron { 3 | text-align: center; 4 | background-color: transparent; 5 | } 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 %} 9 | -------------------------------------------------------------------------------- /public/modules/beer/beer.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use application configuration module to register a new module 4 | ApplicationConfiguration.registerModule('beer'); 5 | -------------------------------------------------------------------------------- /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/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'); -------------------------------------------------------------------------------- /public/modules/search/search.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use application configuration module to register a new module 4 | ApplicationConfiguration.registerModule('search'); 5 | -------------------------------------------------------------------------------- /public/modules/brewery/brewery.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use application configuration module to register a new module 4 | ApplicationConfiguration.registerModule('brewery'); 5 | -------------------------------------------------------------------------------- /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 %} 9 | -------------------------------------------------------------------------------- /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 | }; 8 | -------------------------------------------------------------------------------- /public/modules/users/views/password/reset-password-success.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Password successfully reset

3 | Continue to home page 4 |
5 | -------------------------------------------------------------------------------- /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 | }; 12 | -------------------------------------------------------------------------------- /public/modules/nearby/controllers/info.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('nearby').controller('infoWindowController', ['$scope', '$stateParams', 4 | function($scope, $stateParams) { 5 | $scope.params = $stateParams; 6 | } 7 | ]); -------------------------------------------------------------------------------- /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 |
5 | -------------------------------------------------------------------------------- /public/modules/core/css/sticky-footer.less: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | } 5 | 6 | body { 7 | margin-bottom: 108px; 8 | } 9 | 10 | .footer { 11 | height: 60px; 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/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/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 | ]); 9 | -------------------------------------------------------------------------------- /public/modules/users/css/users.less: -------------------------------------------------------------------------------- 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 | } 15 | -------------------------------------------------------------------------------- /public/modules/beer/services/beer.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('beer').factory('Beer', ['$http', 4 | function($http) { 5 | // Public API 6 | return { 7 | getData: function(beerId){ 8 | return $http.get('/api/beer/' + beerId); 9 | } 10 | }; 11 | } 12 | ]); 13 | -------------------------------------------------------------------------------- /public/modules/ratings/services/beer.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('beer').factory('Beer', ['$http', 4 | function($http) { 5 | // Public API 6 | return { 7 | getData: function(beerId){ 8 | return $http.get('/api/beer/' + beerId); 9 | } 10 | }; 11 | } 12 | ]); 13 | -------------------------------------------------------------------------------- /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', 'Recommendations', 'recommendations', '/ratings(/create)?'); 8 | } 9 | ]); 10 | -------------------------------------------------------------------------------- /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/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){ 8 | return $http.get('/api/search/' + keyword + '/' + page); 9 | } 10 | }; 11 | } 12 | ]); 13 | -------------------------------------------------------------------------------- /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 | ]); 15 | -------------------------------------------------------------------------------- /public/modules/nearby/services/breweries.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('nearby').factory('Breweries', ['$http', 4 | function($http) { 5 | // Public API 6 | return { 7 | getData: function(coords){ 8 | return $http.get('/api/breweries/' + coords.lat + '/' + coords.long); 9 | } 10 | }; 11 | } 12 | ]); 13 | -------------------------------------------------------------------------------- /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 | ]); 13 | -------------------------------------------------------------------------------- /public/modules/beer/config/beer.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('beer').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Beers state routing 7 | $stateProvider. 8 | state('beer', { 9 | url: '/beer/:beerId', 10 | templateUrl: 'modules/beer/views/beer.client.view.html' 11 | }); 12 | } 13 | ]); 14 | -------------------------------------------------------------------------------- /public/modules/beer/services/stylequery.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('beer').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('/api/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 | 14 | -------------------------------------------------------------------------------- /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('/api/style/' + styleName); 12 | } 13 | }; 14 | } 15 | ]); 16 | -------------------------------------------------------------------------------- /public/modules/brewery/config/brewery.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('brewery').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Beers state routing 7 | $stateProvider. 8 | state('brewery', { 9 | url: '/brewery/:breweryId', 10 | templateUrl: 'modules/brewery/views/brewery.client.view.html' 11 | }); 12 | } 13 | ]); 14 | -------------------------------------------------------------------------------- /public/modules/nearby/views/info.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ params.name }}
4 | {{ params.dist }} miles away
5 | {{ params.addr }}
6 | {{ params.phone}}
7 | List their beers 8 |
9 |
10 | -------------------------------------------------------------------------------- /public/modules/search/config/search.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('search').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Ratings state routing 7 | $stateProvider. 8 | state('search', { 9 | url: '/search/:page/:keyword', 10 | templateUrl: 'modules/search/views/search.client.view.html' 11 | }); 12 | } 13 | ]); 14 | -------------------------------------------------------------------------------- /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('/api/ratings/:ratingId', { 7 | ratingId: '@_id' 8 | }, { 9 | update: { 10 | method: 'PUT' 11 | } 12 | }); 13 | } 14 | ]); 15 | -------------------------------------------------------------------------------- /public/modules/ratings/services/predictionio.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('ratings').factory('PredictionIO', ['$http', 4 | function($http) { 5 | // Predictionio service logic 6 | // ... 7 | 8 | // Public API 9 | return { 10 | getRecommendaton: function(userId) { 11 | return $http.get('/api/recommendation/' + userId); 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/brewery/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('/api/brewery/' + breweryId); 9 | }, 10 | getBeersData: function(breweryId){ 11 | return $http.get('/api/beers/' + breweryId); 12 | } 13 | }; 14 | } 15 | ]); 16 | -------------------------------------------------------------------------------- /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 | ); 17 | -------------------------------------------------------------------------------- /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('home', { 12 | url: '/', 13 | templateUrl: 'modules/core/views/home.client.view.html' 14 | }); 15 | } 16 | ]); 17 | -------------------------------------------------------------------------------- /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 | 19 | -------------------------------------------------------------------------------- /app/routes/beers.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var request = require('request'); 3 | var config = require('../../config/config'); 4 | 5 | module.exports = function(app) { 6 | // breweryId contains the id of the brewery to allow listing of beers 7 | app.get('/api/beers/:breweryId', function(req, res){ 8 | request('https://api.brewerydb.com/v2/brewery/' + req.params.breweryId + '/beers?key=' + config.brewerydb.api, 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 request = require('request'); 3 | var config = require('../../config/config'); 4 | 5 | module.exports = function(app) { 6 | // breweryId contains the id of the beer 7 | app.get('/api/brewery/:breweryId', function(req, res){ 8 | request('https://api.brewerydb.com/v2/brewery/' + req.params.breweryId + '?withGuilds=Y&withSocialAccounts=Y&withLocations=Y&key=' + config.brewerydb.api, function (error, response, body) { 9 | if (!error && response.statusCode === 200) { 10 | res.send(body); 11 | } 12 | }); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /app/routes/search.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var request = require('request'); 3 | var config = require('../../config/config'); 4 | 5 | module.exports = function(app) { 6 | // name contains the partial of what the user is searching for 7 | app.get('/api/search/:keyword/:page', function(req, res){ 8 | request('https://api.brewerydb.com/v2/search?q=' + req.params.keyword + '&p=' + req.params.page + '&key=' + config.brewerydb.api, function (error, response, body) { 9 | if (!error && response.statusCode === 200) { 10 | res.send(body); 11 | } 12 | }); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /app/routes/beer.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var request = require('request'); 3 | var config = require('../../config/config'); 4 | var api_key = config.brewerydb.api; 5 | 6 | module.exports = function(app) { 7 | // beerId contains the id of the beer 8 | app.get('/api/beer/:beerId', function(req, res){ 9 | request('https://api.brewerydb.com/v2/beer/' + req.params.beerId + '?withBreweries=Y&withSocialAccounts=Y&withIngredients=Y&key=' + api_key, function (error, response, body) { 10 | if (!error && response.statusCode === 200) { 11 | res.send(body); 12 | } 13 | }); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /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('nearby', { 9 | url: '/nearby', 10 | templateUrl: 'modules/nearby/views/nearby.client.view.html' 11 | }); 12 | 13 | uiGmapGoogleMapApiProvider.configure({ 14 | key: 'AIzaSyAQHm36O2gZr34HkBjElKYHox3LVWR8UWY', 15 | v: '3.17', 16 | libraries: 'geometry,visualization' 17 | }); 18 | } 19 | ]); 20 | -------------------------------------------------------------------------------- /app/routes/breweries.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var request = require('request'); 3 | var config = require('../../config/config'); 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('/api/breweries/:lat/:lng', function(req, res){ 9 | request('https://api.brewerydb.com/v2/search/geo/point?lat=' + req.params.lat + '&lng=' + req.params.lng + '&radius=20&key=' + config.brewerydb.api, 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 | })(); 25 | -------------------------------------------------------------------------------- /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: '/recommendations', 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 | })(); 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }; 37 | -------------------------------------------------------------------------------- /app/routes/recommedation.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require('request'); 4 | var config = require('../../config/config'); 5 | 6 | module.exports = function(app) { 7 | // Routing logic 8 | // ... 9 | app.get('/api/recommendation/:userId', function(req, res){ 10 | 11 | request.post({ 12 | headers: {'content-type' : 'application/json'}, 13 | url: 'http://54.183.105.216:8000/queries.json', 14 | body: JSON.stringify({ 15 | user: '54b47a41557aa852537fb0ef', 16 | num: 1 17 | }) 18 | }, function (error, response, body) { 19 | if (!error && response.statusCode === 200) { 20 | res.send(body); 21 | } else { 22 | res.send(error); 23 | } 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /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"] -------------------------------------------------------------------------------- /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 | }; 34 | -------------------------------------------------------------------------------- /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.html5Mode(true); 10 | $locationProvider.hashPrefix('!'); 11 | } 12 | ]); 13 | 14 | //Then define the init function for starting up the application 15 | angular.element(document).ready(function() { 16 | //Fixing facebook bug with redirect 17 | if (window.location.hash === '#_=_') window.location.hash = '#!'; 18 | 19 | //Then init the app 20 | angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); 21 | }); 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }; 32 | -------------------------------------------------------------------------------- /.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 | public/assets/ 61 | recommendation-engine/ 62 | -------------------------------------------------------------------------------- /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}); 25 | $scope.keyword = ''; 26 | }; 27 | } 28 | ]); 29 | -------------------------------------------------------------------------------- /public/modules/nearby/css/map.less: -------------------------------------------------------------------------------- 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: 0; 14 | top: 53px; 15 | bottom: 0; 16 | height: 100%; 17 | } 18 | 19 | .right-half-container{ 20 | position: absolute; 21 | left: 40%; 22 | right: 0; 23 | top: 53px; 24 | bottom: 0; 25 | overflow: scroll; 26 | } 27 | 28 | @media (max-width: 750px) { 29 | 30 | .angular-google-map, 31 | .angular-google-map-container { 32 | height: 100%; 33 | width: 100%; 34 | } 35 | 36 | .left-half-container{ 37 | position: absolute; 38 | left: 0; 39 | right: 0; 40 | top: 0; 41 | bottom: 0; 42 | height: 100%; 43 | margin-top: 53px; 44 | } 45 | 46 | .right-half-container{ 47 | position: absolute; 48 | left: 0; 49 | right: 0; 50 | top: 25%; 51 | bottom: 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /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 | "contributors": [ 13 | "Julie", 14 | "Victor" 15 | ], 16 | "dependencies": { 17 | "bootstrap": "^3.3.1", 18 | "angular": "^1.2", 19 | "angular-resource": "^1.2", 20 | "angular-animate": "~1.2", 21 | "angular-mocks": "^1.2", 22 | "angular-bootstrap": "^0.12.0", 23 | "angular-ui-utils": "^0.1.1", 24 | "angular-ui-router": "^0.2.11", 25 | "fontawesome": "^4.2.0", 26 | "angular-google-maps": "^2.0.12", 27 | "angularjs-geolocation": "^0.1.1", 28 | "flat-ui": "^2.2.2", 29 | "angular-spinner": "^0.6.1", 30 | "spin.js": "^2.0.2", 31 | "fastclick": "^1.0.3" 32 | }, 33 | "resolutions": { 34 | "angular": ">=1 <1.3.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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 | ]); 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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/nearby/views/nearby.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /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 | }; 39 | -------------------------------------------------------------------------------- /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 | }; 43 | -------------------------------------------------------------------------------- /public/modules/users/views/password/forgot-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Restore your password

3 |

Enter your account username.

4 |
5 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /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 request = require('request'); 4 | var config = require('../../config/config'); 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('/api/ratings') 12 | .get(ratings.list) 13 | .post(users.requiresLogin, ratings.create); 14 | 15 | app.route('/api/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('/api/style/:styleName', function(req, res){ 25 | request('https://api.brewerydb.com/v2/search/style?q=' + req.params.styleName + '&key=' + config.brewerydb.api, function (error, response, body) { 26 | if (!error && response.statusCode === 200) { 27 | res.send(body); 28 | } 29 | }); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /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."} -------------------------------------------------------------------------------- /public/modules/core/css/core.less: -------------------------------------------------------------------------------- 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-bottom: 40px; 25 | padding-bottom: 20px; 26 | } 27 | .search-bar { 28 | width: 240px; 29 | margin-top: 5px; 30 | } 31 | .search-form { 32 | margin-bottom: 5px; 33 | } 34 | .sponsor{ 35 | width: 150px; 36 | } 37 | .quote-container { 38 | background-color: #f5f5f5; 39 | border: 1px solid #e3e3e3; 40 | padding-top: 40px; 41 | padding-bottom: 40px; 42 | margin-bottom: 40px; 43 | } 44 | .top-rated{ 45 | height: 40px; 46 | } 47 | .landing-page-icon{ 48 | width: 100px; 49 | height: 100px; 50 | } 51 | .landing-page-heading{ 52 | font-size: 30px; 53 | } 54 | .landing-page-sub-heading{ 55 | font-size: 18px; 56 | } 57 | -------------------------------------------------------------------------------- /public/modules/search/views/search.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 |
8 |

Your search for "{{keyword}}" returned {{totalResults}} results:

9 | 10 | 16 | 17 |
18 |
19 | Nothing found!! Please try again... 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /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 | }; 39 | -------------------------------------------------------------------------------- /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 | ]); 35 | -------------------------------------------------------------------------------- /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 | }; 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OnTapp 2 | 3 | > Discover breweries around you and receive personalized recommendations based on your rated beers. OnTapp uses your geolocation and machine learning algorithms to find the perfect beer you’ll love. 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: [http://ontappapp.com](http://ontappapp.com) 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/search/controllers/search.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('search').controller('SearchController', ['$scope', 'Search', '$stateParams', '$state', 'usSpinnerService', 4 | function($scope, Search, $stateParams, $state, usSpinnerService) { 5 | // Search controller logic 6 | $scope.results = []; 7 | $scope.totalResults = undefined; 8 | 9 | $scope.search = function(currentPage) { 10 | currentPage = currentPage || 1; 11 | $state.go('search', {'page': currentPage, 'keyword': $scope.keyword}); 12 | $scope.keyword = ''; 13 | }; 14 | 15 | Search.getData($stateParams.keyword, $stateParams.page).success(function(response, status) { 16 | $scope.status = status; 17 | if ($scope.status === 200) { 18 | if (response.totalResults !== undefined) { 19 | $scope.numberOfPages = response.numberOfPages; 20 | $scope.totalResults = response.totalResults; 21 | $scope.keyword = $stateParams.keyword; 22 | $scope.currentPage = $stateParams.page; 23 | $scope.results = response.data; 24 | } else { 25 | $scope.totalResults = 0; 26 | $scope.numberOfPages = 0; 27 | } 28 | } else { 29 | $scope.results = response || 'Request failed'; 30 | } 31 | usSpinnerService.stop('spinner-2'); //stop the spinner 32 | }); 33 | } 34 | 35 | 36 | ]); 37 | -------------------------------------------------------------------------------- /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 |
24 | -------------------------------------------------------------------------------- /public/modules/users/views/password/reset-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Reset your password

3 |
4 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }; 42 | -------------------------------------------------------------------------------- /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 | }; 42 | -------------------------------------------------------------------------------- /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 | }; 57 | -------------------------------------------------------------------------------- /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 | }); 65 | -------------------------------------------------------------------------------- /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('--'); 52 | -------------------------------------------------------------------------------- /public/modules/beer/controllers/beer.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('beer').controller('BeerController', ['$scope', 'Beer', '$stateParams', 'StyleQuery', 4 | function($scope, Beer, $stateParams, StyleQuery) { 5 | // Beer controller logic 6 | $scope.beerId = $stateParams.beerId; 7 | 8 | // an array to store recommendations 9 | $scope.recommendations = []; 10 | 11 | var shuffle = function (array) { 12 | var currentIndex = array.length, temporaryValue, randomIndex ; 13 | 14 | // While there remain elements to shuffle... 15 | while (0 !== currentIndex) { 16 | 17 | // Pick a remaining element... 18 | randomIndex = Math.floor(Math.random() * currentIndex); 19 | currentIndex -= 1; 20 | 21 | // And swap it with the current element. 22 | temporaryValue = array[currentIndex]; 23 | array[currentIndex] = array[randomIndex]; 24 | array[randomIndex] = temporaryValue; 25 | } 26 | 27 | return array; 28 | }; 29 | 30 | // pushing recommendations data from $http request 31 | var handleSuccess = function(data, status){ 32 | $scope.recommendations = shuffle(data.data); 33 | }; 34 | 35 | // Find the beers in the same category 36 | var getRecommendations = function(styleName){ 37 | StyleQuery.getStyle(styleName).success(handleSuccess); 38 | }; 39 | 40 | Beer.getData($scope.beerId).success(function(results, status) { 41 | $scope.beer = results.data || 'Request failed'; 42 | getRecommendations(results.data.style.name); 43 | }); 44 | } 45 | ]); 46 | -------------------------------------------------------------------------------- /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: ['Chrome', 'Firefox'], 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 | ]); 45 | -------------------------------------------------------------------------------- /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 | ]); 46 | -------------------------------------------------------------------------------- /public/modules/users/views/settings/change-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Change your password

3 |
4 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /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 | // Finish by binding the user middleware 48 | app.param('userId', users.userByID); 49 | }; 50 | -------------------------------------------------------------------------------- /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 | mailer: { 40 | from: process.env.MAILER_FROM || 'MAILER_FROM', 41 | options: { 42 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 43 | auth: { 44 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 45 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 46 | } 47 | } 48 | }, 49 | brewerydb: { 50 | api: '87f92c3b32c0d8e031f828b0d03c2c2a' 51 | }, 52 | predictionio: { 53 | api: 'hmQhiIak1jIyaE4haGDSzUBhTd43XKZxkTfTRcyL9MPueUohvQcVdFnAB0smNQb6' 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /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 | }); 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 - Discover breweries around you & receive personalized recommendations based on your rated beers' 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 | mailer: { 39 | from: process.env.MAILER_FROM || 'ontappapp@gmail.com', 40 | options: { 41 | service: process.env.MAILER_SERVICE_PROVIDER || 'Gmail', 42 | auth: { 43 | user: process.env.MAILER_EMAIL_ID || 'ontappapp@gmail.com', 44 | pass: process.env.MAILER_PASSWORD || 'Cs0cs5#BVfrBR5a&ZK@Z' 45 | } 46 | } 47 | }, 48 | brewerydb: { 49 | api: '87f92c3b32c0d8e031f828b0d03c2c2a' 50 | }, 51 | predictionio: { 52 | api: 'hmQhiIak1jIyaE4haGDSzUBhTd43XKZxkTfTRcyL9MPueUohvQcVdFnAB0smNQb6' 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /public/modules/users/views/settings/edit-profile.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Edit your profile

3 | 4 |
5 | 34 |
35 |
36 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 |
40 | -------------------------------------------------------------------------------- /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 | }; 77 | -------------------------------------------------------------------------------- /public/modules/beer/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 | }()); 59 | -------------------------------------------------------------------------------- /public/modules/search/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 | }()); 59 | -------------------------------------------------------------------------------- /public/modules/brewery/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 | }()); 59 | -------------------------------------------------------------------------------- /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 | ]); 72 | -------------------------------------------------------------------------------- /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 | mailer: { 55 | from: process.env.MAILER_FROM || 'MAILER_FROM', 56 | options: { 57 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 58 | auth: { 59 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 60 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 61 | } 62 | } 63 | }, 64 | brewerydb: { 65 | api: '87f92c3b32c0d8e031f828b0d03c2c2a' 66 | }, 67 | predictionio: { 68 | api: 'hmQhiIak1jIyaE4haGDSzUBhTd43XKZxkTfTRcyL9MPueUohvQcVdFnAB0smNQb6' 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 |
49 | -------------------------------------------------------------------------------- /public/modules/core/services/core.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').factory('Core', [ 4 | function() { 5 | return { 6 | fixPhone: function(phone) { 7 | var s = phone.toString(); 8 | return s.substring(0,3) + '-' + s.substring(3,6) + '-' + s.substring(6); 9 | }, 10 | findIndexByKeyValue: function(obj, key, value) { 11 | for (var i = 0; i < obj.length; i++) { 12 | if (obj[i][key] === value) { 13 | return i; 14 | } 15 | } 16 | return -1; 17 | }, 18 | abbrState: function(input) { 19 | var states = [ 20 | ['Arizona', 'AZ'], 21 | ['Alabama', 'AL'], 22 | ['Alaska', 'AK'], 23 | ['Arizona', 'AZ'], 24 | ['Arkansas', 'AR'], 25 | ['California', 'CA'], 26 | ['Colorado', 'CO'], 27 | ['Connecticut', 'CT'], 28 | ['Delaware', 'DE'], 29 | ['Florida', 'FL'], 30 | ['Georgia', 'GA'], 31 | ['Hawaii', 'HI'], 32 | ['Idaho', 'ID'], 33 | ['Illinois', 'IL'], 34 | ['Indiana', 'IN'], 35 | ['Iowa', 'IA'], 36 | ['Kansas', 'KS'], 37 | ['Kentucky', 'KY'], 38 | ['Kentucky', 'KY'], 39 | ['Louisiana', 'LA'], 40 | ['Maine', 'ME'], 41 | ['Maryland', 'MD'], 42 | ['Massachusetts', 'MA'], 43 | ['Michigan', 'MI'], 44 | ['Minnesota', 'MN'], 45 | ['Mississippi', 'MS'], 46 | ['Missouri', 'MO'], 47 | ['Montana', 'MT'], 48 | ['Nebraska', 'NE'], 49 | ['Nevada', 'NV'], 50 | ['New Hampshire', 'NH'], 51 | ['New Jersey', 'NJ'], 52 | ['New Mexico', 'NM'], 53 | ['New York', 'NY'], 54 | ['North Carolina', 'NC'], 55 | ['North Dakota', 'ND'], 56 | ['Ohio', 'OH'], 57 | ['Oklahoma', 'OK'], 58 | ['Oregon', 'OR'], 59 | ['Pennsylvania', 'PA'], 60 | ['Rhode Island', 'RI'], 61 | ['South Carolina', 'SC'], 62 | ['South Dakota', 'SD'], 63 | ['Tennessee', 'TN'], 64 | ['Texas', 'TX'], 65 | ['Utah', 'UT'], 66 | ['Vermont', 'VT'], 67 | ['Virginia', 'VA'], 68 | ['Washington', 'WA'], 69 | ['West Virginia', 'WV'], 70 | ['Wisconsin', 'WI'], 71 | ['Wyoming', 'WY'], 72 | ]; 73 | 74 | input = input.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); 75 | for (var i = 0; i < states.length; i++){ 76 | if (states[i][0] === input){ 77 | return (states[i][1]); 78 | } 79 | } 80 | } 81 | }; 82 | } 83 | ]); -------------------------------------------------------------------------------- /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 | 'public/application.min.css' 68 | ], 69 | js: [ 70 | 'public/config.js', 71 | 'public/application.js', 72 | 'public/modules/*/*.js', 73 | 'public/modules/*/*[!tests]*/*.js' 74 | ], 75 | tests: [ 76 | 'public/lib/angular-mocks/angular-mocks.js', 77 | 'public/modules/*/tests/*.js' 78 | ] 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: { 5 | uri: process.env.MONGOHQ_URL || 'mongodb://MongoLab-i:8Lu9GZSXvdiVCqOTDGRZL7CHOS4XN.FzDpCqHCeTKhc-@ds052827.mongolab.com:52827/MongoLab-i' || '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 | ], 24 | js: [ 25 | 'public/lib/angular/angular.min.js', 26 | 'public/lib/angular-resource/angular-resource.min.js', 27 | 'public/lib/angular-animate/angular-animate.min.js', 28 | 'public/lib/angular-ui-router/release/angular-ui-router.min.js', 29 | 'public/lib/angular-ui-utils/ui-utils.min.js', 30 | 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js', 31 | 'public/lib/lodash/dist/lodash.min.js', 32 | 'public/lib/angular-google-maps/dist/angular-google-maps.min.js', 33 | 'public/lib/angularjs-geolocation/src/geolocation.js', 34 | 'public/lib/spin.js/spin.js', 35 | 'public/lib/angular-spinner/angular-spinner.min.js', 36 | 'public/lib/fastclick/lib/fastclick.js' 37 | ] 38 | }, 39 | css: 'public/dist/application.min.css', 40 | js: 'public/dist/application.min.js' 41 | }, 42 | facebook: { 43 | clientID: process.env.FACEBOOK_ID || '968149419880980', 44 | clientSecret: process.env.FACEBOOK_SECRET || 'fc60727d78e8fcd2e975822c8eef619e', 45 | callbackURL: '/auth/facebook/callback' 46 | }, 47 | twitter: { 48 | clientID: process.env.TWITTER_KEY || 'k186DeKdHcNN4Vlk8lB9MmjBW', 49 | clientSecret: process.env.TWITTER_SECRET || '9V0X34pyjBuixI8bRwuGf639spcOWs91Q1Fb9Iq4qjDPRgsCf7', 50 | callbackURL: '/auth/twitter/callback' 51 | }, 52 | google: { 53 | clientID: process.env.GOOGLE_ID || '768801944338-ms3g3ic84auevq51a2t4cld7ept7ffos.apps.googleusercontent.com', 54 | clientSecret: process.env.GOOGLE_SECRET || '5xxSzR-ltreqF7EloyCt9JVC', 55 | callbackURL: '/auth/google/callback' 56 | }, 57 | mailer: { 58 | from: process.env.MAILER_FROM || 'ontappapp@gmail.com', 59 | options: { 60 | service: process.env.MAILER_SERVICE_PROVIDER || 'Gmail', 61 | auth: { 62 | user: process.env.MAILER_EMAIL_ID || 'ontappapp@gmail.com', 63 | pass: process.env.MAILER_PASSWORD || 'Cs0cs5#BVfrBR5a&ZK@Z' 64 | } 65 | } 66 | }, 67 | brewerydb: { 68 | api: '87f92c3b32c0d8e031f828b0d03c2c2a' 69 | }, 70 | predictionio: { 71 | eventServerIP: 'http://54.183.105.216:7070', 72 | resultsServerIP: 'http://54.183.105.216:8000', 73 | api: 'hmQhiIak1jIyaE4haGDSzUBhTd43XKZxkTfTRcyL9MPueUohvQcVdFnAB0smNQb6' 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /public/modules/core/css/generic.less: -------------------------------------------------------------------------------- 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 | p.beer-btn { 17 | margin-top: 15px; 18 | } 19 | .beer { 20 | box-sizing: border-box; 21 | font-size: 0.8em; 22 | line-height: 1.3em; 23 | margin: 0 15px 15px 0; 24 | padding: 5px 15px 0 15px; 25 | border: 1px solid #5a5a5a; 26 | width: 232px; 27 | height: 420px; 28 | -webkit-border-radius: 5px; 29 | -moz-border-radius: 5px; 30 | border-radius: 5px; 31 | } 32 | .beer p { 33 | margin-top: 10px; 34 | } 35 | form.list-beers { 36 | display: block; 37 | position:absolute; 38 | bottom: 10px; 39 | padding-left: 18px; 40 | } 41 | .ta-search { 42 | width: 240px; 43 | } 44 | .guilds { 45 | text-align: center; 46 | } 47 | .guild-image { 48 | display: inline-block; 49 | margin: 10px 5px 5px 5px; 50 | } 51 | .sm-image { 52 | display: inline-block; 53 | margin: 10px 2px 0 2px; 54 | } 55 | .sm-icons { 56 | padding-left: 0; 57 | padding-right: 0; 58 | } 59 | .navbar-form { 60 | padding-top: 0; 61 | padding-bottom: 0; 62 | } 63 | ul.locations { 64 | list-style-type: none; 65 | padding-left: 0; 66 | } 67 | li.location { 68 | line-height: 1.5em; 69 | padding-bottom: 15px; 70 | } 71 | .loc-name { 72 | font-weight: bold; 73 | } 74 | .beer-name { 75 | margin-top: 0; 76 | margin-bottom: 5px; 77 | } 78 | .text-left { 79 | padding-left: 15px; 80 | } 81 | .ratings .form-group { 82 | margin-bottom: 0; 83 | } 84 | .hours { 85 | font-family: "Lato", Helvetica, Arial, sans-serif; 86 | font-size: 0.8em; 87 | line-height: 1.5em; 88 | margin: 0; 89 | white-space: pre-wrap; 90 | word-break: normal; 91 | border: none; 92 | padding-left: 0; 93 | } 94 | .beer-boxes { 95 | margin-bottom: 15px; 96 | } 97 | .checkbox-inline { 98 | margin-left: 0; 99 | padding-left: 30px; 100 | padding-bottom:20px; 101 | line-height: 1.5em; 102 | } 103 | .checkbox-inline + .checkbox-inline { 104 | margin-left: 0; 105 | } 106 | .loc-info { 107 | font-size: 0.9em; 108 | line-height: 1.3em; 109 | margin-bottom: 0; 110 | } 111 | h4.beer-name { 112 | font-size: 18px; 113 | } 114 | .brewery-img { 115 | margin: 15px auto; 116 | } 117 | .label-inline { 118 | padding-left: 0; 119 | } 120 | .beer-item { 121 | min-width: 225px; 122 | max-width: 262px; 123 | } 124 | .col-md-4 { 125 | padding-left: 10px; 126 | padding-right: 10px; 127 | } 128 | :focus + .input-group-btn .btn { 129 | border-color: #1abc9c; 130 | } 131 | @media (max-width: 767px) { 132 | .no-mobile { 133 | display: none; 134 | } 135 | } 136 | @media (min-width: 768px) and (max-width: 991px) { 137 | .no-mobile { 138 | display: none; 139 | } 140 | } 141 | .footer { 142 | padding-top: 20px; 143 | } 144 | .form-control:focus { 145 | border-color: transparent; 146 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "on-tapp", 3 | "description": "HRR2 Thesis Project", 4 | "version": "0.0.2", 5 | "private": false, 6 | "contributors": "Julie, 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/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', 'PredictionIO', 'Beer', 5 | function($scope, $stateParams, $location, Authentication, Ratings, StyleQuery, PredictionIO, Beer) { 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 | var userId = Authentication.user._id; 42 | 43 | $scope.ratings.$promise.then(function(data){ 44 | getPredition(userId); 45 | }); 46 | }; 47 | 48 | // Find existing Rating 49 | $scope.findOne = function() { 50 | $scope.rating = Ratings.get({ 51 | ratingId: $stateParams.ratingId 52 | }); 53 | 54 | $scope.rating.$promise.then(function(data) { 55 | getStars(data.stars); 56 | getRecommendations(data.styleName); 57 | getPredition(data.user._id); 58 | getBeerDetails(data.beerId); 59 | }); 60 | }; 61 | 62 | //an array to store number of stars 63 | $scope.stars = []; 64 | 65 | // get the number of stars 66 | var getStars = function(noOfStars){ 67 | // for (var i = 0; i < noOfStars; i++) { 68 | // $scope.stars.push(i); 69 | // } 70 | $scope.stars = noOfStars; 71 | }; 72 | 73 | // Find the beers in the same category 74 | var getRecommendations = function(styleName){ 75 | StyleQuery.getStyle(styleName).success(handleSuccess); 76 | }; 77 | 78 | $scope.itemScores = []; 79 | 80 | // get result for PreditionIO 81 | var getPredition = function(userId){ 82 | PredictionIO.getRecommendaton(userId).success(function(data, status){ 83 | console.log('this is the prediction data', data); 84 | Beer.getData(data.itemScores[0].item).success(function(data, status){ 85 | $scope.itemScores = [data.data]; 86 | }); 87 | }); 88 | }; 89 | 90 | $scope.beer = {}; 91 | 92 | var getBeerDetails = function(beerId){ 93 | Beer.getData(beerId).success(function(results, status) { 94 | $scope.beer = results.data || 'Request failed'; 95 | }); 96 | }; 97 | 98 | // an array to store recommendations 99 | $scope.recommendations = []; 100 | 101 | // pushing recommendations data from $http request 102 | var handleSuccess = function(data, status){ 103 | $scope.recommendations = data.data; 104 | }; 105 | } 106 | ]); 107 | -------------------------------------------------------------------------------- /public/modules/core/views/header.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 11 | 12 | 70 |
71 | -------------------------------------------------------------------------------- /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.update() should update a valid Rating', inject(function(Ratings) { 54 | // Define a sample Rating put data 55 | var sampleRatingPutData = new Ratings({ 56 | _id: '525cf20451979dea2c000001', 57 | name: 'New Rating' 58 | }); 59 | 60 | // Mock Rating in scope 61 | scope.rating = sampleRatingPutData; 62 | 63 | // Set PUT response 64 | $httpBackend.expectPUT(/ratings\/([0-9a-fA-F]{24})$/).respond(); 65 | 66 | // Run controller functionality 67 | scope.update(); 68 | $httpBackend.flush(); 69 | 70 | // Test URL location to new object 71 | expect($location.path()).toBe('/ratings/' + sampleRatingPutData._id); 72 | })); 73 | 74 | it('$scope.remove() should send a DELETE request with a valid ratingId and remove the Rating from the scope', inject(function(Ratings) { 75 | // Create new Rating object 76 | var sampleRating = new Ratings({ 77 | _id: '525a8422f6d0f87f0e407a33' 78 | }); 79 | 80 | // Create new Ratings array and include the Rating 81 | scope.ratings = [sampleRating]; 82 | 83 | // Set expected DELETE response 84 | $httpBackend.expectDELETE(/ratings\/([0-9a-fA-F]{24})$/).respond(204); 85 | 86 | // Run controller functionality 87 | scope.remove(sampleRating); 88 | $httpBackend.flush(); 89 | 90 | // Test array after successful delete 91 | expect(scope.ratings.length).toBe(0); 92 | })); 93 | }); 94 | }()); 95 | -------------------------------------------------------------------------------- /app/controllers/ratings.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // accessKey is required for PredictionIO 0.8.2+ 4 | var request = require('request'); 5 | var config = require('../../config/config'); 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | var mongoose = require('mongoose'), 11 | errorHandler = require('./errors.server.controller'), 12 | Rating = mongoose.model('Rating'), 13 | _ = require('lodash'); 14 | 15 | /** 16 | * Create a Rating 17 | */ 18 | exports.create = function(req, res) { 19 | var rating = new Rating(req.body); 20 | rating.user = req.user; 21 | 22 | Rating.findByBeerId(rating.beerId, function (err, beer) { 23 | 24 | var result; 25 | // Register a new user-to-item action 26 | request.post({ 27 | headers: {'content-type' : 'application/json'}, 28 | url: 'http://54.183.105.216:7070/events.json?accessKey=' + config.predictionio.api, 29 | body: JSON.stringify({ 30 | event: 'rate', 31 | entityType : 'user', 32 | entityId: rating.user, 33 | targetEntityType: 'item', 34 | targetEntityId: rating.beerId, 35 | properties : { 36 | rating : rating.stars 37 | }, 38 | eventTime: new Date().toISOString() 39 | }) 40 | }, function (error, response, body) { 41 | if (!error && response.statusCode === 200) { 42 | console.log(body); 43 | } 44 | }); 45 | 46 | if (beer.length){ 47 | rating = beer[0]; 48 | rating.stars += beer[0].stars; 49 | } 50 | 51 | rating.save(function(err) { 52 | if (err) { 53 | return res.status(400).send({ 54 | message: errorHandler.getErrorMessage(err) 55 | }); 56 | } else { 57 | res.jsonp(rating); 58 | } 59 | }); 60 | 61 | }); 62 | }; 63 | 64 | /** 65 | * Show the current Rating 66 | */ 67 | exports.read = function(req, res) { 68 | res.jsonp(req.rating); 69 | }; 70 | 71 | /** 72 | * Update a Rating 73 | */ 74 | exports.update = function(req, res) { 75 | var rating = req.rating ; 76 | 77 | rating = _.extend(rating , req.body); 78 | 79 | rating.save(function(err) { 80 | if (err) { 81 | return res.status(400).send({ 82 | message: errorHandler.getErrorMessage(err) 83 | }); 84 | } else { 85 | res.jsonp(rating); 86 | } 87 | }); 88 | }; 89 | 90 | /** 91 | * Delete an Rating 92 | */ 93 | exports.delete = function(req, res) { 94 | var rating = req.rating ; 95 | 96 | rating.remove(function(err) { 97 | if (err) { 98 | return res.status(400).send({ 99 | message: errorHandler.getErrorMessage(err) 100 | }); 101 | } else { 102 | res.jsonp(rating); 103 | } 104 | }); 105 | }; 106 | 107 | /** 108 | * List of Ratings 109 | */ 110 | exports.list = function(req, res) { 111 | Rating.find().sort('-created').populate('user', 'displayName').exec(function(err, ratings) { 112 | if (err) { 113 | return res.status(400).send({ 114 | message: errorHandler.getErrorMessage(err) 115 | }); 116 | } else { 117 | res.jsonp(ratings); 118 | } 119 | }); 120 | }; 121 | 122 | /** 123 | * Rating middleware 124 | */ 125 | exports.ratingByID = function(req, res, next, id) { 126 | Rating.findById(id).populate('user', 'displayName').exec(function(err, rating) { 127 | if (err) return next(err); 128 | if (! rating) return next(new Error('Failed to load Rating ' + id)); 129 | req.rating = rating ; 130 | next(); 131 | }); 132 | }; 133 | 134 | /** 135 | * Rating authorization middleware 136 | */ 137 | exports.hasAuthorization = function(req, res, next) { 138 | if (req.rating.user.id !== req.user.id) { 139 | return res.status(403).send('User is not authorized'); 140 | } 141 | next(); 142 | }; 143 | -------------------------------------------------------------------------------- /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 | 39 | {% for cssFile in cssFiles %}{% endfor %} 40 | 41 | 42 | 45 | 46 | 53 | 54 | 64 | 65 | 66 | 67 | 68 |
69 | {% block content %}{% endblock %} 70 |
71 | 72 | 73 | 76 | 77 | 78 | {% for jsFile in jsFiles %}{% endfor %} 79 | 80 | {% if process.env.NODE_ENV === 'development' %} 81 | 82 | 83 | {% endif %} 84 | 85 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /public/modules/beer/views/beer.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 |
    12 |
  • 13 | {{beer.abv}}% 14 | abv 15 |
  • 16 |
  • 17 | {{beer.ibu}} 18 | ibu 19 |
  • 20 |
  • 21 | {{beer.srmId}} 22 | N/A 23 | srm 24 |
  • 25 |
  • 26 | {{beer.og}} 27 | N/A 28 | og 29 |
  • 30 |
31 |
32 |
33 |
34 |
35 |
Name:
36 |
{{beer.name}}
37 |
38 |
39 |
Brewery:
40 | 43 |
44 |
45 |
Style:
46 |
47 | {{beer.style.name}} 48 |
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 |
69 | 70 |

You may also want to try:

71 | 72 |
73 |
74 |

{{recommendation.name}} - {{recommendation.category.name}}

75 | 76 | abvMax: {{recommendation.abvMax}}, abvMin: {{recommendation.abvMin}} | 77 | 78 | fgMax: {{recommendation.fgMax}}, fgMin: {{recommendation.fgMin}} | 79 | 80 | ibuMax: {{recommendation.ibuMax}}, ibuMin: {{recommendation.ibuMin}} 81 | 82 |
83 |
84 |
85 |
86 |
87 |
88 | -------------------------------------------------------------------------------- /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); 147 | -------------------------------------------------------------------------------- /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 | }()); 119 | -------------------------------------------------------------------------------- /public/modules/nearby/controllers/nearby.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('nearby').controller('NearbyController', ['$scope', 'uiGmapGoogleMapApi', 'Breweries', 'geolocation', 'uiGmapLogger', 'usSpinnerService', 4 | function($scope, uiGmapGoogleMapApi, Breweries, geolocation, uiGmapLogger, usSpinnerService) { 5 | 6 | // enable logging of google map info and error 7 | uiGmapLogger.doLog = true; 8 | 9 | $scope.breweries = []; // used to fetch data from brewerydb factory 10 | $scope.coords = {}; // user's current coordinates 11 | $scope.allMarkers = []; // array to store the brewery markers 12 | $scope.oldWindow = -1; // var to hold id of old info window 13 | 14 | // pushing breweries data from $http request and place markers 15 | var handleSuccess = function(data, status){ 16 | if (data.data){ 17 | $scope.breweries = data.data; 18 | placeMarker(); 19 | } 20 | usSpinnerService.stop('spinner-1'); //stop the spinner 21 | }; 22 | 23 | // marker for current coordinate 24 | var curLocationMarker = function(){ 25 | $scope.marker = { 26 | id: 'curLoc', 27 | coords: { 28 | latitude: $scope.coords.lat, 29 | longitude: $scope.coords.long, 30 | }, 31 | options: { 32 | title: 'You are here!' 33 | } 34 | }; 35 | }; 36 | 37 | var createMarker = function (i) { 38 | var name = $scope.breweries[i].brewery.name; 39 | var addr = ($scope.breweries[i].streetAddress !== undefined) ? $scope.breweries[i].streetAddress + '
' : ''; 40 | var phone = ($scope.breweries[i].phone !== undefined) ? $scope.breweries[i].phone + '
' : ''; 41 | var id = $scope.breweries[i].brewery.id; 42 | var dist = $scope.breweries[i].distance + ' miles away
'; 43 | var desc = '
' + 44 | name + '
' + dist + addr + phone + '
'; 45 | var ret = { 46 | id: i, 47 | coords : { 48 | latitude: $scope.breweries[i].latitude, 49 | longitude: $scope.breweries[i].longitude, 50 | }, 51 | options: { 52 | title: name, 53 | }, 54 | infoWindow: { 55 | content: desc 56 | }, 57 | icon: '/modules/nearby/images/beer-icon.png', 58 | showWindow: false 59 | }; 60 | ret.onClick = function() { 61 | if ($scope.oldWindow > -1) { 62 | $scope.allMarkers[$scope.oldWindow].showWindow = false; 63 | } 64 | $scope.oldWindow = ret.id; 65 | ret.showWindow = !ret.showWindow; 66 | }; 67 | 68 | return ret; 69 | }; 70 | 71 | // create markers for all breweries 72 | var placeMarker = function() { // places all the brewery markers 73 | var markers = []; 74 | for (var i = 0; i < $scope.breweries.length; i++) { 75 | markers.push(createMarker(i)); 76 | } 77 | $scope.allMarkers = markers; 78 | }; 79 | 80 | $scope.closeClick = function() { 81 | $scope.windowOptions.visible = false; 82 | }; 83 | 84 | $scope.getUserLocation = function(){ 85 | // function to access users geolocation coordinates, draw map and place markers 86 | geolocation.getLocation().then(function(data){ 87 | // set to san francisco by Default for Victor 88 | // $scope.coords = {lat:37.783973, long:-122.409100}; 89 | $scope.coords = {lat:data.coords.latitude, long:data.coords.longitude}; 90 | 91 | uiGmapGoogleMapApi.then(function(maps) { 92 | $scope.map = { center: { latitude: $scope.coords.lat, longitude: $scope.coords.long }, zoom: 12}; // initialize the Google map 93 | $scope.windowOptions = { 94 | visible: true 95 | }; 96 | 97 | curLocationMarker(); // add marker for current location 98 | Breweries.getData($scope.coords).success(handleSuccess); // get brewery data from factory 99 | }); 100 | }); 101 | }; 102 | } 103 | ]); 104 | -------------------------------------------------------------------------------- /public/modules/core/views/home.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |
9 |

Discover breweries around you & receive personalized recommendations based on your rated beers

10 |
11 |
12 |

OnTapp uses your geolocation and machine learning algorithms to find the perfect beer you’ll love.

13 |

14 | Discover Breweries Nearby » 15 |

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

Summary

25 |

Users can locate breweries within a 10 mile radius of their current location. They can rate the beers at those specific breweries and receive recommendations of other beers that are similar.

26 |
27 |
28 | Search 29 |

Problem

30 |

We all love beer, and we face an unprecedented variety of options. Where to find the beers we like? What should I drink next? OnTapp's goal is to solve these two problems.

31 |
32 |
33 | Ratings 34 |

Solution

35 |

We would like to discover new and interesting beers, not simply based on categories, but also our current location and personal taste preferences.

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

"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!"

46 |
47 |
48 | 49 |
50 | Ratings 51 |
52 |

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

53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 |
69 |
70 | 71 |
72 |
73 |
74 | 75 | 76 | 81 |
82 | 83 | 84 | -------------------------------------------------------------------------------- /public/modules/ratings/views/view-rating.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 |
    21 |
  • 22 | {{beer.abv}}% 23 | abv 24 |
  • 25 |
  • 26 | {{beer.ibu}} 27 | ibu 28 |
  • 29 | 30 |
  • 31 | {{beer.srmId}} 32 | N/A 33 | srm 34 |
  • 35 | 36 |
  • 37 | {{beer.og}} 38 | N/A 39 | og 40 |
  • 41 |
42 |
43 |
44 |
45 |
Name:
46 |
{{beer.name}}
47 |
48 |
49 |
Brewery:
50 | 53 |
54 |
55 |
Style:
56 | 59 |
60 |
61 |
Availability:
62 |
{{beer.available.description}}
63 |
64 |
65 |
Serving:
66 |
{{beer.glass.name}}
67 |
68 |
69 |

{{beer.description}}

70 |
71 |
72 | Food Pairings:
73 |

{{beer.foodPairings}}

74 |
75 |
76 |
77 |
78 | 79 | 80 |
81 | 82 | 83 | Lasted voted on 84 | 85 | by 86 | 87 | 88 | 89 |
90 | 91 |

Our customized recommendation:

92 | 93 |
94 |
95 |

{{itemScore.name}}

96 |
97 |
98 | 99 |

You may also likedislike:

100 | 101 |
102 |
103 |

{{recommendation.name}}

104 | 105 | {{recommendation.category.name}} 106 | 107 |
108 |
109 | 110 |
111 |
112 | -------------------------------------------------------------------------------- /public/modules/brewery/controllers/brewery.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('nearby').controller('BreweryController', ['$scope', 'Brewery', '$stateParams', 'Ratings', '$location', 'Core', 4 | function($scope, Brewery, $stateParams, Ratings, $location, Core) { 5 | // Brewery controller logic 6 | $scope.breweryId = $stateParams.breweryId; 7 | 8 | $scope.brewery = []; 9 | $scope.socialMedia = []; 10 | var socialMediaArr = [1,2,3,8,10,14,15,16]; 11 | // send the brewery id and get the brewery information 12 | Brewery.getData($scope.breweryId).success(function(data, status) { 13 | $scope.brewery = data.data || 'Request failed'; 14 | 15 | // if there are locations, abbreviate the states 16 | if ($scope.brewery.locations !== undefined) { 17 | for (var i = 0; i < $scope.brewery.locations.length; i++) { 18 | $scope.brewery.locations[i].region = Core.abbrState($scope.brewery.locations[i].region); 19 | if ($scope.brewery.locations[i].phone.indexOf('-') === -1) { 20 | $scope.brewery.locations[i].phone = Core.fixPhone($scope.brewery.locations[i].phone); 21 | } 22 | } 23 | } 24 | 25 | // only get social media info for certain sites 26 | if ($scope.brewery.socialAccounts !== undefined) { 27 | for (var j = 0; j < $scope.brewery.socialAccounts.length; j++) { 28 | var tempSocial = $scope.brewery.socialAccounts[j]; 29 | if (socialMediaArr.indexOf(tempSocial.socialMediaId) > -1) { 30 | $scope.socialMedia.push(tempSocial); 31 | } 32 | } 33 | } 34 | }); 35 | 36 | $scope.beers = []; 37 | $scope.availabilityGroups = []; 38 | // send the brewery id and get all the beers 39 | Brewery.getBeersData($scope.breweryId).success(function(data, status) { 40 | $scope.beers = data.data || 'Request failed'; 41 | $scope.availabilityGroups = uniqueItems($scope.beers); 42 | }); 43 | 44 | $scope.useAvailability = []; 45 | $scope.filterBeer = function() { 46 | return function(p) { 47 | if (($scope.useAvailability.length === 0) || ($scope.useAvailability.indexOf(true) === -1)) { 48 | return true; 49 | } 50 | for (var i in $scope.useAvailability) { 51 | if (p.availableId === $scope.availabilityGroups[i].availableId && $scope.useAvailability[i]) { 52 | return true; 53 | } 54 | } 55 | }; 56 | }; 57 | 58 | // handle the stars rating 59 | $scope.rate = 0; 60 | $scope.max = 5; 61 | $scope.isReadonly = false; 62 | 63 | // hoveing over on ratings stars 64 | $scope.hoveringOver = function(value) { 65 | $scope.overStar = value; 66 | $scope.percent = 100 * (value / $scope.max); 67 | }; 68 | 69 | // set the ratings stars 70 | $scope.ratingStates = [ 71 | {stateOn: 'glyphicon-star', stateOff: 'glyphicon-star-empty'}, 72 | ]; 73 | 74 | // Create new Rating 75 | $scope.create = function(index) { 76 | 77 | // Create new Rating object 78 | var rating = new Ratings ({ 79 | beerId: $scope.beers[index].id, 80 | name: $scope.beers[index].name, 81 | stars: this.rate, 82 | styleName: $scope.beers[index].style.name 83 | }); 84 | 85 | // Redirect after save 86 | rating.$save(function(response) { 87 | // console.log('this is the response', response); 88 | // $location.path('ratings/' + response._id); 89 | $location.path('beer/' + response.beerId); 90 | 91 | // Clear form fields 92 | $scope.name = ''; 93 | }, function(errorResponse) { 94 | $scope.error = errorResponse.data.message; 95 | }); 96 | }; 97 | 98 | var uniqueItems = function(data) { 99 | var result = []; 100 | for (var i = 0; i < data.length; i++) { 101 | var idx = data[i].availableId; 102 | if (idx !== undefined) { 103 | var aName = data[i].available.name; 104 | if (Core.findIndexByKeyValue(result, 'availableId', idx) === -1) { 105 | result.push({'availableId': idx, 'availableName': aName}); 106 | } 107 | } 108 | } 109 | result.sort(function(a, b) { 110 | if (a.availableId > b.availableId) { 111 | return 1; 112 | } 113 | if (a.availableId < b.availableId) { 114 | return -1; 115 | } 116 | return 0; // a must be equal to b 117 | }); 118 | return result; 119 | }; 120 | } 121 | ]); 122 | -------------------------------------------------------------------------------- /public/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------------------------------- 3 | 4 | // Utilities 5 | @import "../lib/bootstrap/less/mixins/hide-text.less"; 6 | @import "../lib/bootstrap/less/mixins/opacity.less"; 7 | @import "../lib/bootstrap/less/mixins/image.less"; 8 | @import "../lib/bootstrap/less/mixins/labels.less"; 9 | @import "../lib/bootstrap/less/mixins/reset-filter.less"; 10 | @import "../lib/bootstrap/less/mixins/resize.less"; 11 | @import "../lib/bootstrap/less/mixins/responsive-visibility.less"; 12 | @import "../lib/bootstrap/less/mixins/size.less"; 13 | @import "../lib/bootstrap/less/mixins/tab-focus.less"; 14 | @import "../lib/bootstrap/less/mixins/text-emphasis.less"; 15 | @import "../lib/bootstrap/less/mixins/text-overflow.less"; 16 | @import "../lib/bootstrap/less/mixins/vendor-prefixes.less"; 17 | 18 | // Components 19 | @import "../lib/bootstrap/less/mixins/alerts.less"; 20 | @import "../lib/bootstrap/less/mixins/buttons.less"; 21 | @import "../lib/bootstrap/less/mixins/panels.less"; 22 | @import "../lib/bootstrap/less/mixins/pagination.less"; 23 | @import "../lib/bootstrap/less/mixins/list-group.less"; 24 | @import "../lib/bootstrap/less/mixins/nav-divider.less"; 25 | @import "../lib/bootstrap/less/mixins/forms.less"; 26 | @import "../lib/bootstrap/less/mixins/progress-bar.less"; 27 | @import "../lib/bootstrap/less/mixins/table-row.less"; 28 | 29 | // Skins 30 | @import "../lib/bootstrap/less/mixins/background-variant.less"; 31 | @import "../lib/bootstrap/less/mixins/border-radius.less"; 32 | @import "../lib/bootstrap/less/mixins/gradients.less"; 33 | 34 | // Layout 35 | @import "../lib/bootstrap/less/mixins/clearfix.less"; 36 | @import "../lib/bootstrap/less/mixins/center-block.less"; 37 | @import "../lib/bootstrap/less/mixins/nav-vertical-align.less"; 38 | @import "../lib/bootstrap/less/mixins/grid-framework.less"; 39 | @import "../lib/bootstrap/less/mixins/grid.less"; 40 | 41 | // fontawesome 42 | // -------------------------- 43 | 44 | .fa-icon() { 45 | display: inline-block; 46 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 47 | font-size: inherit; // can't have font-size inherit on line above, so need to override 48 | text-rendering: auto; // optimizelegibility throws things off #1094 49 | -webkit-font-smoothing: antialiased; 50 | -moz-osx-font-smoothing: grayscale; 51 | } 52 | 53 | .fa-icon-rotate(@degrees, @rotation) { 54 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 55 | -webkit-transform: rotate(@degrees); 56 | -ms-transform: rotate(@degrees); 57 | transform: rotate(@degrees); 58 | } 59 | 60 | .fa-icon-flip(@horiz, @vert, @rotation) { 61 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 62 | -webkit-transform: scale(@horiz, @vert); 63 | -ms-transform: scale(@horiz, @vert); 64 | transform: scale(@horiz, @vert); 65 | } 66 | 67 | 68 | // flat-ui 69 | 70 | // Mixins 71 | // -------------------------------------------------- 72 | 73 | // Utilities 74 | @import "../lib/flat-ui/less/mixins/hide-text.less"; 75 | @import "../lib/flat-ui/less/mixins/opacity.less"; 76 | @import "../lib/flat-ui/less/mixins/image.less"; 77 | @import "../lib/flat-ui/less/mixins/reset-filter.less"; 78 | @import "../lib/flat-ui/less/mixins/resize.less"; 79 | @import "../lib/flat-ui/less/mixins/responsive-visibility.less"; 80 | @import "../lib/flat-ui/less/mixins/size.less"; 81 | @import "../lib/flat-ui/less/mixins/tab-focus.less"; 82 | @import "../lib/flat-ui/less/mixins/text-emphasis.less"; 83 | @import "../lib/flat-ui/less/mixins/text-overflow.less"; 84 | @import "../lib/flat-ui/less/mixins/vendor-prefixes.less"; 85 | @import "../lib/flat-ui/less/mixins/background-clip.less"; 86 | 87 | // Components 88 | @import "../lib/flat-ui/less/mixins/buttons.less"; 89 | @import "../lib/flat-ui/less/mixins/select.less"; 90 | @import "../lib/flat-ui/less/mixins/pagination.less"; 91 | @import "../lib/flat-ui/less/mixins/nav-divider.less"; 92 | @import "../lib/flat-ui/less/mixins/forms.less"; 93 | @import "../lib/flat-ui/less/mixins/switches.less"; 94 | @import "../lib/flat-ui/less/mixins/pallets.less"; 95 | 96 | // Skins 97 | @import "../lib/flat-ui/less/mixins/background-variant.less"; 98 | @import "../lib/flat-ui/less/mixins/border-radius.less"; 99 | @import "../lib/flat-ui/less/mixins/gradients.less"; 100 | 101 | // Layout 102 | @import "../lib/flat-ui/less/mixins/clearfix.less"; 103 | @import "../lib/flat-ui/less/mixins/center-block.less"; 104 | @import "../lib/flat-ui/less/mixins/navbar-vertical-align.less"; 105 | @import "../lib/flat-ui/less/mixins/grid.less"; 106 | -------------------------------------------------------------------------------- /public/modules/ratings/views/list-ratings.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
    14 |
  • 15 | {{itemScore.abv}}% 16 | abv 17 |
  • 18 |
  • 19 | {{itemScore.ibu}} 20 | ibu 21 |
  • 22 |
  • 23 | {{itemScore.srmId}} 24 | N/A 25 | srm 26 |
  • 27 |
  • 28 | {{itemScore.og}} 29 | N/A 30 | og 31 |
  • 32 |
33 |
34 |
35 |
36 |
37 |
Name:
38 |
39 | {{itemScore.name}} 40 |
41 |
42 |
43 |
Brewery:
44 | 47 |
48 |
49 |
Style:
50 |
51 | {{itemScore.style.name}} 52 |
53 |
54 |
55 |
Availability:
56 |
{{itemScore.available.description}}
57 |
58 |
59 |
Serving:
60 |
{{itemScore.glass.name}}
61 |
62 |
63 |

{{itemScore.description}}

64 |
65 |
66 | Food Pairings:
67 |

{{itemScore.foodPairings}}

68 |
69 |
70 |
71 | 72 |

You may also want to try:

73 | 74 |
75 |
76 |

{{recommendation.name}}

77 | 78 | {{recommendation.category.name}} 79 | 80 |
81 |
82 |
83 |
84 |
85 | 86 | 87 |

Top 10 rated beers

88 | 100 |
101 | No Ratings yet, why don't you create one? 102 |
103 |
104 |
105 | -------------------------------------------------------------------------------- /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 | /* Lastly, register catch-all route for html5mode on AngularJs */ 125 | var core = require('../app/controllers/core.server.controller'); 126 | app.get('/*', core.index); 127 | 128 | // 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. 129 | app.use(function(err, req, res, next) { 130 | // If the error object doesn't exists 131 | if (!err) return next(); 132 | 133 | // Log it 134 | console.error(err.stack); 135 | 136 | // Error page 137 | res.status(500).render('500', { 138 | error: err.stack 139 | }); 140 | }); 141 | 142 | // Assume 404 since no middleware responded 143 | app.use(function(req, res) { 144 | res.status(404).render('404', { 145 | url: req.originalUrl, 146 | error: 'Not Found' 147 | }); 148 | }); 149 | 150 | if (process.env.NODE_ENV === 'secure') { 151 | // Load SSL key and certificate 152 | var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8'); 153 | var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8'); 154 | 155 | // Create HTTPS Server 156 | var httpsServer = https.createServer({ 157 | key: privateKey, 158 | cert: certificate 159 | }, app); 160 | 161 | // Return HTTPS server instance 162 | return httpsServer; 163 | } 164 | 165 | // Return Express server instance 166 | return app; 167 | }; 168 | -------------------------------------------------------------------------------- /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/**/*.less', 'public/less/*.less'], 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', 'less'], 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: 'public/modules/**/*.less' 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 | nodemon: { 79 | dev: { 80 | script: 'server.js', 81 | options: { 82 | nodeArgs: ['--debug'], 83 | ext: 'js,html', 84 | watch: watchFiles.serverViews.concat(watchFiles.serverJS) 85 | } 86 | } 87 | }, 88 | 'node-inspector': { 89 | custom: { 90 | options: { 91 | 'web-port': 1337, 92 | 'web-host': 'localhost', 93 | 'debug-port': 5858, 94 | 'save-live-edit': true, 95 | 'no-preload': true, 96 | 'stack-trace-limit': 50, 97 | 'hidden': [] 98 | } 99 | } 100 | }, 101 | ngAnnotate: { 102 | production: { 103 | files: { 104 | 'public/dist/application.js': '<%= applicationJavaScriptFiles %>' 105 | } 106 | } 107 | }, 108 | concurrent: { 109 | default: ['nodemon', 'watch'], 110 | debug: ['nodemon', 'watch', 'node-inspector'], 111 | options: { 112 | logConcurrentOutput: true, 113 | limit: 10 114 | } 115 | }, 116 | env: { 117 | test: { 118 | NODE_ENV: 'test' 119 | }, 120 | secure: { 121 | NODE_ENV: 'secure' 122 | } 123 | }, 124 | mochaTest: { 125 | src: watchFiles.mochaTests, 126 | options: { 127 | reporter: 'spec', 128 | require: 'server.js' 129 | } 130 | }, 131 | karma: { 132 | unit: { 133 | configFile: 'karma.conf.js' 134 | } 135 | }, 136 | less: { 137 | production: { 138 | options: { 139 | paths: ['public/less'], 140 | cleancss: true, 141 | compress: true 142 | }, 143 | files: { 144 | 'public/dist/application.min.css': 'public/less/application.less' 145 | } 146 | }, 147 | development: { 148 | options: { 149 | sourceMap: false, 150 | ieCompat:true, 151 | dumpLineNumbers:true 152 | }, 153 | files: { 154 | 'public/application.min.css': 'public/less/application.less' 155 | } 156 | } 157 | } 158 | }); 159 | 160 | // Load NPM tasks 161 | require('load-grunt-tasks')(grunt); 162 | 163 | // Making grunt default to force in order not to break the project. 164 | grunt.option('force', true); 165 | 166 | // A Task for loading the configuration object 167 | grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() { 168 | var init = require('./config/init')(); 169 | var config = require('./config/config'); 170 | 171 | grunt.config.set('applicationJavaScriptFiles', config.assets.js); 172 | grunt.config.set('applicationCSSFiles', config.assets.css); 173 | }); 174 | 175 | // Default task(s). 176 | grunt.registerTask('default', ['lint', 'concurrent:default', 'build']); 177 | 178 | // Debug task. 179 | grunt.registerTask('debug', ['lint', 'concurrent:debug']); 180 | 181 | // Secure task(s). 182 | grunt.registerTask('secure', ['env:secure', 'lint', 'concurrent:default']); 183 | 184 | // Lint task(s). 185 | grunt.registerTask('lint', ['jshint', 'csslint']); 186 | 187 | // Build task(s). 188 | // grunt.registerTask('build', ['lint', 'loadConfig', 'ngAnnotate', 'uglify', 'cssmin']); 189 | grunt.registerTask('build', ['lint', 'loadConfig', 'ngAnnotate', 'uglify', 'less']); 190 | 191 | // Test task. 192 | grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']); 193 | }; 194 | -------------------------------------------------------------------------------- /public/less/application.less: -------------------------------------------------------------------------------- 1 | // Core variables and mixins 2 | @import "variables.less"; 3 | @import "mixins.less"; 4 | 5 | // Reset and dependencies 6 | @import "../lib/bootstrap/less/normalize.less"; 7 | @import "../lib/bootstrap/less/print.less"; 8 | @import "../lib/bootstrap/less/glyphicons.less"; 9 | 10 | // Core CSS 11 | @import "../lib/bootstrap/less/scaffolding.less"; 12 | @import "../lib/bootstrap/less/type.less"; 13 | @import "../lib/bootstrap/less/code.less"; 14 | @import "../lib/bootstrap/less/grid.less"; 15 | @import "../lib/bootstrap/less/tables.less"; 16 | @import "../lib/bootstrap/less/forms.less"; 17 | @import "../lib/bootstrap/less/buttons.less"; 18 | 19 | // Components 20 | @import "../lib/bootstrap/less/component-animations.less"; 21 | @import "../lib/bootstrap/less/dropdowns.less"; 22 | @import "../lib/bootstrap/less/button-groups.less"; 23 | @import "../lib/bootstrap/less/input-groups.less"; 24 | @import "../lib/bootstrap/less/navs.less"; 25 | @import "../lib/bootstrap/less/navbar.less"; 26 | @import "../lib/bootstrap/less/breadcrumbs.less"; 27 | @import "../lib/bootstrap/less/pagination.less"; 28 | @import "../lib/bootstrap/less/pager.less"; 29 | @import "../lib/bootstrap/less/labels.less"; 30 | @import "../lib/bootstrap/less/badges.less"; 31 | @import "../lib/bootstrap/less/jumbotron.less"; 32 | @import "../lib/bootstrap/less/thumbnails.less"; 33 | @import "../lib/bootstrap/less/alerts.less"; 34 | @import "../lib/bootstrap/less/progress-bars.less"; 35 | @import "../lib/bootstrap/less/media.less"; 36 | @import "../lib/bootstrap/less/list-group.less"; 37 | @import "../lib/bootstrap/less/panels.less"; 38 | @import "../lib/bootstrap/less/responsive-embed.less"; 39 | @import "../lib/bootstrap/less/wells.less"; 40 | @import "../lib/bootstrap/less/close.less"; 41 | 42 | // Components w/ JavaScript 43 | @import "../lib/bootstrap/less/modals.less"; 44 | @import "../lib/bootstrap/less/tooltip.less"; 45 | @import "../lib/bootstrap/less/popovers.less"; 46 | @import "../lib/bootstrap/less/carousel.less"; 47 | 48 | // Utility classes 49 | @import "../lib/bootstrap/less/utilities.less"; 50 | @import "../lib/bootstrap/less/responsive-utilities.less"; 51 | 52 | // fontawesome 53 | // @import "../lib/fontawesome/less/variables.less"; 54 | // @import "../lib/fontawesome/less/mixins.less"; 55 | @import "../lib/fontawesome/less/path.less"; 56 | @import "../lib/fontawesome/less/core.less"; 57 | @import "../lib/fontawesome/less/larger.less"; 58 | @import "../lib/fontawesome/less/fixed-width.less"; 59 | @import "../lib/fontawesome/less/list.less"; 60 | @import "../lib/fontawesome/less/bordered-pulled.less"; 61 | @import "../lib/fontawesome/less/spinning.less"; 62 | @import "../lib/fontawesome/less/rotated-flipped.less"; 63 | @import "../lib/fontawesome/less/stacked.less"; 64 | @import "../lib/fontawesome/less/icons.less"; 65 | 66 | // 67 | // Flat UI main stylesheet that aggregates all modules 68 | // -------------------------------------------------- 69 | 70 | // Loading config with variables (changing them leads to changing a color scheme) 71 | // @import "../lib/flat-ui/less/variables"; 72 | 73 | // Utility mixins for greater good 74 | // @import "../lib/flat-ui/less/mixins"; 75 | 76 | // Loading custom fonts 77 | //@import url("https://fonts.googleapis.com/css?family=Lato:400,700,700italic,900,400italic,300"); 78 | @import "../lib/flat-ui/less/modules/local-fonts.less"; 79 | @import "../lib/flat-ui/less/modules/glyphicons.less"; 80 | 81 | // Modules 82 | @import "../lib/flat-ui/less/modules/scaffolding.less"; 83 | @import "../lib/flat-ui/less/modules/type.less"; 84 | @import "../lib/flat-ui/less/modules/code.less"; 85 | @import "../lib/flat-ui/less/modules/thumbnails.less"; 86 | @import "../lib/flat-ui/less/modules/buttons.less"; 87 | @import "../lib/flat-ui/less/modules/button-groups.less"; 88 | @import "../lib/flat-ui/less/modules/forms.less"; 89 | @import "../lib/flat-ui/less/modules/input-groups.less"; 90 | @import "../lib/flat-ui/less/modules/radiocheck.less"; 91 | @import "../lib/flat-ui/less/modules/tagsinput.less"; 92 | @import "../lib/flat-ui/less/modules/typeahead.less"; 93 | @import "../lib/flat-ui/less/modules/progress-bars.less"; 94 | @import "../lib/flat-ui/less/modules/slider.less"; 95 | @import "../lib/flat-ui/less/modules/pager.less"; 96 | // @import "../lib/flat-ui/less/modules/pagination.less"; 97 | @import "../lib/flat-ui/less/modules/tooltip.less"; 98 | @import "../lib/flat-ui/less/modules/dropdowns.less"; 99 | @import "../lib/flat-ui/less/modules/select.less"; 100 | @import "../lib/flat-ui/less/modules/tiles.less"; 101 | @import "../lib/flat-ui/less/modules/navbar.less"; 102 | @import "../lib/flat-ui/less/modules/switch.less"; 103 | @import "../lib/flat-ui/less/modules/share.less"; 104 | @import "../lib/flat-ui/less/modules/video.less"; 105 | @import "../lib/flat-ui/less/modules/todo-list.less"; 106 | 107 | // Examples 108 | @import "../lib/flat-ui/less/modules/palette.less"; 109 | @import "../lib/flat-ui/less/modules/login.less"; 110 | @import "../lib/flat-ui/less/modules/footer.less"; 111 | 112 | // Spaces 113 | @import "../lib/flat-ui/less/spaces.less"; 114 | 115 | 116 | // these are your LESS files 117 | 118 | // core modules 119 | @import "../modules/core/css/angular-ui.less"; 120 | @import "../modules/core/css/core.less"; 121 | @import "../modules/core/css/generic.less"; 122 | @import "../modules/core/css/justified-nav.less"; 123 | @import "../modules/core/css/navbar-static-top.less"; 124 | @import "../modules/core/css/sticky-footer.less"; 125 | // nearby modules 126 | @import "../modules/nearby/css/map.less"; 127 | // users modules 128 | @import "../modules/users/css/users.less"; 129 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------