├── .bowerrc ├── .csslintrc ├── .gitignore ├── .jshintrc ├── LICENSE ├── LICENSE.md ├── Procfile ├── README.md ├── app ├── controllers │ ├── core.server.controller.js │ ├── event.server.controller.js │ └── users.server.controller.js ├── models │ ├── event.js │ └── user.server.model.js ├── routes │ ├── core.server.routes.js │ ├── events.server.routes.js │ └── users.server.routes.js ├── tests │ ├── controllers │ │ └── event.server.controller.spec.js │ └── models │ │ └── event.spec.js └── views │ ├── 404.server.view.html │ ├── 500.server.view.html │ ├── index.server.view.html │ └── layout.server.view.html ├── bower.json ├── config ├── config.js ├── env │ ├── all.js │ ├── development.js │ ├── production.js │ └── test.js ├── express.js ├── init.js ├── passport.js └── strategies │ └── twitter.js ├── gruntfile.js ├── karma.conf.js ├── package.json ├── public ├── application.js ├── config.js ├── dist │ ├── application.js │ ├── application.min.css │ └── application.min.js ├── humans.txt ├── modules │ ├── core │ │ ├── config │ │ │ └── core.client.routes.js │ │ ├── controllers │ │ │ ├── header.client.controller.js │ │ │ └── home.client.controller.js │ │ ├── core.client.module.js │ │ ├── css │ │ │ └── core.css │ │ ├── img │ │ │ ├── brand │ │ │ │ ├── favicon.ico │ │ │ │ └── logo.png │ │ │ └── loaders │ │ │ │ └── loader.gif │ │ ├── services │ │ │ └── menus.client.service.js │ │ ├── tests │ │ │ ├── header.client.controller.test.js │ │ │ └── home.client.controller.test.js │ │ └── views │ │ │ ├── header.client.view.html │ │ │ └── home.client.view.html │ ├── ratings │ │ ├── config │ │ │ └── ratings.routes.js │ │ ├── controllers │ │ │ ├── event.create.controller.js │ │ │ ├── event.details.controller.js │ │ │ └── event.list.controller.js │ │ ├── css │ │ │ └── ratings.css │ │ ├── ratings.client.module.js │ │ ├── services │ │ │ └── events.service.js │ │ ├── tests │ │ │ ├── event.create.controller.spec.js │ │ │ ├── event.details.controller.spec.js │ │ │ ├── event.list.controller.spec.js │ │ │ └── events.service.spec.js │ │ └── views │ │ │ ├── event.create.html │ │ │ ├── event.details.html │ │ │ └── event.list.html │ └── users │ │ ├── config │ │ ├── users.client.config.js │ │ └── users.client.routes.js │ │ ├── controllers │ │ ├── authentication.client.controller.js │ │ └── settings.client.controller.js │ │ ├── css │ │ └── users.css │ │ ├── img │ │ └── buttons │ │ │ ├── facebook.png │ │ │ ├── google.png │ │ │ ├── linkedin.png │ │ │ └── twitter.png │ │ ├── services │ │ ├── authentication.client.service.js │ │ └── users.client.service.js │ │ ├── tests │ │ └── authentication.client.controller.test.js │ │ ├── users.client.module.js │ │ └── views │ │ ├── settings │ │ ├── change-password.client.view.html │ │ ├── edit-profile.client.view.html │ │ └── social-accounts.client.view.html │ │ ├── signin.client.view.html │ │ └── signup.client.view.html └── robots.txt └── server.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/lib" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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": false, // 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 | "spyOn" 39 | ], 40 | "indent": 4, // Specify indentation spacing 41 | "devel": true, // Allow development statements e.g. `console.log();`. 42 | "noempty": true // Prohibit use of empty blocks. 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nathan Taylor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: ./node_modules/.bin/forever -m 5 server.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Important Note 2 | 3 | Due to changes in node after v6.5 running `npm install` will fail. *However*, it is still possible to run this project. 4 | 5 | 1. Start by installing `n` which is a node version manager. `sudo npm install n` 6 | 2. Set the version of node for your project by calling `n 6.5.0` 7 | 3. Run `npm install` 8 | 9 | ## Mean JS 10 | 11 | [![MEAN.JS Logo](http://meanjs.org/img/logo-small.png)](http://meanjs.org/) 12 | 13 | [![Build Status](https://travis-ci.org/meanjs/mean.svg?branch=master)](https://travis-ci.org/meanjs/mean) 14 | [![Dependencies Status](https://david-dm.org/meanjs/mean.svg)](https://david-dm.org/meanjs/mean) 15 | 16 | MEAN.JS is a full-stack JavaScript open-source solution, which provides a solid starting point for [MongoDB](http://www.mongodb.org/), [Node.js](http://www.nodejs.org/), [Express](http://expressjs.com/), and [AngularJS](http://angularjs.org/) based applications. The idea is to solve the common issues with connecting those frameworks, build a robust framework to support daily development needs, and help developers use better practices while working with popular JavaScript components. 17 | 18 | ## Before You Begin 19 | Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application: 20 | * MongoDB - Go through [MongoDB Official Website](http://mongodb.org/) and proceed to their [Official Manual](http://docs.mongodb.org/manual/), which should help you understand NoSQL and MongoDB better. 21 | * Express - The best way to understand express is through its [Official Website](http://expressjs.com/), particularly [The Express Guide](http://expressjs.com/guide.html); you can also go through this [StackOverflow Thread](http://stackoverflow.com/questions/8144214/learning-express-for-node-js) for more resources. 22 | * AngularJS - Angular's [Official Website](http://angularjs.org/) is a great starting point. You can also use [Thinkster Popular Guide](http://www.thinkster.io/), and the [Egghead Videos](https://egghead.io/). 23 | * Node.js - Start by going through [Node.js Official Website](http://nodejs.org/) and this [StackOverflow Thread](http://stackoverflow.com/questions/2353818/how-do-i-get-started-with-node-js), which should get you going with the Node.js platform in no time. 24 | 25 | 26 | ## Prerequisites 27 | Make sure you have installed all these prerequisites on your development machine. 28 | * Node.js - [Download & Install Node.js](http://www.nodejs.org/download/) and the npm package manager, if you encounter any problems, you can also use this [Github Gist](https://gist.github.com/isaacs/579814) to install Node.js. 29 | * MongoDB - [Download & Install MongoDB](http://www.mongodb.org/downloads), and make sure it's running on the default port (27017). 30 | * Bower - You're going to use the [Bower Package Manager](http://bower.io/) to manage your front-end packages, in order to install it make sure you've installed Node.js and npm, then install bower globally using npm: 31 | 32 | ``` 33 | $ npm install -g bower 34 | ``` 35 | 36 | * Grunt - You're going to use the [Grunt Task Runner](http://gruntjs.com/) to automate your development process, in order to install it make sure you've installed Node.js and npm, then install grunt globally using npm: 37 | 38 | ``` 39 | $ sudo npm install -g grunt-cli 40 | ``` 41 | 42 | ## Downloading MEAN.JS 43 | There are several ways you can get the MEAN.JS boilerplate: 44 | 45 | ### Yo Generator 46 | The recommended way would be to use the [Official Yo Generator](http://meanjs.org/generator.html) which will generate the latest stable copy of the MEAN.JS boilerplate and supplies multiple sub-generators to ease your daily development cycles. 47 | 48 | ### Cloning The GitHub Repository 49 | You can also use Git to directly clone the MEAN.JS repository: 50 | ``` 51 | $ git clone https://github.com/meanjs/mean.git meanjs 52 | ``` 53 | This will clone the latest version of the MEAN.JS repository to a **meanjs** folder. 54 | 55 | ### Downloading The Repository Zip File 56 | Another way to use the MEAN.JS boilerplate is to download a zip copy from the [master branch on github](https://github.com/meanjs/mean/archive/master.zip). You can also do this using `wget` command: 57 | ``` 58 | $ wget https://github.com/meanjs/mean/archive/master.zip -O meanjs.zip; unzip meanjs.zip; rm meanjs.zip 59 | ``` 60 | Don't forget to rename **mean-master** after your project name. 61 | 62 | ## Quick Install 63 | Once you've downloaded the boilerplate and installed all the prerequisites, you're just a few steps away from starting to develop you MEAN application. 64 | 65 | The first thing you should do is install the Node.js dependencies. The boilerplate comes pre-bundled with a package.json file that contains the list of modules you need to start your application, to learn more about the modules installed visit the NPM & Package.json section. 66 | 67 | To install Node.js dependencies you're going to use npm again, in the application folder run this in the command-line: 68 | 69 | ``` 70 | $ npm install 71 | ``` 72 | 73 | This command does a few things: 74 | * First it will install the dependencies needed for the application to run. 75 | * If you're running in a development environment, it will then also install development dependencies needed for testing and running your application. 76 | * Finally, when the install process is over, npm will initiate a bower installcommand to install all the front-end modules needed for the application 77 | 78 | ## Running Your Application 79 | After the install process is over, you'll be able to run your application using Grunt, just run grunt default task: 80 | 81 | ``` 82 | $ grunt 83 | ``` 84 | 85 | Your application should run on the 3000 port so in your browser just go to [http://localhost:3000](http://localhost:3000) 86 | 87 | That's it! your application should be running by now, to proceed with your development check the other sections in this documentation. 88 | If you encounter any problem try the Troubleshooting section. 89 | 90 | ## Getting Started With MEAN.JS 91 | You have your application running but there are a lot of stuff to understand, we recommend you'll go over the [Offical Documentation](http://meanjs.org/docs.html). 92 | In the docs we'll try to explain both general concepts of MEAN components and give you some guidelines to help you improve your development procees. We tried covering as many aspects as possible, and will keep update it by your request, you can also help us develop the documentation better by checking out the *gh-pages* branch of this repository. 93 | 94 | ## Community 95 | * Use to [Offical Website](http://meanjs.org) to learn about changes and the roadmap. 96 | * Join #meanjs on freenode. 97 | * Discuss it in the new [Google Group](https://groups.google.com/d/forum/meanjs) 98 | * Ping us on [Twitter](http://twitter.com/meanjsorg) and [Facebook](http://facebook.com/meanjs) 99 | 100 | ## Live Example 101 | Browse the live MEAN.JS example on [http://meanjs.herokuapp.com](http://meanjs.herokuapp.com). 102 | 103 | ## Credits 104 | Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/) 105 | The MEAN name was coined by [Valeri Karpov](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and) 106 | 107 | ## License 108 | (The MIT License) 109 | 110 | Permission is hereby granted, free of charge, to any person obtaining 111 | a copy of this software and associated documentation files (the 112 | 'Software'), to deal in the Software without restriction, including 113 | without limitation the rights to use, copy, modify, merge, publish, 114 | distribute, sublicense, and/or sell copies of the Software, and to 115 | permit persons to whom the Software is furnished to do so, subject to 116 | the following conditions: 117 | 118 | The above copyright notice and this permission notice shall be 119 | included in all copies or substantial portions of the Software. 120 | 121 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 122 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 123 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 124 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 125 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 126 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 127 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 128 | -------------------------------------------------------------------------------- /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 | }); 10 | }; -------------------------------------------------------------------------------- /app/controllers/event.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | EventModel = mongoose.model('Event'); 5 | 6 | var sendBackData = function(res, err, data){ 7 | if(err){ 8 | res.send(500); 9 | } else { 10 | if(data){ 11 | res.send(200, data); 12 | } else { 13 | res.send(404); 14 | } 15 | } 16 | }; 17 | 18 | exports.getAllEvents = function(req, res){ 19 | EventModel.find(function(err, data){ 20 | sendBackData(res, err, data); 21 | }); 22 | }; 23 | 24 | exports.findSingle = function(req, res){ 25 | EventModel.findById(req.params.id, function(err, data){ 26 | sendBackData(res, err, data); 27 | }); 28 | }; 29 | 30 | exports.addEvent = function(req, res){ 31 | var model = new EventModel({ 32 | name: req.body.name, 33 | description: req.body.description 34 | }); 35 | 36 | model.save(function(err){ 37 | if(err){ 38 | res.send(500); 39 | } else { 40 | res.send(201); 41 | } 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /app/controllers/users.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | passport = require('passport'), 8 | User = mongoose.model('User'), 9 | _ = require('lodash'); 10 | 11 | /** 12 | * Get the error message from error object 13 | */ 14 | var getErrorMessage = function(err) { 15 | var message = ''; 16 | 17 | if (err.code) { 18 | switch (err.code) { 19 | case 11000: 20 | case 11001: 21 | message = 'Username already exists'; 22 | break; 23 | default: 24 | message = 'Something went wrong'; 25 | } 26 | } else { 27 | for (var errName in err.errors) { 28 | if (err.errors[errName].message) message = err.errors[errName].message; 29 | } 30 | } 31 | 32 | return message; 33 | }; 34 | 35 | /** 36 | * Signup 37 | */ 38 | exports.signup = function(req, res) { 39 | // For security measurement we remove the roles from the req.body object 40 | delete req.body.roles; 41 | 42 | // Init Variables 43 | var user = new User(req.body); 44 | var message = null; 45 | 46 | // Add missing user fields 47 | user.provider = 'local'; 48 | user.displayName = user.firstName + ' ' + user.lastName; 49 | 50 | // Then save the user 51 | user.save(function(err) { 52 | if (err) { 53 | return res.send(400, { 54 | message: getErrorMessage(err) 55 | }); 56 | } else { 57 | // Remove sensitive data before login 58 | user.password = undefined; 59 | user.salt = undefined; 60 | 61 | req.login(user, function(err) { 62 | if (err) { 63 | res.send(400, err); 64 | } else { 65 | res.jsonp(user); 66 | } 67 | }); 68 | } 69 | }); 70 | }; 71 | 72 | /** 73 | * Signin after passport authentication 74 | */ 75 | exports.signin = function(req, res, next) { 76 | passport.authenticate('local', function(err, user, info) { 77 | if (err || !user) { 78 | res.send(400, info); 79 | } else { 80 | // Remove sensitive data before login 81 | user.password = undefined; 82 | user.salt = undefined; 83 | 84 | req.login(user, function(err) { 85 | if (err) { 86 | res.send(400, err); 87 | } else { 88 | res.jsonp(user); 89 | } 90 | }); 91 | } 92 | })(req, res, next); 93 | }; 94 | 95 | /** 96 | * Update user details 97 | */ 98 | exports.update = function(req, res) { 99 | // Init Variables 100 | var user = req.user; 101 | var message = null; 102 | 103 | // For security measurement we remove the roles from the req.body object 104 | delete req.body.roles; 105 | 106 | if (user) { 107 | // Merge existing user 108 | user = _.extend(user, req.body); 109 | user.updated = Date.now(); 110 | user.displayName = user.firstName + ' ' + user.lastName; 111 | 112 | user.save(function(err) { 113 | if (err) { 114 | return res.send(400, { 115 | message: getErrorMessage(err) 116 | }); 117 | } else { 118 | req.login(user, function(err) { 119 | if (err) { 120 | res.send(400, err); 121 | } else { 122 | res.jsonp(user); 123 | } 124 | }); 125 | } 126 | }); 127 | } else { 128 | res.send(400, { 129 | message: 'User is not signed in' 130 | }); 131 | } 132 | }; 133 | 134 | /** 135 | * Change Password 136 | */ 137 | exports.changePassword = function(req, res, next) { 138 | // Init Variables 139 | var passwordDetails = req.body; 140 | var message = null; 141 | 142 | if (req.user) { 143 | User.findById(req.user.id, function(err, user) { 144 | if (!err && user) { 145 | if (user.authenticate(passwordDetails.currentPassword)) { 146 | if (passwordDetails.newPassword === passwordDetails.verifyPassword) { 147 | user.password = passwordDetails.newPassword; 148 | 149 | user.save(function(err) { 150 | if (err) { 151 | return res.send(400, { 152 | message: getErrorMessage(err) 153 | }); 154 | } else { 155 | req.login(user, function(err) { 156 | if (err) { 157 | res.send(400, err); 158 | } else { 159 | res.send({ 160 | message: 'Password changed successfully' 161 | }); 162 | } 163 | }); 164 | } 165 | }); 166 | } else { 167 | res.send(400, { 168 | message: 'Passwords do not match' 169 | }); 170 | } 171 | } else { 172 | res.send(400, { 173 | message: 'Current password is incorrect' 174 | }); 175 | } 176 | } else { 177 | res.send(400, { 178 | message: 'User is not found' 179 | }); 180 | } 181 | }); 182 | } else { 183 | res.send(400, { 184 | message: 'User is not signed in' 185 | }); 186 | } 187 | }; 188 | 189 | /** 190 | * Signout 191 | */ 192 | exports.signout = function(req, res) { 193 | req.logout(); 194 | res.redirect('/'); 195 | }; 196 | 197 | /** 198 | * Send User 199 | */ 200 | exports.me = function(req, res) { 201 | res.jsonp(req.user || null); 202 | }; 203 | 204 | /** 205 | * OAuth callback 206 | */ 207 | exports.oauthCallback = function(strategy) { 208 | return function(req, res, next) { 209 | passport.authenticate(strategy, function(err, user, redirectURL) { 210 | if (err || !user) { 211 | return res.redirect('/#!/signin'); 212 | } 213 | req.login(user, function(err) { 214 | if (err) { 215 | return res.redirect('/#!/signin'); 216 | } 217 | 218 | return res.redirect(redirectURL || '/'); 219 | }); 220 | })(req, res, next); 221 | }; 222 | }; 223 | 224 | /** 225 | * User middleware 226 | */ 227 | exports.userByID = function(req, res, next, id) { 228 | User.findOne({ 229 | _id: id 230 | }).exec(function(err, user) { 231 | if (err) return next(err); 232 | if (!user) return next(new Error('Failed to load User ' + id)); 233 | req.profile = user; 234 | next(); 235 | }); 236 | }; 237 | 238 | /** 239 | * Require login routing middleware 240 | */ 241 | exports.requiresLogin = function(req, res, next) { 242 | if (!req.isAuthenticated()) { 243 | return res.send(401, { 244 | message: 'User is not logged in' 245 | }); 246 | } 247 | 248 | next(); 249 | }; 250 | 251 | /** 252 | * User authorizations routing middleware 253 | */ 254 | exports.hasAuthorization = function(roles) { 255 | var _this = this; 256 | 257 | return function(req, res, next) { 258 | _this.requiresLogin(req, res, function() { 259 | if (_.intersection(req.user.roles, roles).length) { 260 | return next(); 261 | } else { 262 | return res.send(403, { 263 | message: 'User is not authorized' 264 | }); 265 | } 266 | }); 267 | }; 268 | }; 269 | 270 | /** 271 | * Helper function to save or update a OAuth user profile 272 | */ 273 | exports.saveOAuthUserProfile = function(req, providerUserProfile, done) { 274 | if (!req.user) { 275 | // Define a search query fields 276 | var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField; 277 | var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField; 278 | 279 | // Define main provider search query 280 | var mainProviderSearchQuery = {}; 281 | mainProviderSearchQuery.provider = providerUserProfile.provider; 282 | mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; 283 | 284 | // Define additional provider search query 285 | var additionalProviderSearchQuery = {}; 286 | additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField]; 287 | 288 | // Define a search query to find existing user with current provider profile 289 | var searchQuery = { 290 | $or: [mainProviderSearchQuery, additionalProviderSearchQuery] 291 | }; 292 | 293 | User.findOne(searchQuery, function(err, user) { 294 | if (err) { 295 | return done(err); 296 | } else { 297 | if (!user) { 298 | var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : ''); 299 | 300 | User.findUniqueUsername(possibleUsername, null, function(availableUsername) { 301 | user = new User({ 302 | firstName: providerUserProfile.firstName, 303 | lastName: providerUserProfile.lastName, 304 | username: availableUsername, 305 | displayName: providerUserProfile.displayName, 306 | email: providerUserProfile.email, 307 | provider: providerUserProfile.provider, 308 | providerData: providerUserProfile.providerData 309 | }); 310 | 311 | // And save the user 312 | user.save(function(err) { 313 | return done(err, user); 314 | }); 315 | }); 316 | } else { 317 | return done(err, user); 318 | } 319 | } 320 | }); 321 | } else { 322 | // User is already logged in, join the provider data to the existing user 323 | var user = req.user; 324 | 325 | // Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured 326 | if (user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) { 327 | // Add the provider data to the additional provider data field 328 | if (!user.additionalProvidersData) user.additionalProvidersData = {}; 329 | user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData; 330 | 331 | // Then tell mongoose that we've updated the additionalProvidersData field 332 | user.markModified('additionalProvidersData'); 333 | 334 | // And save the user 335 | user.save(function(err) { 336 | return done(err, user, '/#!/settings/accounts'); 337 | }); 338 | } else { 339 | return done(new Error('User is already connected using this provider'), user); 340 | } 341 | } 342 | }; 343 | 344 | /** 345 | * Remove OAuth provider 346 | */ 347 | exports.removeOAuthProvider = function(req, res, next) { 348 | var user = req.user; 349 | var provider = req.param('provider'); 350 | 351 | if (user && provider) { 352 | // Delete the additional provider 353 | if (user.additionalProvidersData[provider]) { 354 | delete user.additionalProvidersData[provider]; 355 | 356 | // Then tell mongoose that we've updated the additionalProvidersData field 357 | user.markModified('additionalProvidersData'); 358 | } 359 | 360 | user.save(function(err) { 361 | if (err) { 362 | return res.send(400, { 363 | message: getErrorMessage(err) 364 | }); 365 | } else { 366 | req.login(user, function(err) { 367 | if (err) { 368 | res.send(400, err); 369 | } else { 370 | res.jsonp(user); 371 | } 372 | }); 373 | } 374 | }); 375 | } 376 | }; -------------------------------------------------------------------------------- /app/models/event.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | _ = require('lodash'), 5 | Schema = mongoose.Schema, 6 | EventSchema = new Schema({ 7 | name: { 8 | type: String, 9 | trim: true, 10 | default: '', 11 | required: 'Name is required' 12 | }, 13 | description: { 14 | type: String, 15 | trim: true, 16 | default: '' 17 | }, 18 | ratings: [] 19 | }); 20 | 21 | EventSchema.methods.getTotalRating = function(){ 22 | var totalRatings = 0; 23 | 24 | _.each(this.ratings, function(item){ 25 | totalRatings += item.rating; 26 | }); 27 | 28 | return totalRatings; 29 | }; 30 | 31 | EventSchema.methods.calculateAverageRating = function(){ 32 | var totalRatings = this.getTotalRating(); 33 | 34 | if(this.ratings.length > 0){ 35 | this.averageRating = totalRatings / this.ratings.length; 36 | } else { 37 | this.averageRating = 0; 38 | } 39 | }; 40 | 41 | EventSchema.pre('save', function(next){ 42 | this.calculateAverageRating(); 43 | 44 | next(); 45 | }); 46 | 47 | module.exports = mongoose.model('Event', EventSchema); 48 | -------------------------------------------------------------------------------- /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: true, 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 | }); 86 | 87 | /** 88 | * Hook a pre save method to hash the password 89 | */ 90 | UserSchema.pre('save', function(next) { 91 | if (this.password && this.password.length > 6) { 92 | this.salt = new Buffer(crypto.randomBytes(16).toString('base64'), 'base64'); 93 | this.password = this.hashPassword(this.password); 94 | } 95 | 96 | next(); 97 | }); 98 | 99 | /** 100 | * Create instance method for hashing a password 101 | */ 102 | UserSchema.methods.hashPassword = function(password) { 103 | if (this.salt && password) { 104 | return crypto.pbkdf2Sync(password, this.salt, 10000, 64).toString('base64'); 105 | } else { 106 | return password; 107 | } 108 | }; 109 | 110 | /** 111 | * Create instance method for authenticating user 112 | */ 113 | UserSchema.methods.authenticate = function(password) { 114 | return this.password === this.hashPassword(password); 115 | }; 116 | 117 | /** 118 | * Find possible not used username 119 | */ 120 | UserSchema.statics.findUniqueUsername = function(username, suffix, callback) { 121 | var _this = this; 122 | var possibleUsername = username + (suffix || ''); 123 | 124 | _this.findOne({ 125 | username: possibleUsername 126 | }, function(err, user) { 127 | if (!err) { 128 | if (!user) { 129 | callback(possibleUsername); 130 | } else { 131 | return _this.findUniqueUsername(username, (suffix || 0) + 1, callback); 132 | } 133 | } else { 134 | callback(null); 135 | } 136 | }); 137 | }; 138 | 139 | mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /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'); 6 | app.route('/').get(core.index); 7 | }; -------------------------------------------------------------------------------- /app/routes/events.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var ctrl = require('../controllers/event.server.controller.js'); 3 | 4 | module.exports = function(app){ 5 | app.route('/events').get(ctrl.getAllEvents); 6 | app.route('/events').post(ctrl.addEvent); 7 | app.route('/events/:id').get(ctrl.findSingle); 8 | }; 9 | -------------------------------------------------------------------------------- /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'); 11 | app.route('/users/me').get(users.me); 12 | app.route('/users').put(users.update); 13 | app.route('/users/password').post(users.changePassword); 14 | app.route('/users/accounts').delete(users.removeOAuthProvider); 15 | 16 | // Setting up the users api 17 | app.route('/auth/signup').post(users.signup); 18 | app.route('/auth/signin').post(users.signin); 19 | app.route('/auth/signout').get(users.signout); 20 | 21 | // Setting the facebook oauth routes 22 | app.route('/auth/facebook').get(passport.authenticate('facebook', { 23 | scope: ['email'] 24 | })); 25 | app.route('/auth/facebook/callback').get(users.oauthCallback('facebook')); 26 | 27 | // Setting the twitter oauth routes 28 | app.route('/auth/twitter').get(passport.authenticate('twitter')); 29 | app.route('/auth/twitter/callback').get(users.oauthCallback('twitter')); 30 | 31 | // Setting the google oauth routes 32 | app.route('/auth/google').get(passport.authenticate('google', { 33 | scope: [ 34 | 'https://www.googleapis.com/auth/userinfo.profile', 35 | 'https://www.googleapis.com/auth/userinfo.email' 36 | ] 37 | })); 38 | app.route('/auth/google/callback').get(users.oauthCallback('google')); 39 | 40 | // Setting the linkedin oauth routes 41 | app.route('/auth/linkedin').get(passport.authenticate('linkedin')); 42 | app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin')); 43 | 44 | // Finish by binding the user middleware 45 | app.param('userId', users.userByID); 46 | }; -------------------------------------------------------------------------------- /app/tests/controllers/event.server.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var controller = require('../../controllers/event.server.controller.js'), 4 | mongoose = require('mongoose'), 5 | sinon = require('sinon'), 6 | EventModel = mongoose.model('Event'); 7 | 8 | describe('Event Controller', function(){ 9 | var req, 10 | res, 11 | statusCode, 12 | sentData; 13 | 14 | beforeEach(function(){ 15 | res = { 16 | send: function(code, data){ 17 | statusCode = code; 18 | sentData = data; 19 | } 20 | }; 21 | }); 22 | 23 | describe('When fetching all events', function(){ 24 | beforeEach(function(){ 25 | EventModel.find = function(callback){ 26 | callback(null, [{name: 'event1'}]); 27 | }; 28 | }); 29 | 30 | it('should return 200', function(){ 31 | controller.getAllEvents(req, res); 32 | statusCode.should.equal(200); 33 | }); 34 | 35 | it('should send back data', function(){ 36 | controller.getAllEvents(req, res); 37 | sentData[0].name.should.equal('event1'); 38 | }); 39 | 40 | it('should return 500 when find errors', function(){ 41 | EventModel.find = function(callback){ 42 | callback({err: 1}, null); 43 | }; 44 | 45 | controller.getAllEvents(req, res); 46 | statusCode.should.equal(500); 47 | }); 48 | }); 49 | 50 | describe('When fetching single event', function(){ 51 | beforeEach(function(){ 52 | req = { 53 | params: { 54 | id: 1 55 | } 56 | }; 57 | }); 58 | 59 | it('should return 404 when not found', function(){ 60 | EventModel.findById = function(id, callback){ 61 | callback(undefined, undefined); 62 | }; 63 | 64 | controller.findSingle(req, res); 65 | 66 | statusCode.should.equal(404); 67 | }); 68 | 69 | it('should return 500 when find errors', function(){ 70 | EventModel.findById = function(id, callback){ 71 | callback({err:1}, undefined); 72 | }; 73 | 74 | controller.findSingle(req, res); 75 | statusCode.should.equal(500); 76 | }); 77 | 78 | it('should return data when successful', function(){ 79 | EventModel.findById = function(id, callback){ 80 | callback(undefined, {id: id, name: 'Test Event'}); 81 | }; 82 | 83 | controller.findSingle(req, res); 84 | sentData.id.should.equal(1); 85 | }); 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /app/tests/models/event.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('should'); 4 | 5 | var Model = require('../../models/event.js'); 6 | 7 | describe('Event Model', function(){ 8 | it('should have an array of ratings', function(){ 9 | var event = new Model(); 10 | event.ratings.should.not.equal(undefined); 11 | }); 12 | 13 | describe('when saving event', function(){ 14 | beforeEach(function(){ 15 | Model.prototype.save = function(callback){ 16 | callback(); 17 | }; 18 | }); 19 | 20 | it('should calculate average rating', function(){ 21 | 22 | var event = new Model({ 23 | name: 'test', 24 | ratings: [{ 25 | rating: 1 26 | }, { 27 | rating: 2 28 | }] 29 | }); 30 | 31 | event.save(function(){ 32 | event.averageRating.should.equal(1.5); 33 | }); 34 | }); 35 | 36 | it('should set average rating of 0 if no ratings', function(){ 37 | var event = new Model({name: 'test'}); 38 | event.save(function(){ 39 | event.averageRating.should.equal(0); 40 | }); 41 | 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /app/views/index.server.view.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.server.view.html' %} 2 | 3 | {% block content %} 4 |
5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /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 | {% for cssFile in cssFiles %} 37 | {% endfor %} 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 |
48 |
49 | {% block content %}{% endblock %} 50 |
51 |
52 | 53 | 54 | 57 | 58 | 59 | {% for jsFile in jsFiles %} 60 | {% endfor %} 61 | 62 | {% if process.env.NODE_ENV === 'development' %} 63 | 64 | 65 | 66 | {% endif %} 67 | 68 | 69 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copperpitch", 3 | "version": "0.0.1", 4 | "description": "Sample application for intro to protractor pluralsight course", 5 | "dependencies": { 6 | "bootstrap": "~3", 7 | "angular": "~1.2", 8 | "angular-resource": "~1.2", 9 | "angular-mocks": "~1.2", 10 | "angular-cookies": "~1.2", 11 | "angular-sanitize": "~1.2", 12 | "angular-bootstrap": "~0.11.0", 13 | "angular-ui-utils": "~0.1.1", 14 | "angular-ui-router": "~0.2.10" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | glob = require('glob'); 8 | 9 | /** 10 | * Load app configurations 11 | */ 12 | module.exports = _.extend( 13 | require('./env/all'), 14 | require('./env/' + process.env.NODE_ENV) || {} 15 | ); 16 | 17 | /** 18 | * Get files by glob patterns 19 | */ 20 | module.exports.getGlobbedFiles = function(globPatterns, removeRoot) { 21 | // For context switching 22 | var _this = this; 23 | 24 | // URL paths regex 25 | var urlRegex = new RegExp('^(?:[a-z]+:)?//', 'i'); 26 | 27 | // The output array 28 | var output = []; 29 | 30 | // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob 31 | if (_.isArray(globPatterns)) { 32 | globPatterns.forEach(function(globPattern) { 33 | output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot)); 34 | }); 35 | } else if (_.isString(globPatterns)) { 36 | if (urlRegex.test(globPatterns)) { 37 | output.push(globPatterns); 38 | } else { 39 | glob(globPatterns, { 40 | sync: true 41 | }, function(err, files) { 42 | if (removeRoot) { 43 | files = files.map(function(file) { 44 | return file.replace(removeRoot, ''); 45 | }); 46 | } 47 | 48 | output = _.union(output, files); 49 | }); 50 | } 51 | } 52 | 53 | return output; 54 | }; 55 | 56 | /** 57 | * Get the modules JavaScript files 58 | */ 59 | module.exports.getJavaScriptAssets = function(includeTests) { 60 | var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js), 'public/'); 61 | 62 | // To include tests 63 | if (includeTests) { 64 | output = _.union(output, this.getGlobbedFiles(this.assets.tests)); 65 | } 66 | 67 | return output; 68 | }; 69 | 70 | /** 71 | * Get the modules CSS files 72 | */ 73 | module.exports.getCSSAssets = function() { 74 | var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/'); 75 | return output; 76 | }; -------------------------------------------------------------------------------- /config/env/all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | app: { 5 | title: 'copperPitch', 6 | description: 'Sample application for TDD as a design pluralsight course', 7 | keywords: 'MongoDB, Express, AngularJS, Node.js' 8 | }, 9 | port: process.env.PORT || 3000, 10 | templateEngine: 'swig', 11 | sessionSecret: 'MEAN', 12 | sessionCollection: 'sessions', 13 | assets: { 14 | lib: { 15 | css: [ 16 | 'public/lib/bootstrap/dist/css/bootstrap.css', 17 | 'public/lib/bootstrap/dist/css/bootstrap-theme.css', 18 | ], 19 | js: [ 20 | 'public/lib/angular/angular.js', 21 | 'public/lib/angular-resource/angular-resource.js', 22 | 'public/lib/angular-cookies/angular-cookies.js', 23 | 'public/lib/angular-sanitize/angular-sanitize.js', 24 | 'public/lib/angular-ui-router/release/angular-ui-router.js', 25 | 'public/lib/angular-ui-utils/ui-utils.js', 26 | 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js' 27 | ] 28 | }, 29 | css: [ 30 | 'public/modules/**/css/*.css' 31 | ], 32 | js: [ 33 | 'public/config.js', 34 | 'public/application.js', 35 | 'public/modules/*/*.js', 36 | 'public/modules/*/*[!tests]*/*.js' 37 | ], 38 | tests: [ 39 | 'public/lib/angular-mocks/angular-mocks.js', 40 | 'public/modules/*/tests/*.js' 41 | ] 42 | } 43 | }; -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: 'mongodb://localhost/copperpitch-dev', 5 | app: { 6 | title: 'copperPitch - Development Environment' 7 | }, 8 | facebook: { 9 | clientID: process.env.FACEBOOK_ID || 'APP_ID', 10 | clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', 11 | callbackURL: 'http://localhost:3000/auth/facebook/callback' 12 | }, 13 | twitter: { 14 | clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', 15 | clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', 16 | callbackURL: 'http://localhost:3000/auth/twitter/callback' 17 | }, 18 | google: { 19 | clientID: process.env.GOOGLE_ID || 'APP_ID', 20 | clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', 21 | callbackURL: 'http://localhost:3000/auth/google/callback' 22 | }, 23 | linkedin: { 24 | clientID: process.env.LINKEDIN_ID || 'APP_ID', 25 | clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 26 | callbackURL: 'http://localhost:3000/auth/linkedin/callback' 27 | } 28 | }; -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/copperpitch', 5 | assets: { 6 | lib: { 7 | css: [ 8 | 'public/lib/bootstrap/dist/css/bootstrap.min.css', 9 | 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css', 10 | ], 11 | js: [ 12 | 'public/lib/angular/angular.min.js', 13 | 'public/lib/angular-resource/angular-resource.js', 14 | 'public/lib/angular-cookies/angular-cookies.js', 15 | 'public/lib/angular-sanitize/angular-sanitize.js', 16 | 'public/lib/angular-ui-router/release/angular-ui-router.min.js', 17 | 'public/lib/angular-ui-utils/ui-utils.min.js', 18 | 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js' 19 | ] 20 | }, 21 | css: 'public/dist/application.min.css', 22 | js: 'public/dist/application.min.js' 23 | }, 24 | facebook: { 25 | clientID: process.env.FACEBOOK_ID || 'APP_ID', 26 | clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', 27 | callbackURL: 'http://localhost:3000/auth/facebook/callback' 28 | }, 29 | twitter: { 30 | clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', 31 | clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', 32 | callbackURL: 'http://localhost:3000/auth/twitter/callback' 33 | }, 34 | google: { 35 | clientID: process.env.GOOGLE_ID || 'APP_ID', 36 | clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', 37 | callbackURL: 'http://localhost:3000/auth/google/callback' 38 | }, 39 | linkedin: { 40 | clientID: process.env.LINKEDIN_ID || 'APP_ID', 41 | clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 42 | callbackURL: 'http://localhost:3000/auth/linkedin/callback' 43 | } 44 | }; -------------------------------------------------------------------------------- /config/env/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | db: 'mongodb://localhost/copperpitch-test', 5 | port: 3001, 6 | app: { 7 | title: 'copperPitch - Test Environment' 8 | }, 9 | facebook: { 10 | clientID: process.env.FACEBOOK_ID || 'APP_ID', 11 | clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', 12 | callbackURL: 'http://localhost:3000/auth/facebook/callback' 13 | }, 14 | twitter: { 15 | clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', 16 | clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', 17 | callbackURL: 'http://localhost:3000/auth/twitter/callback' 18 | }, 19 | google: { 20 | clientID: process.env.GOOGLE_ID || 'APP_ID', 21 | clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', 22 | callbackURL: 'http://localhost:3000/auth/google/callback' 23 | }, 24 | linkedin: { 25 | clientID: process.env.LINKEDIN_ID || 'APP_ID', 26 | clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 27 | callbackURL: 'http://localhost:3000/auth/linkedin/callback' 28 | } 29 | }; -------------------------------------------------------------------------------- /config/express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var express = require('express'), 7 | morgan = require('morgan'), 8 | bodyParser = require('body-parser'), 9 | session = require('express-session'), 10 | compress = require('compression'), 11 | methodOverride = require('method-override'), 12 | cookieParser = require('cookie-parser'), 13 | helmet = require('helmet'), 14 | passport = require('passport'), 15 | mongoStore = require('connect-mongo')({ 16 | session: session 17 | }), 18 | flash = require('connect-flash'), 19 | config = require('./config'), 20 | consolidate = require('consolidate'), 21 | path = require('path'); 22 | 23 | module.exports = function(db) { 24 | // Initialize express app 25 | var app = express(); 26 | 27 | // Globbing model files 28 | config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) { 29 | require(path.resolve(modelPath)); 30 | }); 31 | 32 | // Setting application local variables 33 | app.locals.title = config.app.title; 34 | app.locals.description = config.app.description; 35 | app.locals.keywords = config.app.keywords; 36 | app.locals.facebookAppId = config.facebook.clientID; 37 | app.locals.jsFiles = config.getJavaScriptAssets(); 38 | app.locals.cssFiles = config.getCSSAssets(); 39 | 40 | // Passing the request url to environment locals 41 | app.use(function(req, res, next) { 42 | res.locals.url = req.protocol + ':// ' + req.headers.host + req.url; 43 | next(); 44 | }); 45 | 46 | // Should be placed before express.static 47 | app.use(compress({ 48 | filter: function(req, res) { 49 | return (/json|text|javascript|css/).test(res.getHeader('Content-Type')); 50 | }, 51 | level: 9 52 | })); 53 | 54 | // Showing stack errors 55 | app.set('showStackError', true); 56 | 57 | // Set swig as the template engine 58 | app.engine('server.view.html', consolidate[config.templateEngine]); 59 | 60 | // Set views path and view engine 61 | app.set('view engine', 'server.view.html'); 62 | app.set('views', './app/views'); 63 | 64 | // Environment dependent middleware 65 | if (process.env.NODE_ENV === 'development') { 66 | // Enable logger (morgan) 67 | app.use(morgan('dev')); 68 | 69 | // Disable views cache 70 | app.set('view cache', false); 71 | } else if (process.env.NODE_ENV === 'production') { 72 | app.locals.cache = 'memory'; 73 | } 74 | 75 | // Request body parsing middleware should be above methodOverride 76 | app.use(bodyParser.urlencoded()); 77 | app.use(bodyParser.json()); 78 | app.use(methodOverride()); 79 | 80 | // Enable jsonp 81 | app.enable('jsonp callback'); 82 | 83 | // CookieParser should be above session 84 | app.use(cookieParser()); 85 | 86 | // Express MongoDB session storage 87 | // app.use(session({ 88 | // secret: config.sessionSecret, 89 | // store: new mongoStore({ 90 | // db: db.connection.db, 91 | // collection: config.sessionCollection 92 | // }) 93 | // })); 94 | 95 | // use passport session 96 | app.use(passport.initialize()); 97 | app.use(passport.session()); 98 | 99 | // connect flash for flash messages 100 | app.use(flash()); 101 | 102 | // Use helmet to secure Express headers 103 | app.use(helmet.xframe()); 104 | app.use(helmet.iexss()); 105 | app.use(helmet.contentTypeOptions()); 106 | app.use(helmet.ienoopen()); 107 | app.disable('x-powered-by'); 108 | 109 | // Setting the app router and static folder 110 | app.use(express.static(path.resolve('./public'))); 111 | 112 | // Globbing routing files 113 | config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) { 114 | require(path.resolve(routePath))(app); 115 | }); 116 | 117 | // 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. 118 | app.use(function(err, req, res, next) { 119 | // If the error object doesn't exists 120 | if (!err) return next(); 121 | 122 | // Log it 123 | console.error(err.stack); 124 | 125 | // Error page 126 | res.status(500).render('500', { 127 | error: err.stack 128 | }); 129 | }); 130 | 131 | // Assume 404 since no middleware responded 132 | app.use(function(req, res) { 133 | res.status(404).render('404', { 134 | url: req.originalUrl, 135 | error: 'Not Found' 136 | }); 137 | }); 138 | 139 | return app; 140 | }; -------------------------------------------------------------------------------- /config/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var glob = require('glob'); 7 | 8 | /** 9 | * Module init function. 10 | */ 11 | module.exports = function() { 12 | /** 13 | * Before we begin, lets set the environment variable 14 | * We'll Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV 15 | */ 16 | glob('./config/env/' + process.env.NODE_ENV + '.js', { 17 | sync: true 18 | }, function(err, environmentFiles) { 19 | console.log(); 20 | if (!environmentFiles.length) { 21 | if (process.env.NODE_ENV) { 22 | console.log('\x1b[31m', 'No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead'); 23 | } else { 24 | console.log('\x1b[31m', 'NODE_ENV is not defined! Using default development environment'); 25 | } 26 | 27 | process.env.NODE_ENV = 'development'; 28 | } else { 29 | console.log('\x1b[7m', 'Application loaded using the "' + process.env.NODE_ENV + '" environment configuration'); 30 | } 31 | console.log('\x1b[0m'); 32 | }); 33 | 34 | /** 35 | * Add our server node extensions 36 | */ 37 | require.extensions['.server.controller.js'] = require.extensions['.js']; 38 | require.extensions['.server.model.js'] = require.extensions['.js']; 39 | require.extensions['.server.routes.js'] = require.extensions['.js']; 40 | }; -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var passport = require('passport'), 4 | User = require('mongoose').model('User'), 5 | path = require('path'), 6 | config = require('./config'); 7 | 8 | module.exports = function() { 9 | // Serialize sessions 10 | passport.serializeUser(function(user, done) { 11 | done(null, user.id); 12 | }); 13 | 14 | // Deserialize sessions 15 | passport.deserializeUser(function(id, done) { 16 | User.findOne({ 17 | _id: id 18 | }, '-salt -password', function(err, user) { 19 | done(err, user); 20 | }); 21 | }); 22 | 23 | // Initialize strategies 24 | config.getGlobbedFiles('./config/strategies/**/*.js').forEach(function(strategy) { 25 | require(path.resolve(strategy))(); 26 | }); 27 | }; -------------------------------------------------------------------------------- /config/strategies/twitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | url = require('url'), 8 | TwitterStrategy = require('passport-twitter').Strategy, 9 | config = require('../config'), 10 | users = require('../../app/controllers/users'); 11 | 12 | module.exports = function() { 13 | // Use twitter strategy 14 | passport.use(new TwitterStrategy({ 15 | consumerKey: config.twitter.clientID, 16 | consumerSecret: config.twitter.clientSecret, 17 | callbackURL: config.twitter.callbackURL, 18 | passReqToCallback: true 19 | }, 20 | function(req, token, tokenSecret, profile, done) { 21 | // Set the provider data and include tokens 22 | var providerData = profile._json; 23 | providerData.token = token; 24 | providerData.tokenSecret = tokenSecret; 25 | 26 | // Create the user OAuth profile 27 | var providerUserProfile = { 28 | displayName: profile.displayName, 29 | username: profile.username, 30 | provider: 'twitter', 31 | providerIdentifierField: 'id_str', 32 | providerData: providerData 33 | }; 34 | 35 | // Save the user OAuth profile 36 | users.saveOAuthUserProfile(req, providerUserProfile, done); 37 | } 38 | )); 39 | }; -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | // Unified Watch Object 5 | var watchFiles = { 6 | serverViews: ['app/views/**/*.*'], 7 | serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js'], 8 | clientViews: ['public/modules/**/views/*.html'], 9 | clientJS: ['public/js/*.js', 'public/modules/**/*.js'], 10 | clientCSS: ['public/modules/**/*.css'], 11 | mochaTests: ['app/tests/**/*.js'] 12 | }; 13 | 14 | // Project Configuration 15 | grunt.initConfig({ 16 | pkg: grunt.file.readJSON('package.json'), 17 | watch: { 18 | serverViews: { 19 | files: watchFiles.serverViews, 20 | options: { 21 | livereload: true 22 | } 23 | }, 24 | serverJS: { 25 | files: watchFiles.serverJS.concat(watchFiles.mochaTests), 26 | tasks: ['jshint', 'mochaTest'], 27 | options: { 28 | livereload: true 29 | } 30 | }, 31 | clientViews: { 32 | files: watchFiles.clientViews, 33 | options: { 34 | livereload: true, 35 | } 36 | }, 37 | clientJS: { 38 | files: watchFiles.clientJS, 39 | tasks: ['jshint'], 40 | options: { 41 | livereload: true 42 | } 43 | }, 44 | clientCSS: { 45 | files: watchFiles.clientCSS, 46 | tasks: ['csslint'], 47 | options: { 48 | livereload: true 49 | } 50 | } 51 | }, 52 | jshint: { 53 | all: { 54 | src: watchFiles.clientJS.concat(watchFiles.serverJS), 55 | options: { 56 | jshintrc: true 57 | } 58 | } 59 | }, 60 | csslint: { 61 | options: { 62 | csslintrc: '.csslintrc', 63 | }, 64 | all: { 65 | src: watchFiles.clientCSS 66 | } 67 | }, 68 | uglify: { 69 | production: { 70 | options: { 71 | mangle: false 72 | }, 73 | files: { 74 | 'public/dist/application.min.js': 'public/dist/application.js' 75 | } 76 | } 77 | }, 78 | cssmin: { 79 | combine: { 80 | files: { 81 | 'public/dist/application.min.css': '<%= applicationCSSFiles %>' 82 | } 83 | } 84 | }, 85 | nodemon: { 86 | dev: { 87 | script: 'server.js', 88 | options: { 89 | nodeArgs: ['--debug'], 90 | ext: 'js,html', 91 | watch: watchFiles.serverViews.concat(watchFiles.serverJS) 92 | } 93 | } 94 | }, 95 | 'node-inspector': { 96 | custom: { 97 | options: { 98 | 'web-port': 1337, 99 | 'web-host': 'localhost', 100 | 'debug-port': 5858, 101 | 'save-live-edit': true, 102 | 'no-preload': true, 103 | 'stack-trace-limit': 50, 104 | 'hidden': [] 105 | } 106 | } 107 | }, 108 | ngmin: { 109 | production: { 110 | files: { 111 | 'public/dist/application.js': '<%= applicationJavaScriptFiles %>' 112 | } 113 | } 114 | }, 115 | concurrent: { 116 | default: ['nodemon', 'watch'], 117 | debug: ['nodemon', 'watch', 'node-inspector'], 118 | test: ['mochaTest', 'watch'], 119 | options: { 120 | logConcurrentOutput: true 121 | } 122 | }, 123 | env: { 124 | test: { 125 | NODE_ENV: 'test' 126 | } 127 | }, 128 | mochaTest: { 129 | src: watchFiles.mochaTests, 130 | options: { 131 | reporter: 'spec', 132 | require: 'server.js' 133 | } 134 | }, 135 | karma: { 136 | unit: { 137 | configFile: 'karma.conf.js' 138 | } 139 | } 140 | }); 141 | 142 | // Load NPM tasks 143 | require('load-grunt-tasks')(grunt); 144 | 145 | // Making grunt default to force in order not to break the project. 146 | grunt.option('force', true); 147 | 148 | // A Task for loading the configuration object 149 | grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() { 150 | var init = require('./config/init')(); 151 | var config = require('./config/config'); 152 | 153 | grunt.config.set('applicationJavaScriptFiles', config.assets.js); 154 | grunt.config.set('applicationCSSFiles', config.assets.css); 155 | }); 156 | 157 | // Default task(s). 158 | grunt.registerTask('default', ['lint', 'concurrent:default']); 159 | 160 | // Debug task. 161 | grunt.registerTask('debug', ['lint', 'concurrent:debug']); 162 | 163 | // Lint task(s). 164 | grunt.registerTask('lint', ['jshint', 'csslint']); 165 | 166 | // Build task(s). 167 | grunt.registerTask('build', ['lint', 'loadConfig', 'ngmin', 'uglify', 'cssmin']); 168 | 169 | grunt.registerTask('demoTest', ['env:test', 'concurrent:test']); 170 | 171 | // Test task. 172 | grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']); 173 | }; 174 | -------------------------------------------------------------------------------- /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'], 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 | reporters: ['progress'], 21 | 22 | // Web server port 23 | port: 9876, 24 | 25 | // Enable / disable colors in the output (reporters and logs) 26 | colors: true, 27 | 28 | // Level of logging 29 | // Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 30 | logLevel: config.LOG_INFO, 31 | 32 | // Enable / disable watching file and executing tests whenever any file changes 33 | autoWatch: true, 34 | 35 | // Start these browsers, currently available: 36 | // - Chrome 37 | // - ChromeCanary 38 | // - Firefox 39 | // - Opera 40 | // - Safari (only Mac) 41 | // - PhantomJS 42 | // - IE (only Windows) 43 | browsers: [ 'PhantomJS'], 44 | 45 | // If browser does not capture in given timeout [ms], kill it 46 | captureTimeout: 60000, 47 | 48 | // Continuous Integration mode 49 | // If true, it capture browsers, run tests and exit 50 | singleRun: false 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copperpitch", 3 | "description": "Sample application for TDD as a design pluralsight course", 4 | "version": "0.0.1", 5 | "author": "Nate Taylor", 6 | "engines": { 7 | "node": "0.10.x", 8 | "npm": "1.4.x" 9 | }, 10 | "scripts": { 11 | "start": "grunt", 12 | "test": "grunt test", 13 | "postinstall": "bower install --config.interactive=false" 14 | }, 15 | "dependencies": { 16 | "express": "~4.2.0", 17 | "express-session": "~1.1.0", 18 | "body-parser": "~1.2.0", 19 | "cookie-parser": "~1.1.0", 20 | "compression": "~1.0.1", 21 | "method-override": "~1.0.0", 22 | "morgan": "~1.1.0", 23 | "connect-mongo": "~0.4.0", 24 | "connect-flash": "~0.1.1", 25 | "helmet": "~0.2.1", 26 | "consolidate": "~0.10.0", 27 | "swig": "~1.3.2", 28 | "mongoose": "~3.8.8", 29 | "passport": "~0.2.0", 30 | "passport-local": "~1.0.0", 31 | "passport-facebook": "~1.0.2", 32 | "passport-twitter": "~1.0.2", 33 | "passport-linkedin": "~0.1.3", 34 | "passport-google-oauth": "~0.1.5", 35 | "lodash": "^2.4.1", 36 | "forever": "~0.11.0", 37 | "bower": "~1.3.1", 38 | "grunt-cli": "~0.1.13", 39 | "glob": "~3.2.9" 40 | }, 41 | "devDependencies": { 42 | "supertest": "~0.12.1", 43 | "should": "~3.3.1", 44 | "grunt-env": "~0.4.1", 45 | "grunt-contrib-watch": "~0.6.1", 46 | "grunt-contrib-jshint": "~0.10.0", 47 | "grunt-contrib-csslint": "^0.2.0", 48 | "grunt-ngmin": "0.0.3", 49 | "grunt-contrib-uglify": "~0.4.0", 50 | "grunt-contrib-cssmin": "~0.9.0", 51 | "grunt-nodemon": "~0.2.1", 52 | "grunt-concurrent": "~0.5.0", 53 | "grunt-mocha-test": "~0.10.0", 54 | "grunt-karma": "~0.8.2", 55 | "load-grunt-tasks": "~0.4.0", 56 | "karma": "~0.12.0", 57 | "karma-jasmine": "~0.2.1", 58 | "karma-coverage": "~0.2.0", 59 | "karma-chrome-launcher": "~0.1.2", 60 | "karma-firefox-launcher": "~0.1.3", 61 | "karma-phantomjs-launcher": "~0.1.2", 62 | "sinon": "^1.12.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /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) 8 | .config(['$locationProvider', 9 | function ($locationProvider) { 10 | $locationProvider.html5Mode(false); 11 | $locationProvider.hashPrefix('!'); 12 | } 13 | ]); 14 | 15 | //Then define the init function for starting up the application 16 | angular.element(document).ready(function () { 17 | //Fixing facebook bug with redirect 18 | if (window.location.hash === '#_=_') window.location.hash = '#!'; 19 | 20 | //Then init the app 21 | angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); 22 | }); -------------------------------------------------------------------------------- /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 = 'copperpitch'; 7 | var applicationModuleVendorDependencies = ['ngResource', 'ngCookies', 'ngSanitize', 'ui.router', 'ui.bootstrap', 'ui.utils']; 8 | 9 | // Add a new vertical module 10 | var registerModule = function(moduleName) { 11 | // Create angular module 12 | angular.module(moduleName, []); 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 | })(); -------------------------------------------------------------------------------- /public/dist/application.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Init the application configuration module for AngularJS application 3 | var ApplicationConfiguration = function () { 4 | // Init module configuration options 5 | var applicationModuleName = 'copperpitch'; 6 | var applicationModuleVendorDependencies = [ 7 | 'ngResource', 8 | 'ngCookies', 9 | 'ngSanitize', 10 | 'ui.router', 11 | 'ui.bootstrap', 12 | 'ui.utils' 13 | ]; 14 | // Add a new vertical module 15 | var registerModule = function (moduleName) { 16 | // Create angular module 17 | angular.module(moduleName, []); 18 | // Add the module to the AngularJS configuration file 19 | angular.module(applicationModuleName).requires.push(moduleName); 20 | }; 21 | return { 22 | applicationModuleName: applicationModuleName, 23 | applicationModuleVendorDependencies: applicationModuleVendorDependencies, 24 | registerModule: registerModule 25 | }; 26 | }();'use strict'; 27 | //Start by defining the main module and adding the module dependencies 28 | angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies); 29 | // Setting HTML5 Location Mode 30 | angular.module(ApplicationConfiguration.applicationModuleName).config([ 31 | '$locationProvider', 32 | function ($locationProvider) { 33 | $locationProvider.hashPrefix('!'); 34 | } 35 | ]); 36 | //Then define the init function for starting up the application 37 | angular.element(document).ready(function () { 38 | //Fixing facebook bug with redirect 39 | if (window.location.hash === '#_=_') 40 | window.location.hash = '#!'; 41 | //Then init the app 42 | angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); 43 | });'use strict'; 44 | // Use Applicaion configuration module to register a new module 45 | ApplicationConfiguration.registerModule('core');'use strict'; 46 | // Use applicaion configuration module to register a new module 47 | ApplicationConfiguration.registerModule('ratings');'use strict'; 48 | // Use Applicaion configuration module to register a new module 49 | ApplicationConfiguration.registerModule('users');'use strict'; 50 | // Setting up route 51 | angular.module('core').config([ 52 | '$stateProvider', 53 | '$urlRouterProvider', 54 | function ($stateProvider, $urlRouterProvider) { 55 | // Redirect to home view when route not found 56 | $urlRouterProvider.otherwise('/'); 57 | // Home state routing 58 | $stateProvider.state('home', { 59 | url: '/', 60 | templateUrl: 'modules/core/views/home.client.view.html' 61 | }); 62 | } 63 | ]);'use strict'; 64 | angular.module('core').controller('HeaderController', [ 65 | '$scope', 66 | 'Authentication', 67 | 'Menus', 68 | function ($scope, Authentication, Menus) { 69 | $scope.authentication = Authentication; 70 | $scope.isCollapsed = false; 71 | $scope.menu = Menus.getMenu('topbar'); 72 | $scope.toggleCollapsibleMenu = function () { 73 | $scope.isCollapsed = !$scope.isCollapsed; 74 | }; 75 | // Collapsing the menu after navigation 76 | $scope.$on('$stateChangeSuccess', function () { 77 | $scope.isCollapsed = false; 78 | }); 79 | } 80 | ]);'use strict'; 81 | angular.module('core').controller('HomeController', [ 82 | '$scope', 83 | 'Authentication', 84 | function ($scope, Authentication) { 85 | // This provides Authentication context. 86 | $scope.authentication = Authentication; 87 | } 88 | ]);'use strict'; 89 | //Menu service used for managing menus 90 | angular.module('core').service('Menus', [function () { 91 | // Define a set of default roles 92 | this.defaultRoles = ['user']; 93 | // Define the menus object 94 | this.menus = {}; 95 | // A private function for rendering decision 96 | var shouldRender = function (user) { 97 | if (user) { 98 | for (var userRoleIndex in user.roles) { 99 | for (var roleIndex in this.roles) { 100 | if (this.roles[roleIndex] === user.roles[userRoleIndex]) { 101 | return true; 102 | } 103 | } 104 | } 105 | } else { 106 | return this.isPublic; 107 | } 108 | return false; 109 | }; 110 | // Validate menu existance 111 | this.validateMenuExistance = function (menuId) { 112 | if (menuId && menuId.length) { 113 | if (this.menus[menuId]) { 114 | return true; 115 | } else { 116 | throw new Error('Menu does not exists'); 117 | } 118 | } else { 119 | throw new Error('MenuId was not provided'); 120 | } 121 | return false; 122 | }; 123 | // Get the menu object by menu id 124 | this.getMenu = function (menuId) { 125 | // Validate that the menu exists 126 | this.validateMenuExistance(menuId); 127 | // Return the menu object 128 | return this.menus[menuId]; 129 | }; 130 | // Add new menu object by menu id 131 | this.addMenu = function (menuId, isPublic, roles) { 132 | // Create the new menu 133 | this.menus[menuId] = { 134 | isPublic: isPublic || false, 135 | roles: roles || this.defaultRoles, 136 | items: [], 137 | shouldRender: shouldRender 138 | }; 139 | // Return the menu object 140 | return this.menus[menuId]; 141 | }; 142 | // Remove existing menu object by menu id 143 | this.removeMenu = function (menuId) { 144 | // Validate that the menu exists 145 | this.validateMenuExistance(menuId); 146 | // Return the menu object 147 | delete this.menus[menuId]; 148 | }; 149 | // Add menu item object 150 | this.addMenuItem = function (menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles) { 151 | // Validate that the menu exists 152 | this.validateMenuExistance(menuId); 153 | // Push new menu item 154 | this.menus[menuId].items.push({ 155 | title: menuItemTitle, 156 | link: menuItemURL, 157 | menuItemType: menuItemType || 'item', 158 | menuItemClass: menuItemType, 159 | uiRoute: menuItemUIRoute || '/' + menuItemURL, 160 | isPublic: isPublic || this.menus[menuId].isPublic, 161 | roles: roles || this.defaultRoles, 162 | items: [], 163 | shouldRender: shouldRender 164 | }); 165 | // Return the menu object 166 | return this.menus[menuId]; 167 | }; 168 | // Add submenu item object 169 | this.addSubMenuItem = function (menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles) { 170 | // Validate that the menu exists 171 | this.validateMenuExistance(menuId); 172 | // Search for menu item 173 | for (var itemIndex in this.menus[menuId].items) { 174 | if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) { 175 | // Push new submenu item 176 | this.menus[menuId].items[itemIndex].items.push({ 177 | title: menuItemTitle, 178 | link: menuItemURL, 179 | uiRoute: menuItemUIRoute || '/' + menuItemURL, 180 | isPublic: isPublic || this.menus[menuId].isPublic, 181 | roles: roles || this.defaultRoles, 182 | shouldRender: shouldRender 183 | }); 184 | } 185 | } 186 | // Return the menu object 187 | return this.menus[menuId]; 188 | }; 189 | // Remove existing menu object by menu id 190 | this.removeMenuItem = function (menuId, menuItemURL) { 191 | // Validate that the menu exists 192 | this.validateMenuExistance(menuId); 193 | // Search for menu item to remove 194 | for (var itemIndex in this.menus[menuId].items) { 195 | if (this.menus[menuId].items[itemIndex].link === menuItemURL) { 196 | this.menus[menuId].items.splice(itemIndex, 1); 197 | } 198 | } 199 | // Return the menu object 200 | return this.menus[menuId]; 201 | }; 202 | // Remove existing menu object by menu id 203 | this.removeSubMenuItem = function (menuId, submenuItemURL) { 204 | // Validate that the menu exists 205 | this.validateMenuExistance(menuId); 206 | // Search for menu item to remove 207 | for (var itemIndex in this.menus[menuId].items) { 208 | for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { 209 | if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { 210 | this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); 211 | } 212 | } 213 | } 214 | // Return the menu object 215 | return this.menus[menuId]; 216 | }; 217 | //Adding the topbar menu 218 | this.addMenu('topbar'); 219 | }]);'use strict'; 220 | angular.module('ratings').config([ 221 | '$stateProvider', 222 | ratingsRoutes 223 | ]); 224 | function ratingsRoutes($stateProvider) { 225 | $stateProvider.state('events', { 226 | url: '/EventRatings', 227 | templateUrl: 'modules/ratings/views/event.list.html', 228 | controller: 'EventListController' 229 | }).state('eventsCreate', { 230 | url: '/EventRatings/new', 231 | templateUrl: 'modules/ratings/views/event.create.html', 232 | controller: 'EventCreateController' 233 | }).state('eventsDetails', { 234 | url: '/EventRatings/:eventId', 235 | templateUrl: 'modules/ratings/views/event.details.html', 236 | controller: 'EventDetailsController' 237 | }); 238 | }(function () { 239 | 'use strict'; 240 | angular.module('ratings').controller('EventCreateController', [ 241 | '$scope', 242 | '$state', 243 | 'EventsService', 244 | controller 245 | ]); 246 | function controller($scope, $state, EventsService) { 247 | $scope.event = {}; 248 | $scope.submit = function () { 249 | EventsService.addEvent($scope.event).then(function () { 250 | $state.go('events'); 251 | }); 252 | }; 253 | } 254 | }());'use strict'; 255 | angular.module('ratings').controller('EventDetailsController', [ 256 | '$scope', 257 | '$stateParams', 258 | 'EventsService', 259 | controller 260 | ]); 261 | function controller($scope, $stateParams, EventsService) { 262 | $scope.loading = true; 263 | $scope.getEvent = function (id) { 264 | EventsService.getSingleEvent(id).then(function (detail) { 265 | $scope.eventDetails = detail; 266 | }).finally(function () { 267 | $scope.loading = false; 268 | }); 269 | }; 270 | $scope.getEvent($stateParams.eventId); 271 | }'use strict'; 272 | var app = angular.module('ratings'); 273 | app.controller('EventListController', [ 274 | '$scope', 275 | '$state', 276 | 'EventsService', 277 | controller 278 | ]); 279 | function controller($scope, $state, eventService) { 280 | $scope.loading = true; 281 | $scope.getAllEvents = function () { 282 | eventService.getAllEvents().then(function (events) { 283 | $scope.events = events; 284 | }).finally(function () { 285 | $scope.loading = false; 286 | }); 287 | }; 288 | $scope.selectEvent = function (id) { 289 | $state.go('eventsDetails', { eventId: id }); 290 | }; 291 | $scope.calculateRatingQuality = function (rating) { 292 | if (rating < 2) { 293 | return 'bad'; 294 | } else if (rating <= 3.5) { 295 | return 'ok'; 296 | } else { 297 | return 'good'; 298 | } 299 | }; 300 | $scope.getAllEvents(); 301 | }'use strict'; 302 | var app = angular.module('ratings'); 303 | app.factory('EventsService', [ 304 | '$resource', 305 | '$q', 306 | eventsService 307 | ]); 308 | function eventsService($resource, $q) { 309 | function getData(route, method) { 310 | var deferred = $q.defer(); 311 | var addr = [ 312 | 'http://localhost:3000/events', 313 | route 314 | ].join('/'); 315 | $resource(addr)[method]().$promise.then(function (data) { 316 | deferred.resolve(data); 317 | }).catch(function (error) { 318 | deferred.reject(error); 319 | }); 320 | return deferred.promise; 321 | } 322 | function create(event) { 323 | var deferred = $q.defer(); 324 | $resource('http://localhost:3000/events').save(event).$promise.then(function () { 325 | deferred.resolve(); 326 | }); 327 | return deferred.promise; 328 | } 329 | return { 330 | getAllEvents: function () { 331 | return getData('', 'query'); 332 | }, 333 | getSingleEvent: function (id) { 334 | return getData(id, 'get'); 335 | }, 336 | addEvent: create 337 | }; 338 | }'use strict'; 339 | // Config HTTP Error Handling 340 | angular.module('users').config([ 341 | '$httpProvider', 342 | function ($httpProvider) { 343 | // Set the httpProvider "not authorized" interceptor 344 | $httpProvider.interceptors.push([ 345 | '$q', 346 | '$location', 347 | 'Authentication', 348 | function ($q, $location, Authentication) { 349 | return { 350 | responseError: function (rejection) { 351 | switch (rejection.status) { 352 | case 401: 353 | // Deauthenticate the global user 354 | Authentication.user = null; 355 | // Redirect to signin page 356 | $location.path('signin'); 357 | break; 358 | case 403: 359 | // Add unauthorized behaviour 360 | break; 361 | } 362 | return $q.reject(rejection); 363 | } 364 | }; 365 | } 366 | ]); 367 | } 368 | ]);'use strict'; 369 | // Setting up route 370 | angular.module('users').config([ 371 | '$stateProvider', 372 | function ($stateProvider) { 373 | // Users state routing 374 | $stateProvider.state('profile', { 375 | url: '/settings/profile', 376 | templateUrl: 'modules/users/views/settings/edit-profile.client.view.html' 377 | }).state('password', { 378 | url: '/settings/password', 379 | templateUrl: 'modules/users/views/settings/change-password.client.view.html' 380 | }).state('accounts', { 381 | url: '/settings/accounts', 382 | templateUrl: 'modules/users/views/settings/social-accounts.client.view.html' 383 | }).state('signup', { 384 | url: '/signup', 385 | templateUrl: 'modules/users/views/signup.client.view.html' 386 | }).state('signin', { 387 | url: '/signin', 388 | templateUrl: 'modules/users/views/signin.client.view.html' 389 | }); 390 | } 391 | ]);'use strict'; 392 | angular.module('users').controller('AuthenticationController', [ 393 | '$scope', 394 | '$http', 395 | '$location', 396 | 'Authentication', 397 | function ($scope, $http, $location, Authentication) { 398 | $scope.authentication = Authentication; 399 | //If user is signed in then redirect back home 400 | if ($scope.authentication.user) 401 | $location.path('/'); 402 | $scope.signup = function () { 403 | $http.post('/auth/signup', $scope.credentials).success(function (response) { 404 | //If successful we assign the response to the global user model 405 | $scope.authentication.user = response; 406 | //And redirect to the index page 407 | $location.path('/'); 408 | }).error(function (response) { 409 | $scope.error = response.message; 410 | }); 411 | }; 412 | $scope.signin = function () { 413 | $http.post('/auth/signin', $scope.credentials).success(function (response) { 414 | //If successful we assign the response to the global user model 415 | $scope.authentication.user = response; 416 | //And redirect to the index page 417 | $location.path('/'); 418 | }).error(function (response) { 419 | $scope.error = response.message; 420 | }); 421 | }; 422 | } 423 | ]);'use strict'; 424 | angular.module('users').controller('SettingsController', [ 425 | '$scope', 426 | '$http', 427 | '$location', 428 | 'Users', 429 | 'Authentication', 430 | function ($scope, $http, $location, Users, Authentication) { 431 | $scope.user = Authentication.user; 432 | // If user is not signed in then redirect back home 433 | if (!$scope.user) 434 | $location.path('/'); 435 | // Check if there are additional accounts 436 | $scope.hasConnectedAdditionalSocialAccounts = function (provider) { 437 | for (var i in $scope.user.additionalProvidersData) { 438 | return true; 439 | } 440 | return false; 441 | }; 442 | // Check if provider is already in use with current user 443 | $scope.isConnectedSocialAccount = function (provider) { 444 | return $scope.user.provider === provider || $scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]; 445 | }; 446 | // Remove a user social account 447 | $scope.removeUserSocialAccount = function (provider) { 448 | $scope.success = $scope.error = null; 449 | $http.delete('/users/accounts', { params: { provider: provider } }).success(function (response) { 450 | // If successful show success message and clear form 451 | $scope.success = true; 452 | $scope.user = Authentication.user = response; 453 | }).error(function (response) { 454 | $scope.error = response.message; 455 | }); 456 | }; 457 | // Update a user profile 458 | $scope.updateUserProfile = function () { 459 | $scope.success = $scope.error = null; 460 | var user = new Users($scope.user); 461 | user.$update(function (response) { 462 | $scope.success = true; 463 | Authentication.user = response; 464 | }, function (response) { 465 | $scope.error = response.data.message; 466 | }); 467 | }; 468 | // Change user password 469 | $scope.changeUserPassword = function () { 470 | $scope.success = $scope.error = null; 471 | $http.post('/users/password', $scope.passwordDetails).success(function (response) { 472 | // If successful show success message and clear form 473 | $scope.success = true; 474 | $scope.passwordDetails = null; 475 | }).error(function (response) { 476 | $scope.error = response.message; 477 | }); 478 | }; 479 | } 480 | ]);'use strict'; 481 | // Authentication service for user variables 482 | angular.module('users').factory('Authentication', [function () { 483 | var _this = this; 484 | _this._data = { user: window.user }; 485 | return _this._data; 486 | }]);'use strict'; 487 | // Users service used for communicating with the users REST endpoint 488 | angular.module('users').factory('Users', [ 489 | '$resource', 490 | function ($resource) { 491 | return $resource('users', {}, { update: { method: 'PUT' } }); 492 | } 493 | ]); -------------------------------------------------------------------------------- /public/dist/application.min.css: -------------------------------------------------------------------------------- 1 | .content{margin-top:50px}.undecorated-link:hover{text-decoration:none}.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}ul{list-style-type:none}li.event.item{font-size:x-large;font-weight:700;padding:10px}h3{color:#d3d3d3}h4{margin-top:20px}span.bad{color:red}span.ok{color:#ff0}span.good{color:green}.event.list,form{margin-top:1em}@media (min-width:992px){.nav-users{position:fixed}}.remove-account-container{display:inline-block;position:relative}.btn-remove-account{top:10px;right:10px;position:absolute} -------------------------------------------------------------------------------- /public/dist/application.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function ratingsRoutes($stateProvider){$stateProvider.state("events",{url:"/EventRatings",templateUrl:"modules/ratings/views/event.list.html",controller:"EventListController"}).state("eventsCreate",{url:"/EventRatings/new",templateUrl:"modules/ratings/views/event.create.html",controller:"EventCreateController"}).state("eventsDetails",{url:"/EventRatings/:eventId",templateUrl:"modules/ratings/views/event.details.html",controller:"EventDetailsController"})}function controller($scope,$stateParams,EventsService){$scope.loading=!0,$scope.getEvent=function(id){EventsService.getSingleEvent(id).then(function(detail){$scope.eventDetails=detail}).finally(function(){$scope.loading=!1})},$scope.getEvent($stateParams.eventId)}function controller($scope,$state,eventService){$scope.loading=!0,$scope.getAllEvents=function(){eventService.getAllEvents().then(function(events){$scope.events=events}).finally(function(){$scope.loading=!1})},$scope.selectEvent=function(id){$state.go("eventsDetails",{eventId:id})},$scope.calculateRatingQuality=function(rating){return rating<2?"bad":rating<=3.5?"ok":"good"},$scope.getAllEvents()}function eventsService($resource,$q){function getData(route,method){var deferred=$q.defer(),addr=["http://localhost:3000/events",route].join("/");return $resource(addr)[method]().$promise.then(function(data){deferred.resolve(data)}).catch(function(error){deferred.reject(error)}),deferred.promise}function create(event){var deferred=$q.defer();return $resource("http://localhost:3000/events").save(event).$promise.then(function(){deferred.resolve()}),deferred.promise}return{getAllEvents:function(){return getData("","query")},getSingleEvent:function(id){return getData(id,"get")},addEvent:create}}var ApplicationConfiguration=function(){var applicationModuleName="copperpitch";return{applicationModuleName:applicationModuleName,applicationModuleVendorDependencies:["ngResource","ngCookies","ngSanitize","ui.router","ui.bootstrap","ui.utils"],registerModule:function(moduleName){angular.module(moduleName,[]),angular.module(applicationModuleName).requires.push(moduleName)}}}();angular.module(ApplicationConfiguration.applicationModuleName,ApplicationConfiguration.applicationModuleVendorDependencies),angular.module(ApplicationConfiguration.applicationModuleName).config(["$locationProvider",function($locationProvider){$locationProvider.hashPrefix("!")}]),angular.element(document).ready(function(){"#_=_"===window.location.hash&&(window.location.hash="#!"),angular.bootstrap(document,[ApplicationConfiguration.applicationModuleName])}),ApplicationConfiguration.registerModule("core"),ApplicationConfiguration.registerModule("ratings"),ApplicationConfiguration.registerModule("users"),angular.module("core").config(["$stateProvider","$urlRouterProvider",function($stateProvider,$urlRouterProvider){$urlRouterProvider.otherwise("/"),$stateProvider.state("home",{url:"/",templateUrl:"modules/core/views/home.client.view.html"})}]),angular.module("core").controller("HeaderController",["$scope","Authentication","Menus",function($scope,Authentication,Menus){$scope.authentication=Authentication,$scope.isCollapsed=!1,$scope.menu=Menus.getMenu("topbar"),$scope.toggleCollapsibleMenu=function(){$scope.isCollapsed=!$scope.isCollapsed},$scope.$on("$stateChangeSuccess",function(){$scope.isCollapsed=!1})}]),angular.module("core").controller("HomeController",["$scope","Authentication",function($scope,Authentication){$scope.authentication=Authentication}]),angular.module("core").service("Menus",[function(){this.defaultRoles=["user"],this.menus={};var shouldRender=function(user){if(!user)return this.isPublic;for(var userRoleIndex in user.roles)for(var roleIndex in this.roles)if(this.roles[roleIndex]===user.roles[userRoleIndex])return!0;return!1};this.validateMenuExistance=function(menuId){if(menuId&&menuId.length){if(this.menus[menuId])return!0;throw new Error("Menu does not exists")}throw new Error("MenuId was not provided")},this.getMenu=function(menuId){return this.validateMenuExistance(menuId),this.menus[menuId]},this.addMenu=function(menuId,isPublic,roles){return this.menus[menuId]={isPublic:isPublic||!1,roles:roles||this.defaultRoles,items:[],shouldRender:shouldRender},this.menus[menuId]},this.removeMenu=function(menuId){this.validateMenuExistance(menuId),delete this.menus[menuId]},this.addMenuItem=function(menuId,menuItemTitle,menuItemURL,menuItemType,menuItemUIRoute,isPublic,roles){return this.validateMenuExistance(menuId),this.menus[menuId].items.push({title:menuItemTitle,link:menuItemURL,menuItemType:menuItemType||"item",menuItemClass:menuItemType,uiRoute:menuItemUIRoute||"/"+menuItemURL,isPublic:isPublic||this.menus[menuId].isPublic,roles:roles||this.defaultRoles,items:[],shouldRender:shouldRender}),this.menus[menuId]},this.addSubMenuItem=function(menuId,rootMenuItemURL,menuItemTitle,menuItemURL,menuItemUIRoute,isPublic,roles){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===rootMenuItemURL&&this.menus[menuId].items[itemIndex].items.push({title:menuItemTitle,link:menuItemURL,uiRoute:menuItemUIRoute||"/"+menuItemURL,isPublic:isPublic||this.menus[menuId].isPublic,roles:roles||this.defaultRoles,shouldRender:shouldRender});return this.menus[menuId]},this.removeMenuItem=function(menuId,menuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===menuItemURL&&this.menus[menuId].items.splice(itemIndex,1);return this.menus[menuId]},this.removeSubMenuItem=function(menuId,submenuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)for(var subitemIndex in this.menus[menuId].items[itemIndex].items)this.menus[menuId].items[itemIndex].items[subitemIndex].link===submenuItemURL&&this.menus[menuId].items[itemIndex].items.splice(subitemIndex,1);return this.menus[menuId]},this.addMenu("topbar")}]),angular.module("ratings").config(["$stateProvider",ratingsRoutes]),function(){function controller($scope,$state,EventsService){$scope.event={},$scope.submit=function(){EventsService.addEvent($scope.event).then(function(){$state.go("events")})}}angular.module("ratings").controller("EventCreateController",["$scope","$state","EventsService",controller])}(),angular.module("ratings").controller("EventDetailsController",["$scope","$stateParams","EventsService",controller]);var app=angular.module("ratings");app.controller("EventListController",["$scope","$state","EventsService",controller]);var app=angular.module("ratings");app.factory("EventsService",["$resource","$q",eventsService]),angular.module("users").config(["$httpProvider",function($httpProvider){$httpProvider.interceptors.push(["$q","$location","Authentication",function($q,$location,Authentication){return{responseError:function(rejection){switch(rejection.status){case 401:Authentication.user=null,$location.path("signin")}return $q.reject(rejection)}}}])}]),angular.module("users").config(["$stateProvider",function($stateProvider){$stateProvider.state("profile",{url:"/settings/profile",templateUrl:"modules/users/views/settings/edit-profile.client.view.html"}).state("password",{url:"/settings/password",templateUrl:"modules/users/views/settings/change-password.client.view.html"}).state("accounts",{url:"/settings/accounts",templateUrl:"modules/users/views/settings/social-accounts.client.view.html"}).state("signup",{url:"/signup",templateUrl:"modules/users/views/signup.client.view.html"}).state("signin",{url:"/signin",templateUrl:"modules/users/views/signin.client.view.html"})}]),angular.module("users").controller("AuthenticationController",["$scope","$http","$location","Authentication",function($scope,$http,$location,Authentication){$scope.authentication=Authentication,$scope.authentication.user&&$location.path("/"),$scope.signup=function(){$http.post("/auth/signup",$scope.credentials).success(function(response){$scope.authentication.user=response,$location.path("/")}).error(function(response){$scope.error=response.message})},$scope.signin=function(){$http.post("/auth/signin",$scope.credentials).success(function(response){$scope.authentication.user=response,$location.path("/")}).error(function(response){$scope.error=response.message})}}]),angular.module("users").controller("SettingsController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.user||$location.path("/"),$scope.hasConnectedAdditionalSocialAccounts=function(provider){for(var i in $scope.user.additionalProvidersData)return!0;return!1},$scope.isConnectedSocialAccount=function(provider){return $scope.user.provider===provider||$scope.user.additionalProvidersData&&$scope.user.additionalProvidersData[provider]},$scope.removeUserSocialAccount=function(provider){$scope.success=$scope.error=null,$http.delete("/users/accounts",{params:{provider:provider}}).success(function(response){$scope.success=!0,$scope.user=Authentication.user=response}).error(function(response){$scope.error=response.message})},$scope.updateUserProfile=function(){$scope.success=$scope.error=null,new Users($scope.user).$update(function(response){$scope.success=!0,Authentication.user=response},function(response){$scope.error=response.data.message})},$scope.changeUserPassword=function(){$scope.success=$scope.error=null,$http.post("/users/password",$scope.passwordDetails).success(function(response){$scope.success=!0,$scope.passwordDetails=null}).error(function(response){$scope.error=response.message})}}]),angular.module("users").factory("Authentication",[function(){var _this=this;return _this._data={user:window.user},_this._data}]),angular.module("users").factory("Users",["$resource",function($resource){return $resource("users",{},{update:{method:"PUT"}})}]); -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | jQuery, Modernizr 16 | -------------------------------------------------------------------------------- /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 | ]); -------------------------------------------------------------------------------- /public/modules/core/controllers/header.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus', 4 | function($scope, Authentication, Menus) { 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 | ]); -------------------------------------------------------------------------------- /public/modules/core/controllers/home.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | angular.module('core').controller('HomeController', ['$scope', 'Authentication', 5 | function($scope, Authentication) { 6 | // This provides Authentication context. 7 | $scope.authentication = Authentication; 8 | } 9 | ]); -------------------------------------------------------------------------------- /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/core.css: -------------------------------------------------------------------------------- 1 | .content { 2 | margin-top: 50px; 3 | } 4 | .undecorated-link:hover { 5 | text-decoration: none; 6 | } 7 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 8 | display: none !important; 9 | } 10 | -------------------------------------------------------------------------------- /public/modules/core/img/brand/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylonr/intro-to-protractor/c2ffb4b4def5b72a66079abae03628eeabc9d43a/public/modules/core/img/brand/favicon.ico -------------------------------------------------------------------------------- /public/modules/core/img/brand/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylonr/intro-to-protractor/c2ffb4b4def5b72a66079abae03628eeabc9d43a/public/modules/core/img/brand/logo.png -------------------------------------------------------------------------------- /public/modules/core/img/loaders/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylonr/intro-to-protractor/c2ffb4b4def5b72a66079abae03628eeabc9d43a/public/modules/core/img/loaders/loader.gif -------------------------------------------------------------------------------- /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 | function() { 6 | // Define a set of default roles 7 | this.defaultRoles = ['user']; 8 | 9 | // Define the menus object 10 | this.menus = {}; 11 | 12 | // A private function for rendering decision 13 | var shouldRender = function(user) { 14 | if (user) { 15 | for (var userRoleIndex in user.roles) { 16 | for (var roleIndex in this.roles) { 17 | if (this.roles[roleIndex] === user.roles[userRoleIndex]) { 18 | return true; 19 | } 20 | } 21 | } 22 | } else { 23 | return this.isPublic; 24 | } 25 | 26 | return false; 27 | }; 28 | 29 | // Validate menu existance 30 | this.validateMenuExistance = function(menuId) { 31 | if (menuId && menuId.length) { 32 | if (this.menus[menuId]) { 33 | return true; 34 | } else { 35 | throw new Error('Menu does not exists'); 36 | } 37 | } else { 38 | throw new Error('MenuId was not provided'); 39 | } 40 | 41 | return false; 42 | }; 43 | 44 | // Get the menu object by menu id 45 | this.getMenu = function(menuId) { 46 | // Validate that the menu exists 47 | this.validateMenuExistance(menuId); 48 | 49 | // Return the menu object 50 | return this.menus[menuId]; 51 | }; 52 | 53 | // Add new menu object by menu id 54 | this.addMenu = function(menuId, isPublic, roles) { 55 | // Create the new menu 56 | this.menus[menuId] = { 57 | isPublic: isPublic || false, 58 | roles: roles || this.defaultRoles, 59 | items: [], 60 | shouldRender: shouldRender 61 | }; 62 | 63 | // Return the menu object 64 | return this.menus[menuId]; 65 | }; 66 | 67 | // Remove existing menu object by menu id 68 | this.removeMenu = function(menuId) { 69 | // Validate that the menu exists 70 | this.validateMenuExistance(menuId); 71 | 72 | // Return the menu object 73 | delete this.menus[menuId]; 74 | }; 75 | 76 | // Add menu item object 77 | this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles) { 78 | // Validate that the menu exists 79 | this.validateMenuExistance(menuId); 80 | 81 | // Push new menu item 82 | this.menus[menuId].items.push({ 83 | title: menuItemTitle, 84 | link: menuItemURL, 85 | menuItemType: menuItemType || 'item', 86 | menuItemClass: menuItemType, 87 | uiRoute: menuItemUIRoute || ('/' + menuItemURL), 88 | isPublic: isPublic || this.menus[menuId].isPublic, 89 | roles: roles || this.defaultRoles, 90 | items: [], 91 | shouldRender: shouldRender 92 | }); 93 | 94 | // Return the menu object 95 | return this.menus[menuId]; 96 | }; 97 | 98 | // Add submenu item object 99 | this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles) { 100 | // Validate that the menu exists 101 | this.validateMenuExistance(menuId); 102 | 103 | // Search for menu item 104 | for (var itemIndex in this.menus[menuId].items) { 105 | if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) { 106 | // Push new submenu item 107 | this.menus[menuId].items[itemIndex].items.push({ 108 | title: menuItemTitle, 109 | link: menuItemURL, 110 | uiRoute: menuItemUIRoute || ('/' + menuItemURL), 111 | isPublic: isPublic || this.menus[menuId].isPublic, 112 | roles: roles || this.defaultRoles, 113 | shouldRender: shouldRender 114 | }); 115 | } 116 | } 117 | 118 | // Return the menu object 119 | return this.menus[menuId]; 120 | }; 121 | 122 | // Remove existing menu object by menu id 123 | this.removeMenuItem = function(menuId, menuItemURL) { 124 | // Validate that the menu exists 125 | this.validateMenuExistance(menuId); 126 | 127 | // Search for menu item to remove 128 | for (var itemIndex in this.menus[menuId].items) { 129 | if (this.menus[menuId].items[itemIndex].link === menuItemURL) { 130 | this.menus[menuId].items.splice(itemIndex, 1); 131 | } 132 | } 133 | 134 | // Return the menu object 135 | return this.menus[menuId]; 136 | }; 137 | 138 | // Remove existing menu object by menu id 139 | this.removeSubMenuItem = function(menuId, submenuItemURL) { 140 | // Validate that the menu exists 141 | this.validateMenuExistance(menuId); 142 | 143 | // Search for menu item to remove 144 | for (var itemIndex in this.menus[menuId].items) { 145 | for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { 146 | if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { 147 | this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); 148 | } 149 | } 150 | } 151 | 152 | // Return the menu object 153 | return this.menus[menuId]; 154 | }; 155 | 156 | //Adding the topbar menu 157 | this.addMenu('topbar'); 158 | } 159 | ]); -------------------------------------------------------------------------------- /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 | })(); -------------------------------------------------------------------------------- /public/modules/core/tests/home.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('HomeController', function() { 5 | //Initialize global variables 6 | var scope, 7 | HomeController; 8 | 9 | // Load the main application module 10 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 11 | 12 | beforeEach(inject(function($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | 15 | HomeController = $controller('HomeController', { 16 | $scope: scope 17 | }); 18 | })); 19 | 20 | it('should expose the authentication service', function() { 21 | expect(scope.authentication).toBeTruthy(); 22 | }); 23 | }); 24 | })(); -------------------------------------------------------------------------------- /public/modules/core/views/header.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 11 | 58 |
-------------------------------------------------------------------------------- /public/modules/core/views/home.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | MEAN.JS 6 |
7 |
8 |
9 |
10 |

11 | Open-Source Full-Stack Solution For MEAN Applications 12 |

13 |
14 |
15 |

16 | Learn more 17 |

18 |
19 |
20 |
21 |

Congrats! You've configured and run the sample application.

22 |

MEAN.JS is a web application boilerplate, which means you should start changing everything :-)

