├── .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 | [](http://meanjs.org/)
12 |
13 | [](https://travis-ci.org/meanjs/mean)
14 | [](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 |

6 |
7 |
8 |
9 |
10 |
11 | Open-Source Full-Stack Solution For MEAN Applications
12 |
13 |
14 |
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 |
59 |
65 |
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 |
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 |
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 |
30 |
--------------------------------------------------------------------------------
/public/modules/users/views/settings/edit-profile.client.view.html:
--------------------------------------------------------------------------------
1 |
2 | Edit your profile
3 |
34 |
--------------------------------------------------------------------------------
/public/modules/users/views/settings/social-accounts.client.view.html:
--------------------------------------------------------------------------------
1 |
2 | Connected social accounts:
3 |
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);
--------------------------------------------------------------------------------