23 |

This sample application tracks users and articles.

24 |
    25 |
  • 26 | Click 27 | Signup 28 | to get started. 29 |
  • 30 |
  • 31 | Configure your app to work with your social accounts, by editing the 32 | /config/env/*.js 33 | files. 34 |
  • 35 |
  • 36 | Edit your users module. 37 |
  • 38 |
  • 39 | Add new CRUD modules. 40 |
  • 41 |
  • 42 | Have fun... 43 |
  • 44 |
45 |
46 |
47 |
48 |

49 | MongoDB 50 |

51 |

MongoDB is a database. MongoDB's great manual, to get started with NoSQL and MongoDB.

52 |
53 |
54 |

55 | Express 56 |

57 |

Express is an app server. Check out The Express Guide or StackOverflow for more info.

58 |
59 |
60 |

61 | AngularJS 62 |

63 |

AngularJS is web app framework. Angular's webiste offers alot. The Thinkster Popular Guide and Egghead Videos are great resources.

64 |
65 |
66 |

67 | Node.js 68 |

69 |

Node.js is a web server. Node's website and this stackOverflow thread, are great resources.

70 |
71 |
72 |
73 |

MEAN.JS Documentation

74 |

75 | Once you're familiar with the foundation technology, check out the MEAN.JS Documentation: 76 |

90 |

91 |
92 |
Enjoy & Keep Us Updated, 93 |
The MEAN.JS Team. 94 |
-------------------------------------------------------------------------------- /public/modules/ratings/config/ratings.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('ratings').config(['$stateProvider', ratingsRoutes]); 4 | 5 | function ratingsRoutes($stateProvider){ 6 | $stateProvider 7 | .state('events', { 8 | url: '/EventRatings', 9 | templateUrl: 'modules/ratings/views/event.list.html', 10 | controller: 'EventListController' 11 | }) 12 | .state('eventsCreate', { 13 | url: '/EventRatings/new', 14 | templateUrl: 'modules/ratings/views/event.create.html', 15 | controller: 'EventCreateController' 16 | }) 17 | .state('eventsDetails', { 18 | url: '/EventRatings/:eventId', 19 | templateUrl: 'modules/ratings/views/event.details.html', 20 | controller: 'EventDetailsController' 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /public/modules/ratings/controllers/event.create.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('ratings').controller('EventCreateController', 5 | ['$scope', '$state', 'EventsService', controller]); 6 | 7 | function controller($scope, $state, EventsService){ 8 | $scope.event = {}; 9 | 10 | $scope.submit = function(){ 11 | EventsService.addEvent($scope.event).then(function(){ 12 | $state.go('events'); 13 | }); 14 | }; 15 | } 16 | 17 | }()); 18 | -------------------------------------------------------------------------------- /public/modules/ratings/controllers/event.details.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('ratings').controller('EventDetailsController', 4 | ['$scope','$stateParams', 'EventsService', controller]); 5 | 6 | function controller($scope, $stateParams, EventsService){ 7 | $scope.loading = true; 8 | 9 | $scope.getEvent = function(id){ 10 | EventsService.getSingleEvent(id) 11 | .then(function(detail){ 12 | $scope.eventDetails = detail; 13 | }) 14 | .finally(function(){ 15 | $scope.loading = false; 16 | }); 17 | }; 18 | 19 | $scope.getEvent($stateParams.eventId); 20 | } 21 | -------------------------------------------------------------------------------- /public/modules/ratings/controllers/event.list.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = angular.module('ratings'); 4 | 5 | app.controller('EventListController', 6 | ['$scope', '$state', 'EventsService', controller]); 7 | 8 | function controller($scope, $state, eventService){ 9 | $scope.loading = true; 10 | 11 | $scope.getAllEvents = function(){ 12 | eventService.getAllEvents().then(function(events){ 13 | $scope.events = events; 14 | }).finally(function(){ 15 | $scope.loading = false; 16 | }); 17 | }; 18 | 19 | $scope.selectEvent = function(id){ 20 | $state.go('eventsDetails', {eventId: id}); 21 | }; 22 | 23 | $scope.calculateRatingQuality = function(rating){ 24 | if(rating < 2){ 25 | return 'bad'; 26 | } else if (rating <= 3.5){ 27 | return 'ok'; 28 | } else { 29 | return 'good'; 30 | } 31 | }; 32 | 33 | $scope.getAllEvents(); 34 | } 35 | -------------------------------------------------------------------------------- /public/modules/ratings/css/ratings.css: -------------------------------------------------------------------------------- 1 | ul{ 2 | list-style-type: none; 3 | } 4 | 5 | li.event.item{ 6 | font-size: x-large; 7 | font-weight: bold; 8 | padding: 10px; 9 | } 10 | 11 | h3{ 12 | color: lightgray; 13 | } 14 | 15 | h4{ 16 | margin-top: 20px; 17 | } 18 | 19 | span.bad{ 20 | color: red; 21 | } 22 | 23 | span.ok{ 24 | color: yellow; 25 | } 26 | 27 | span.good{ 28 | color: green; 29 | } 30 | 31 | form{ 32 | margin-top: 1em; 33 | } 34 | 35 | .event.list{ 36 | margin-top: 1em; 37 | } 38 | -------------------------------------------------------------------------------- /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/ratings/services/events.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = angular.module('ratings'); 4 | 5 | app.factory('EventsService', ['$resource', '$q', eventsService]); 6 | 7 | function eventsService($resource, $q){ 8 | 9 | function getData(route, method){ 10 | var deferred = $q.defer(); 11 | 12 | var addr = ['http://localhost:3000/events', 13 | route].join('/'); 14 | 15 | $resource(addr)[method]() 16 | .$promise 17 | .then(function(data){ 18 | deferred.resolve(data); 19 | }) 20 | .catch(function(error){ 21 | deferred.reject(error); 22 | }); 23 | 24 | return deferred.promise; 25 | } 26 | 27 | function create(event){ 28 | var deferred = $q.defer(); 29 | 30 | $resource('http://localhost:3000/events').save(event).$promise 31 | .then(function(){ 32 | deferred.resolve(); 33 | }); 34 | 35 | return deferred.promise; 36 | } 37 | 38 | return{ 39 | getAllEvents: function(){ 40 | return getData('', 'query'); 41 | }, 42 | 43 | getSingleEvent: function(id){ 44 | return getData(id, 'get'); 45 | }, 46 | 47 | addEvent: create 48 | }; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /public/modules/ratings/tests/event.create.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Event Create Controller: ', function(){ 4 | var EventService, 5 | $q, 6 | $rootScope, 7 | serviceSpy, 8 | deferred, 9 | $state, 10 | scope; 11 | 12 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 13 | 14 | beforeEach(module(function($provide){ 15 | EventService = { 16 | addEvent: function(){} 17 | }; 18 | 19 | $provide.value('EventsService', EventService); 20 | })); 21 | 22 | beforeEach(inject(function(_$rootScope_, $controller, _$q_, 23 | $httpBackend, _$state_){ 24 | 25 | $rootScope = _$rootScope_; 26 | scope = $rootScope.$new(); 27 | 28 | $httpBackend.whenGET('modules/core/views/home.client.view.html') 29 | .respond(200); 30 | 31 | $state = _$state_; 32 | spyOn($state, 'go'); 33 | 34 | $q = _$q_; 35 | deferred = $q.defer(); 36 | 37 | spyOn(EventService, 'addEvent').and.returnValue(deferred.promise); 38 | 39 | $controller('EventCreateController', { 40 | $scope: scope 41 | }); 42 | })); 43 | 44 | describe('When saving data', function(){ 45 | it('Should call to the service', function(){ 46 | scope.submit(); 47 | expect(EventService.addEvent).toHaveBeenCalled(); 48 | }); 49 | 50 | it('Should redirect to the list page', function(){ 51 | scope.submit(); 52 | deferred.resolve(); 53 | 54 | $rootScope.$digest(); 55 | expect($state.go).toHaveBeenCalledWith('events'); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /public/modules/ratings/tests/event.details.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Event Details Controller', function(){ 4 | var EventService, 5 | $q, 6 | $rootScope, 7 | serviceSpy, 8 | deferred, 9 | $state, 10 | scope; 11 | 12 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 13 | 14 | beforeEach(module(function($provide){ 15 | EventService = { 16 | getSingleEvent: function(){} 17 | }; 18 | 19 | $provide.value('EventsService', EventService); 20 | })); 21 | 22 | beforeEach(inject(function(_$rootScope_, $controller, _$q_, 23 | $httpBackend, _$state_){ 24 | 25 | $rootScope = _$rootScope_; 26 | scope = $rootScope.$new(); 27 | 28 | $httpBackend.whenGET('modules/core/views/home.client.view.html') 29 | .respond(200); 30 | 31 | $state = _$state_; 32 | spyOn($state, 'go'); 33 | 34 | $q = _$q_; 35 | deferred = $q.defer(); 36 | 37 | spyOn(EventService, 'getSingleEvent').and.returnValue(deferred.promise); 38 | 39 | $controller('EventDetailsController', { 40 | $scope: scope 41 | }); 42 | })); 43 | 44 | describe('Scope', function(){ 45 | it('Should initialize loading to true', function(){ 46 | expect(scope.loading).toBeTruthy(); 47 | }); 48 | }); 49 | 50 | describe('Fetching data', function(){ 51 | it('Should make a call to the service', function(){ 52 | scope.getEvent(1); 53 | expect(EventService.getSingleEvent).toHaveBeenCalled(); 54 | }); 55 | 56 | it('Should set loading to false when done', function(){ 57 | scope.getEvent(1); 58 | deferred.resolve(); 59 | $rootScope.$digest(); 60 | 61 | expect(scope.loading).toBeFalsy(); 62 | }); 63 | 64 | it('Should set the event details', function(){ 65 | scope.getEvent(1); 66 | deferred.resolve({name: 'test event'}); 67 | $rootScope.$digest(); 68 | 69 | expect(scope.eventDetails.name).toEqual('test event'); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /public/modules/ratings/tests/event.list.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Event List Controller', function(){ 4 | var EventService, 5 | $q, 6 | $rootScope, 7 | serviceSpy, 8 | deferred, 9 | $state, 10 | scope; 11 | 12 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 13 | 14 | beforeEach(module(function($provide){ 15 | EventService = { 16 | getAllEvents: function(){} 17 | }; 18 | 19 | $provide.value('EventsService', EventService); 20 | })); 21 | 22 | beforeEach(inject(function(_$rootScope_, $controller, _$q_, 23 | $httpBackend, _$state_){ 24 | 25 | $rootScope = _$rootScope_; 26 | scope = $rootScope.$new(); 27 | 28 | $httpBackend.whenGET('modules/core/views/home.client.view.html') 29 | .respond(200); 30 | 31 | $state = _$state_; 32 | spyOn($state, 'go'); 33 | 34 | $q = _$q_; 35 | deferred = $q.defer(); 36 | 37 | spyOn(EventService, 'getAllEvents').and.returnValue(deferred.promise); 38 | 39 | $controller('EventListController', { 40 | $scope: scope 41 | }); 42 | })); 43 | 44 | describe('Fetching events', function(){ 45 | function sendDataFromService(){ 46 | deferred.resolve([{name: 'event test'}]); 47 | 48 | $rootScope.$digest(); 49 | } 50 | 51 | it('Should populate events list from service', function(){ 52 | 53 | scope.getAllEvents(); 54 | 55 | sendDataFromService(); 56 | 57 | expect(scope.events[0].name).toEqual('event test'); 58 | }); 59 | 60 | it('Should set loading to false when data comes back', function(){ 61 | scope.getAllEvents(); 62 | 63 | scope.loading = true; 64 | 65 | sendDataFromService(); 66 | 67 | expect(scope.loading).toBeFalsy(); 68 | }); 69 | 70 | it('Should set loading to false when service fails', function(){ 71 | scope.getAllEvents(); 72 | scope.loading = true; 73 | 74 | deferred.reject(); 75 | $rootScope.$digest(); 76 | 77 | expect(scope.loading).toBeFalsy(); 78 | }); 79 | }); 80 | 81 | describe('Controller Scope', function(){ 82 | it('Should initialize loading to true', function(){ 83 | expect(scope.loading).toBeTruthy(); 84 | }); 85 | }); 86 | 87 | describe('When loading controller', function(){ 88 | it('Should fetch events', function(){ 89 | expect(EventService.getAllEvents).toHaveBeenCalled(); 90 | }); 91 | }); 92 | 93 | describe('When selecting an item', function(){ 94 | it('should navigate to the detail state', function(){ 95 | scope.selectEvent(1); 96 | expect($state.go).toHaveBeenCalledWith('eventsDetails', 97 | {eventId: 1}); 98 | }); 99 | }); 100 | 101 | describe('When calculating rating quality', function(){ 102 | it('should return bad when avg rating < 2', function(){ 103 | var quality = scope.calculateRatingQuality(1.9); 104 | expect(quality).toEqual('bad'); 105 | }); 106 | 107 | it('should return ok when between 2 and 3.5', function(){ 108 | var quality = scope.calculateRatingQuality(3.5); 109 | expect(quality).toEqual('ok'); 110 | }); 111 | 112 | it('should return good when > 3.5', function(){ 113 | var quality = scope.calculateRatingQuality(3.6); 114 | expect(quality).toEqual('good'); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /public/modules/ratings/tests/events.service.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Events Service', function(){ 4 | var $httpBackend, 5 | service, 6 | eventsUrl = 'http://localhost:3000/events'; 7 | 8 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 9 | 10 | beforeEach(inject(function(_$httpBackend_, EventsService){ 11 | $httpBackend = _$httpBackend_; 12 | service = EventsService; 13 | })); 14 | 15 | describe('When getting all events', function(){ 16 | it('Should make a call to the API', function(){ 17 | $httpBackend.expectGET(eventsUrl).respond(200); 18 | 19 | service.getAllEvents(); 20 | 21 | $httpBackend.verifyNoOutstandingExpectation(); 22 | }); 23 | 24 | it('Should send an error when API fails', function(){ 25 | $httpBackend.whenGET(eventsUrl).respond(500); 26 | 27 | var err; 28 | 29 | service.getAllEvents().catch(function(e){ 30 | err = e; 31 | }); 32 | 33 | $httpBackend.flush(); 34 | 35 | expect(err).toBeDefined(); 36 | }); 37 | 38 | it('Should send data when API is successful', function(){ 39 | $httpBackend.whenGET(eventsUrl) 40 | .respond(200, [{name: 'test event'}]); 41 | 42 | var data; 43 | 44 | service.getAllEvents().then(function(d){ 45 | data = d; 46 | }); 47 | 48 | $httpBackend.flush(); 49 | 50 | expect(data[0].name).toEqual('test event'); 51 | }); 52 | }); 53 | 54 | describe('When fetching a single event', function(){ 55 | it('Should pass the ID to the API', function(){ 56 | $httpBackend.expectGET(eventsUrl + '/1') 57 | .respond(200); 58 | 59 | service.getSingleEvent(1); 60 | 61 | $httpBackend.verifyNoOutstandingExpectation(); 62 | }); 63 | 64 | it('Should send back error when API fails', function(){ 65 | $httpBackend.whenGET(eventsUrl + '/1').respond(500); 66 | 67 | var err; 68 | 69 | service.getSingleEvent(1).catch(function(e){ 70 | err = e; 71 | }); 72 | 73 | $httpBackend.flush(); 74 | 75 | expect(err).toBeDefined(); 76 | }); 77 | 78 | it('Should send back data from successful API', function(){ 79 | $httpBackend.whenGET(eventsUrl + '/1') 80 | .respond(200, {name: 'test event'}); 81 | 82 | var data; 83 | 84 | service.getSingleEvent(1).then(function(d){ 85 | data = d; 86 | }); 87 | 88 | $httpBackend.flush(); 89 | 90 | expect(data.name).toEqual('test event'); 91 | }); 92 | }); 93 | 94 | describe('When creating a new event', function(){ 95 | it('Should call out to the API', function(){ 96 | $httpBackend.whenPOST(eventsUrl).respond(201); 97 | 98 | var success; 99 | 100 | service.addEvent({}).then(function(d){ 101 | success = true; 102 | }); 103 | 104 | $httpBackend.flush(); 105 | 106 | expect(success).toBeTruthy(); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /public/modules/ratings/views/event.create.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /public/modules/ratings/views/event.details.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{eventDetails.name}} 4 | {{eventDetails.averageRating}} / 5 5 |

6 | 7 |

{{eventDetails.description}}

8 |
9 | 10 |
11 |

Ratings:

12 |
    13 |
  • 14 | {{r.description}} 15 | {{r.rating}} / 5 16 |
  • 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /public/modules/ratings/views/event.list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | New Event 5 |
6 |
7 |
8 |
9 |
    10 |
  • 11 | {{e.name}} 12 | 13 | {{e.averageRating}} / 5 14 | 15 | 16 | Not Yet Rated 17 | 18 |
  • 19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /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 | ]); -------------------------------------------------------------------------------- /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/signup.client.view.html' 23 | }). 24 | state('signin', { 25 | url: '/signin', 26 | templateUrl: 'modules/users/views/signin.client.view.html' 27 | }); 28 | } 29 | ]); -------------------------------------------------------------------------------- /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 | ]); -------------------------------------------------------------------------------- /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() { 43 | $scope.success = $scope.error = null; 44 | var user = new Users($scope.user); 45 | 46 | user.$update(function(response) { 47 | $scope.success = true; 48 | Authentication.user = response; 49 | }, function(response) { 50 | $scope.error = response.data.message; 51 | }); 52 | }; 53 | 54 | // Change user password 55 | $scope.changeUserPassword = function() { 56 | $scope.success = $scope.error = null; 57 | 58 | $http.post('/users/password', $scope.passwordDetails).success(function(response) { 59 | // If successful show success message and clear form 60 | $scope.success = true; 61 | $scope.passwordDetails = null; 62 | }).error(function(response) { 63 | $scope.error = response.message; 64 | }); 65 | }; 66 | } 67 | ]); -------------------------------------------------------------------------------- /public/modules/users/css/users.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | .nav-users { 3 | position: fixed; 4 | } 5 | } 6 | 7 | .remove-account-container { 8 | display: inline-block; 9 | position: relative; 10 | } 11 | 12 | .btn-remove-account { 13 | top: 10px; 14 | right: 10px; 15 | position: absolute; 16 | } 17 | -------------------------------------------------------------------------------- /public/modules/users/img/buttons/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylonr/intro-to-protractor/c2ffb4b4def5b72a66079abae03628eeabc9d43a/public/modules/users/img/buttons/facebook.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylonr/intro-to-protractor/c2ffb4b4def5b72a66079abae03628eeabc9d43a/public/modules/users/img/buttons/google.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylonr/intro-to-protractor/c2ffb4b4def5b72a66079abae03628eeabc9d43a/public/modules/users/img/buttons/linkedin.png -------------------------------------------------------------------------------- /public/modules/users/img/buttons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taylonr/intro-to-protractor/c2ffb4b4def5b72a66079abae03628eeabc9d43a/public/modules/users/img/buttons/twitter.png -------------------------------------------------------------------------------- /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 | 6 | function() { 7 | var _this = this; 8 | 9 | _this._data = { 10 | user: window.user 11 | }; 12 | 13 | return _this._data; 14 | } 15 | ]); -------------------------------------------------------------------------------- /public/modules/users/services/users.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Users service used for communicating with the users REST endpoint 4 | angular.module('users').factory('Users', ['$resource', 5 | function($resource) { 6 | return $resource('users', {}, { 7 | update: { 8 | method: 'PUT' 9 | } 10 | }); 11 | } 12 | ]); -------------------------------------------------------------------------------- /public/modules/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 | 51 | // test expected GET request 52 | $httpBackend.when('POST', '/auth/signin').respond(200, 'Fred'); 53 | scope.signin(); 54 | $httpBackend.flush(); 55 | // test scope value 56 | expect(scope.authentication.user).toEqual('Fred'); 57 | expect($location.url()).toEqual('/'); 58 | }); 59 | 60 | it('$scope.signin() should fail to log in with nothing', function() { 61 | $httpBackend.expectPOST('/auth/signin').respond(400, { 62 | 'message': 'Missing credentials' 63 | }); 64 | scope.signin(); 65 | $httpBackend.flush(); 66 | // test scope value 67 | expect(scope.error).toEqual('Missing credentials'); 68 | }); 69 | 70 | it('$scope.signin() should fail to log in with wrong credentials', function() { 71 | // Foo/Bar combo assumed to not exist 72 | scope.authentication.user = 'Foo'; 73 | scope.credentials = 'Bar'; 74 | $httpBackend.expectPOST('/auth/signin').respond(400, { 75 | 'message': 'Unknown user' 76 | }); 77 | scope.signin(); 78 | $httpBackend.flush(); 79 | // test scope value 80 | expect(scope.error).toEqual('Unknown user'); 81 | }); 82 | 83 | it('$scope.signup() should register with correct data', function() { 84 | 85 | // test expected GET request 86 | scope.authentication.user = 'Fred'; 87 | $httpBackend.when('POST', '/auth/signup').respond(200, 'Fred'); 88 | scope.signup(); 89 | $httpBackend.flush(); 90 | // test scope value 91 | expect(scope.authentication.user).toBe('Fred'); 92 | expect(scope.error).toEqual(undefined); 93 | expect($location.url()).toBe('/'); 94 | }); 95 | 96 | it('$scope.signup() should fail to register with duplicate Username', function() { 97 | $httpBackend.when('POST', '/auth/signup').respond(400, { 98 | 'message': 'Username already exists' 99 | }); 100 | scope.signup(); 101 | $httpBackend.flush(); 102 | // test scope value 103 | expect(scope.error).toBe('Username already exists'); 104 | }); 105 | 106 | 107 | }); 108 | }()); -------------------------------------------------------------------------------- /public/modules/users/users.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use Applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('users'); 5 | -------------------------------------------------------------------------------- /public/modules/users/views/settings/change-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Change your password

3 |
4 | 29 |
30 |
-------------------------------------------------------------------------------- /public/modules/users/views/settings/edit-profile.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Edit your profile

3 |
4 | 33 |
34 |
-------------------------------------------------------------------------------- /public/modules/users/views/settings/social-accounts.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Connected social accounts:

3 |
4 | 10 |
11 |

Connect other social accounts:

12 | 26 |
-------------------------------------------------------------------------------- /public/modules/users/views/signin.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Sign in using your social accounts

3 |
4 | 5 | 6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /public/modules/users/views/signup.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Sign up using your social accounts

3 |
4 | 5 | 6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Module dependencies. 4 | */ 5 | var init = require('./config/init')(), 6 | config = require('./config/config'), 7 | mongoose = require('mongoose'); 8 | 9 | /** 10 | * Main application entry file. 11 | * Please note that the order of loading is important. 12 | */ 13 | 14 | // Bootstrap db connection 15 | var db = mongoose.connect(config.db); 16 | 17 | // Init the express application 18 | var app = require('./config/express')(db); 19 | 20 | // Bootstrap passport config 21 | require('./config/passport')(); 22 | 23 | // Start the app by listening on 24 | app.listen(config.port); 25 | 26 | // Expose app 27 | exports = module.exports = app; 28 | 29 | // Logging initialization 30 | console.log('MEAN.JS application started on port ' + config.port); --------------------------------------------------------------------------------