diff --git a/.bowerrc b/.bowerrc
new file mode 100644
index 0000000..1b84277
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "public/lib"
+}
diff --git a/.csslintrc b/.csslintrc
new file mode 100644
index 0000000..3911d67
--- /dev/null
+++ b/.csslintrc
@@ -0,0 +1,15 @@
+{
+ "adjoining-classes": false,
+ "box-model": false,
+ "box-sizing": false,
+ "floats": false,
+ "font-sizes": false,
+ "important": false,
+ "known-properties": false,
+ "overqualified-elements": false,
+ "qualified-headings": false,
+ "regex-selectors": false,
+ "unique-headings": false,
+ "universal-selector": false,
+ "unqualified-attributes": false
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ade4e05
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+.nodemonignore
+.sass-cache/
+npm-debug.log
+node_modules/
+public/lib
+app/tests/coverage/
+.bower-*/
+.idea/
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..4cd07cd
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,42 @@
+{
+ "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
+ "browser": true, // Standard browser globals e.g. `window`, `document`.
+ "esnext": true, // Allow ES.next specific features such as `const` and `let`.
+ "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
+ "camelcase": false, // Permit only camelcase for `var` and `object indexes`.
+ "curly": false, // Require {} for every new block or scope.
+ "eqeqeq": true, // Require triple equals i.e. `===`.
+ "immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
+ "latedef": true, // Prohibit variable use before definition.
+ "newcap": true, // Require capitalization of all constructor functions e.g. `new F()`.
+ "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
+ "quotmark": "single", // Define quotes to string values.
+ "regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
+ "undef": true, // Require all non-global variables be declared before they are used.
+ "unused": false, // Warn unused variables.
+ "strict": true, // Require `use strict` pragma in every file.
+ "trailing": true, // Prohibit trailing whitespaces.
+ "smarttabs": false, // Suppresses warnings about mixed tabs and spaces
+ "globals": { // Globals variables.
+ "jasmine": true,
+ "angular": true,
+ "ApplicationConfiguration": true
+ },
+ "predef": [ // Extra globals.
+ "define",
+ "require",
+ "exports",
+ "module",
+ "describe",
+ "before",
+ "beforeEach",
+ "after",
+ "afterEach",
+ "it",
+ "inject",
+ "expect"
+ ],
+ "indent": 4, // Specify indentation spacing
+ "devel": true, // Allow development statements e.g. `console.log();`.
+ "noempty": true // Prohibit use of empty blocks.
+}
\ No newline at end of file
diff --git a/.slugignore b/.slugignore
new file mode 100644
index 0000000..e4e50ba
--- /dev/null
+++ b/.slugignore
@@ -0,0 +1 @@
+/app/tests
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..0abd7e4
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+ - "0.10"
+env:
+ - NODE_ENV=travis
+services:
+ - mongodb
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..b366c5d
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+## License
+(The MIT License)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Procfile b/Procfile
new file mode 100755
index 0000000..3360097
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: ./node_modules/.bin/forever -m 5 server.js
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..05ac4b3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,117 @@
+[](http://meanjs.org/)
+
+[](https://travis-ci.org/meanjs/mean)
+[](https://david-dm.org/meanjs/mean)
+
+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.
+
+## Before You Begin
+Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application:
+* 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.
+* 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.
+* 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/).
+* 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.
+
+
+## Prerequisites
+Make sure you have installed all these prerequisites on your development machine.
+* 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.
+* MongoDB - [Download & Install MongoDB](http://www.mongodb.org/downloads), and make sure it's running on the default port (27017).
+* 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:
+
+```
+$ npm install -g bower
+```
+
+* 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:
+
+```
+$ sudo npm install -g grunt-cli
+```
+
+## Downloading MEAN.JS
+There are several ways you can get the MEAN.JS boilerplate:
+
+### Yo Generator
+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.
+
+### Cloning The GitHub Repository
+You can also use Git to directly clone the MEAN.JS repository:
+```
+$ git clone https://github.com/meanjs/mean.git meanjs
+```
+This will clone the latest version of the MEAN.JS repository to a **meanjs** folder.
+
+### Downloading The Repository Zip File
+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:
+```
+$ wget https://github.com/meanjs/mean/archive/master.zip -O meanjs.zip; unzip meanjs.zip; rm meanjs.zip
+```
+Don't forget to rename **mean-master** after your project name.
+
+## Quick Install
+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.
+
+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.
+
+To install Node.js dependencies you're going to use npm again, in the application folder run this in the command-line:
+
+```
+$ npm install
+```
+
+This command does a few things:
+* First it will install the dependencies needed for the application to run.
+* If you're running in a development environment, it will then also install development dependencies needed for testing and running your application.
+* Finally, when the install process is over, npm will initiate a bower installcommand to install all the front-end modules needed for the application
+
+## Running Your Application
+After the install process is over, you'll be able to run your application using Grunt, just run grunt default task:
+
+```
+$ grunt
+```
+
+Your application should run on the 3000 port so in your browser just go to [http://localhost:3000](http://localhost:3000)
+
+That's it! your application should be running by now, to proceed with your development check the other sections in this documentation.
+If you encounter any problem try the Troubleshooting section.
+
+## Getting Started With MEAN.JS
+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).
+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.
+
+## Community
+* Use to [Offical Website](http://meanjs.org) to learn about changes and the roadmap.
+* Join #meanjs on freenode.
+* Discuss it in the new [Google Group](https://groups.google.com/d/forum/meanjs)
+* Ping us on [Twitter](http://twitter.com/meanjsorg) and [Facebook](http://facebook.com/meanjs)
+
+## Live Example
+Browse the live MEAN.JS example on [http://meanjs.herokuapp.com](http://meanjs.herokuapp.com).
+
+## Credits
+Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/)
+The MEAN name was coined by [Valeri Karpov](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and)
+
+## License
+(The MIT License)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/app/controllers/core.server.controller.js b/app/controllers/core.server.controller.js
new file mode 100644
index 0000000..db02fb5
--- /dev/null
+++ b/app/controllers/core.server.controller.js
@@ -0,0 +1,10 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+exports.index = function(req, res) {
+ res.render('index', {
+ user: req.user || null
+ });
+};
\ No newline at end of file
diff --git a/app/controllers/users.server.controller.js b/app/controllers/users.server.controller.js
new file mode 100755
index 0000000..b8b4c76
--- /dev/null
+++ b/app/controllers/users.server.controller.js
@@ -0,0 +1,380 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var mongoose = require('mongoose'),
+ passport = require('passport'),
+ User = mongoose.model('User'),
+ _ = require('lodash');
+
+/**
+ * Get the error message from error object
+ */
+var getErrorMessage = function(err) {
+ var message = '';
+
+ if (err.code) {
+ switch (err.code) {
+ case 11000:
+ case 11001:
+ message = 'Username already exists';
+ break;
+ default:
+ message = 'Something went wrong';
+ }
+ } else {
+ for (var errName in err.errors) {
+ if (err.errors[errName].message) message = err.errors[errName].message;
+ }
+ }
+
+ return message;
+};
+
+/**
+ * Signup
+ */
+exports.signup = function(req, res) {
+ // For security measurement we remove the roles from the req.body object
+ delete req.body.roles;
+
+ // Init Variables
+ var user = new User(req.body);
+ var message = null;
+
+ // Add missing user fields
+ user.provider = 'local';
+ user.displayName = user.firstName + ' ' + user.lastName;
+
+ // Then save the user
+ user.save(function(err) {
+ if (err) {
+ return res.send(400, {
+ message: getErrorMessage(err)
+ });
+ } else {
+ // Remove sensitive data before login
+ user.password = undefined;
+ user.salt = undefined;
+
+ req.login(user, function(err) {
+ if (err) {
+ res.send(400, err);
+ } else {
+ res.jsonp(user);
+ }
+ });
+ }
+ });
+};
+
+/**
+ * Signin after passport authentication
+ */
+exports.signin = function(req, res, next) {
+ passport.authenticate('local', function(err, user, info) {
+ if (err || !user) {
+ res.send(400, info);
+ } else {
+ // Remove sensitive data before login
+ user.password = undefined;
+ user.salt = undefined;
+
+ req.login(user, function(err) {
+ if (err) {
+ res.send(400, err);
+ } else {
+ res.jsonp(user);
+ }
+ });
+ }
+ })(req, res, next);
+};
+
+/**
+ * Update user details
+ */
+exports.update = function(req, res) {
+ // Init Variables
+ var user = req.user;
+ var message = null;
+
+ // For security measurement we remove the roles from the req.body object
+ delete req.body.roles;
+
+ if (user) {
+ // Merge existing user
+ user = _.extend(user, req.body);
+ user.updated = Date.now();
+ user.displayName = user.firstName + ' ' + user.lastName;
+
+ user.save(function(err) {
+ if (err) {
+ return res.send(400, {
+ message: getErrorMessage(err)
+ });
+ } else {
+ req.login(user, function(err) {
+ if (err) {
+ res.send(400, err);
+ } else {
+ res.jsonp(user);
+ }
+ });
+ }
+ });
+ } else {
+ res.send(400, {
+ message: 'User is not signed in'
+ });
+ }
+};
+
+/**
+ * Change Password
+ */
+exports.changePassword = function(req, res, next) {
+ // Init Variables
+ var passwordDetails = req.body;
+ var message = null;
+
+ if (req.user) {
+ User.findById(req.user.id, function(err, user) {
+ if (!err && user) {
+ if (user.authenticate(passwordDetails.currentPassword)) {
+ if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
+ user.password = passwordDetails.newPassword;
+
+ user.save(function(err) {
+ if (err) {
+ return res.send(400, {
+ message: getErrorMessage(err)
+ });
+ } else {
+ req.login(user, function(err) {
+ if (err) {
+ res.send(400, err);
+ } else {
+ res.send({
+ message: 'Password changed successfully'
+ });
+ }
+ });
+ }
+ });
+ } else {
+ res.send(400, {
+ message: 'Passwords do not match'
+ });
+ }
+ } else {
+ res.send(400, {
+ message: 'Current password is incorrect'
+ });
+ }
+ } else {
+ res.send(400, {
+ message: 'User is not found'
+ });
+ }
+ });
+ } else {
+ res.send(400, {
+ message: 'User is not signed in'
+ });
+ }
+};
+
+/**
+ * Signout
+ */
+exports.signout = function(req, res) {
+ req.logout();
+ res.redirect('/');
+};
+
+/**
+ * Send User
+ */
+exports.me = function(req, res) {
+ res.jsonp(req.user || null);
+};
+
+/**
+ * OAuth callback
+ */
+exports.oauthCallback = function(strategy) {
+ return function(req, res, next) {
+ passport.authenticate(strategy, function(err, user, redirectURL) {
+ if (err || !user) {
+ return res.redirect('/#!/signin');
+ }
+ req.login(user, function(err) {
+ if (err) {
+ return res.redirect('/#!/signin');
+ }
+
+ return res.redirect(redirectURL || '/');
+ });
+ })(req, res, next);
+ };
+};
+
+/**
+ * User middleware
+ */
+exports.userByID = function(req, res, next, id) {
+ User.findOne({
+ _id: id
+ }).exec(function(err, user) {
+ if (err) return next(err);
+ if (!user) return next(new Error('Failed to load User ' + id));
+ req.profile = user;
+ next();
+ });
+};
+
+/**
+ * Require login routing middleware
+ */
+exports.requiresLogin = function(req, res, next) {
+ if (!req.isAuthenticated()) {
+ return res.send(401, {
+ message: 'User is not logged in'
+ });
+ }
+
+ next();
+};
+
+/**
+ * User authorizations routing middleware
+ */
+exports.hasAuthorization = function(roles) {
+ var _this = this;
+
+ return function(req, res, next) {
+ _this.requiresLogin(req, res, function() {
+ if (_.intersection(req.user.roles, roles).length) {
+ return next();
+ } else {
+ return res.send(403, {
+ message: 'User is not authorized'
+ });
+ }
+ });
+ };
+};
+
+/**
+ * Helper function to save or update a OAuth user profile
+ */
+exports.saveOAuthUserProfile = function(req, providerUserProfile, done) {
+ if (!req.user) {
+ // Define a search query fields
+ var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField;
+ var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField;
+
+ // Define main provider search query
+ var mainProviderSearchQuery = {};
+ mainProviderSearchQuery.provider = providerUserProfile.provider;
+ mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField];
+
+ // Define additional provider search query
+ var additionalProviderSearchQuery = {};
+ additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField];
+
+ // Define a search query to find existing user with current provider profile
+ var searchQuery = {
+ $or: [mainProviderSearchQuery, additionalProviderSearchQuery]
+ };
+
+ User.findOne(searchQuery, function(err, user) {
+ if (err) {
+ return done(err);
+ } else {
+ if (!user) {
+ var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : '');
+
+ User.findUniqueUsername(possibleUsername, null, function(availableUsername) {
+ user = new User({
+ firstName: providerUserProfile.firstName,
+ lastName: providerUserProfile.lastName,
+ username: availableUsername,
+ displayName: providerUserProfile.displayName,
+ email: providerUserProfile.email,
+ provider: providerUserProfile.provider,
+ providerData: providerUserProfile.providerData
+ });
+
+ // And save the user
+ user.save(function(err) {
+ return done(err, user);
+ });
+ });
+ } else {
+ return done(err, user);
+ }
+ }
+ });
+ } else {
+ // User is already logged in, join the provider data to the existing user
+ User.findById(req.user.id, function(err, user) {
+ if (err) {
+ return done(err);
+ } else {
+ // Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured
+ if (user && user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) {
+ // Add the provider data to the additional provider data field
+ if (!user.additionalProvidersData) user.additionalProvidersData = {};
+ user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData;
+
+ // Then tell mongoose that we've updated the additionalProvidersData field
+ user.markModified('additionalProvidersData');
+
+ // And save the user
+ user.save(function(err) {
+ return done(err, user, '/#!/settings/accounts');
+ });
+ } else {
+ return done(err, user);
+ }
+ }
+ });
+ }
+};
+
+/**
+ * Remove OAuth provider
+ */
+exports.removeOAuthProvider = function(req, res, next) {
+ var user = req.user;
+ var provider = req.param('provider');
+
+ if (user && provider) {
+ // Delete the additional provider
+ if (user.additionalProvidersData[provider]) {
+ delete user.additionalProvidersData[provider];
+
+ // Then tell mongoose that we've updated the additionalProvidersData field
+ user.markModified('additionalProvidersData');
+ }
+
+ user.save(function(err) {
+ if (err) {
+ return res.send(400, {
+ message: getErrorMessage(err)
+ });
+ } else {
+ req.login(user, function(err) {
+ if (err) {
+ res.send(400, err);
+ } else {
+ res.jsonp(user);
+ }
+ });
+ }
+ });
+ }
+};
\ No newline at end of file
diff --git a/app/models/user.server.model.js b/app/models/user.server.model.js
new file mode 100755
index 0000000..6cfe08c
--- /dev/null
+++ b/app/models/user.server.model.js
@@ -0,0 +1,139 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var mongoose = require('mongoose'),
+ Schema = mongoose.Schema,
+ crypto = require('crypto');
+
+/**
+ * A Validation function for local strategy properties
+ */
+var validateLocalStrategyProperty = function(property) {
+ return ((this.provider !== 'local' && !this.updated) || property.length);
+};
+
+/**
+ * A Validation function for local strategy password
+ */
+var validateLocalStrategyPassword = function(password) {
+ return (this.provider !== 'local' || (password && password.length > 6));
+};
+
+/**
+ * User Schema
+ */
+var UserSchema = new Schema({
+ firstName: {
+ type: String,
+ trim: true,
+ default: '',
+ validate: [validateLocalStrategyProperty, 'Please fill in your first name']
+ },
+ lastName: {
+ type: String,
+ trim: true,
+ default: '',
+ validate: [validateLocalStrategyProperty, 'Please fill in your last name']
+ },
+ displayName: {
+ type: String,
+ trim: true
+ },
+ email: {
+ type: String,
+ trim: true,
+ default: '',
+ validate: [validateLocalStrategyProperty, 'Please fill in your email'],
+ match: [/.+\@.+\..+/, 'Please fill a valid email address']
+ },
+ username: {
+ type: String,
+ unique: true,
+ required: 'Please fill in a username',
+ trim: true
+ },
+ password: {
+ type: String,
+ default: '',
+ validate: [validateLocalStrategyPassword, 'Password should be longer']
+ },
+ salt: {
+ type: String
+ },
+ provider: {
+ type: String,
+ required: 'Provider is required'
+ },
+ providerData: {},
+ additionalProvidersData: {},
+ roles: {
+ type: [{
+ type: String,
+ enum: ['user', 'admin']
+ }],
+ default: ['user']
+ },
+ updated: {
+ type: Date
+ },
+ created: {
+ type: Date,
+ default: Date.now
+ }
+});
+
+/**
+ * Hook a pre save method to hash the password
+ */
+UserSchema.pre('save', function(next) {
+ if (this.password && this.password.length > 6) {
+ this.salt = new Buffer(crypto.randomBytes(16).toString('base64'), 'base64');
+ this.password = this.hashPassword(this.password);
+ }
+
+ next();
+});
+
+/**
+ * Create instance method for hashing a password
+ */
+UserSchema.methods.hashPassword = function(password) {
+ if (this.salt && password) {
+ return crypto.pbkdf2Sync(password, this.salt, 10000, 64).toString('base64');
+ } else {
+ return password;
+ }
+};
+
+/**
+ * Create instance method for authenticating user
+ */
+UserSchema.methods.authenticate = function(password) {
+ return this.password === this.hashPassword(password);
+};
+
+/**
+ * Find possible not used username
+ */
+UserSchema.statics.findUniqueUsername = function(username, suffix, callback) {
+ var _this = this;
+ var possibleUsername = username + (suffix || '');
+
+ _this.findOne({
+ username: possibleUsername
+ }, function(err, user) {
+ if (!err) {
+ if (!user) {
+ callback(possibleUsername);
+ } else {
+ return _this.findUniqueUsername(username, (suffix || 0) + 1, callback);
+ }
+ } else {
+ callback(null);
+ }
+ });
+};
+
+mongoose.model('User', UserSchema);
\ No newline at end of file
diff --git a/app/routes/core.server.routes.js b/app/routes/core.server.routes.js
new file mode 100644
index 0000000..4cd9616
--- /dev/null
+++ b/app/routes/core.server.routes.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = function(app) {
+ // Root routing
+ var core = require('../../app/controllers/core');
+ app.route('/').get(core.index);
+};
\ No newline at end of file
diff --git a/app/routes/users.server.routes.js b/app/routes/users.server.routes.js
new file mode 100644
index 0000000..f925eb0
--- /dev/null
+++ b/app/routes/users.server.routes.js
@@ -0,0 +1,46 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var passport = require('passport');
+
+module.exports = function(app) {
+ // User Routes
+ var users = require('../../app/controllers/users');
+ app.route('/users/me').get(users.me);
+ app.route('/users').put(users.update);
+ app.route('/users/password').post(users.changePassword);
+ app.route('/users/accounts').delete(users.removeOAuthProvider);
+
+ // Setting up the users api
+ app.route('/auth/signup').post(users.signup);
+ app.route('/auth/signin').post(users.signin);
+ app.route('/auth/signout').get(users.signout);
+
+ // Setting the facebook oauth routes
+ app.route('/auth/facebook').get(passport.authenticate('facebook', {
+ scope: ['email']
+ }));
+ app.route('/auth/facebook/callback').get(users.oauthCallback('facebook'));
+
+ // Setting the twitter oauth routes
+ app.route('/auth/twitter').get(passport.authenticate('twitter'));
+ app.route('/auth/twitter/callback').get(users.oauthCallback('twitter'));
+
+ // Setting the google oauth routes
+ app.route('/auth/google').get(passport.authenticate('google', {
+ scope: [
+ 'https://www.googleapis.com/auth/userinfo.profile',
+ 'https://www.googleapis.com/auth/userinfo.email'
+ ]
+ }));
+ app.route('/auth/google/callback').get(users.oauthCallback('google'));
+
+ // Setting the linkedin oauth routes
+ app.route('/auth/linkedin').get(passport.authenticate('linkedin'));
+ app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin'));
+
+ // Finish by binding the user middleware
+ app.param('userId', users.userByID);
+};
diff --git a/app/tests/user.server.model.test.js b/app/tests/user.server.model.test.js
new file mode 100644
index 0000000..63983d1
--- /dev/null
+++ b/app/tests/user.server.model.test.js
@@ -0,0 +1,75 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var should = require('should'),
+ mongoose = require('mongoose'),
+ User = mongoose.model('User');
+
+/**
+ * Globals
+ */
+var user, user2;
+
+/**
+ * Unit tests
+ */
+describe('User Model Unit Tests:', function() {
+ before(function(done) {
+ user = new User({
+ firstName: 'Full',
+ lastName: 'Name',
+ displayName: 'Full Name',
+ email: 'test@test.com',
+ username: 'username',
+ password: 'password',
+ provider: 'local'
+ });
+ user2 = new User({
+ firstName: 'Full',
+ lastName: 'Name',
+ displayName: 'Full Name',
+ email: 'test@test.com',
+ username: 'username',
+ password: 'password',
+ provider: 'local'
+ });
+
+ done();
+ });
+
+ describe('Method Save', function() {
+ it('should begin with no users', function(done) {
+ User.find({}, function(err, users) {
+ users.should.have.length(0);
+ done();
+ });
+ });
+
+ it('should be able to save without problems', function(done) {
+ user.save(done);
+ });
+
+ it('should fail to save an existing user again', function(done) {
+ user.save();
+ return user2.save(function(err) {
+ should.exist(err);
+ done();
+ });
+ });
+
+ it('should be able to show an error when try to save without first name', function(done) {
+ user.firstName = '';
+ return user.save(function(err) {
+ should.exist(err);
+ done();
+ });
+ });
+ });
+
+ after(function(done) {
+ User.remove().exec();
+ done();
+ });
+});
diff --git a/app/views/404.server.view.html b/app/views/404.server.view.html
new file mode 100644
index 0000000..0074fa4
--- /dev/null
+++ b/app/views/404.server.view.html
@@ -0,0 +1,8 @@
+{% extends 'layout.server.view.html' %}
+
+{% block content %}
+
Page Not Found
+
+ {{url}} is not a valid path.
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/views/500.server.view.html b/app/views/500.server.view.html
new file mode 100644
index 0000000..8e6711b
--- /dev/null
+++ b/app/views/500.server.view.html
@@ -0,0 +1,8 @@
+{% extends 'layout.server.view.html' %}
+
+{% block content %}
+
Server Error
+
+ {{error}}
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/views/index.server.view.html b/app/views/index.server.view.html
new file mode 100644
index 0000000..7e60893
--- /dev/null
+++ b/app/views/index.server.view.html
@@ -0,0 +1,5 @@
+{% extends 'layout.server.view.html' %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/app/views/layout.server.view.html b/app/views/layout.server.view.html
new file mode 100644
index 0000000..e13028a
--- /dev/null
+++ b/app/views/layout.server.view.html
@@ -0,0 +1,68 @@
+
+
+
+
+ {{title}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% for cssFile in cssFiles %}
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+
+
+
+
+ {% for jsFile in jsFiles %}
+ {% endfor %}
+
+ {% if process.env.NODE_ENV === 'development' %}
+
+
+ {% endif %}
+
+
+
\ No newline at end of file
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..15646af
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,18 @@
+{
+ "name": "paps",
+ "version": "0.0.1",
+ "description": "Paps en tous genres",
+ "dependencies": {
+ "bootstrap": "~3",
+ "angular": "~1.2",
+ "angular-resource": "~1.2",
+ "angular-mocks": "~1.2",
+ "angular-cookies": "~1.2",
+ "angular-animate": "~1.2",
+ "angular-touch": "~1.2",
+ "angular-sanitize": "~1.2",
+ "angular-bootstrap": "~0.10.0",
+ "angular-ui-utils": "~0.1.1",
+ "angular-ui-router": "~0.2.10"
+ }
+}
\ No newline at end of file
diff --git a/config/config.js b/config/config.js
new file mode 100644
index 0000000..e694bf4
--- /dev/null
+++ b/config/config.js
@@ -0,0 +1,76 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var _ = require('lodash'),
+ glob = require('glob');
+
+/**
+ * Load app configurations
+ */
+module.exports = _.extend(
+ require('./env/all'),
+ require('./env/' + process.env.NODE_ENV) || {}
+);
+
+/**
+ * Get files by glob patterns
+ */
+module.exports.getGlobbedFiles = function(globPatterns, removeRoot) {
+ // For context switching
+ var _this = this;
+
+ // URL paths regex
+ var urlRegex = new RegExp('^(?:[a-z]+:)?//', 'i');
+
+ // The output array
+ var output = [];
+
+ // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob
+ if (_.isArray(globPatterns)) {
+ globPatterns.forEach(function(globPattern) {
+ output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot));
+ });
+ } else if (_.isString(globPatterns)) {
+ if (urlRegex.test(globPatterns)) {
+ output.push(globPatterns);
+ } else {
+ glob(globPatterns, {
+ sync: true
+ }, function(err, files) {
+ if (removeRoot) {
+ files = files.map(function(file) {
+ return file.replace(removeRoot, '');
+ });
+ }
+
+ output = _.union(output, files);
+ });
+ }
+ }
+
+ return output;
+};
+
+/**
+ * Get the modules JavaScript files
+ */
+module.exports.getJavaScriptAssets = function(includeTests) {
+ var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js), 'public/');
+
+ // To include tests
+ if (includeTests) {
+ output = _.union(output, this.getGlobbedFiles(this.assets.tests));
+ }
+
+ return output;
+};
+
+/**
+ * Get the modules CSS files
+ */
+module.exports.getCSSAssets = function() {
+ var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/');
+ return output;
+};
\ No newline at end of file
diff --git a/config/env/all.js b/config/env/all.js
new file mode 100644
index 0000000..bf4bb56
--- /dev/null
+++ b/config/env/all.js
@@ -0,0 +1,45 @@
+'use strict';
+
+module.exports = {
+ app: {
+ title: 'Paps',
+ description: 'Paps en tous genres',
+ keywords: 'Paps;'
+ },
+ port: process.env.PORT || 3000,
+ templateEngine: 'swig',
+ sessionSecret: 'MEAN',
+ sessionCollection: 'sessions',
+ assets: {
+ lib: {
+ css: [
+ 'public/lib/bootstrap/dist/css/bootstrap.css',
+ 'public/lib/bootstrap/dist/css/bootstrap-theme.css',
+ ],
+ js: [
+ 'public/lib/angular/angular.js',
+ 'public/lib/angular-resource/angular-resource.js',
+ 'public/lib/angular-cookies/angular-cookies.js',
+ 'public/lib/angular-animate/angular-animate.js',
+ 'public/lib/angular-touch/angular-touch.js',
+ 'public/lib/angular-sanitize/angular-sanitize.js',
+ 'public/lib/angular-ui-router/release/angular-ui-router.js',
+ 'public/lib/angular-ui-utils/ui-utils.js',
+ 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js'
+ ]
+ },
+ css: [
+ 'public/modules/**/css/*.css'
+ ],
+ js: [
+ 'public/config.js',
+ 'public/application.js',
+ 'public/modules/*/*.js',
+ 'public/modules/*/*[!tests]*/*.js'
+ ],
+ tests: [
+ 'public/lib/angular-mocks/angular-mocks.js',
+ 'public/modules/*/tests/*.js'
+ ]
+ }
+};
\ No newline at end of file
diff --git a/config/env/development.js b/config/env/development.js
new file mode 100644
index 0000000..9f22e70
--- /dev/null
+++ b/config/env/development.js
@@ -0,0 +1,28 @@
+'use strict';
+
+module.exports = {
+ db: 'mongodb://localhost/paps-dev',
+ app: {
+ title: 'Paps - Development Environment'
+ },
+ facebook: {
+ clientID: process.env.FACEBOOK_ID || 'APP_ID',
+ clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/facebook/callback'
+ },
+ twitter: {
+ clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
+ clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
+ callbackURL: 'http://localhost:3000/auth/twitter/callback'
+ },
+ google: {
+ clientID: process.env.GOOGLE_ID || 'APP_ID',
+ clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/google/callback'
+ },
+ linkedin: {
+ clientID: process.env.LINKEDIN_ID || 'APP_ID',
+ clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/linkedin/callback'
+ }
+};
\ No newline at end of file
diff --git a/config/env/production.js b/config/env/production.js
new file mode 100644
index 0000000..e25b94b
--- /dev/null
+++ b/config/env/production.js
@@ -0,0 +1,46 @@
+'use strict';
+
+module.exports = {
+ db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/paps',
+ assets: {
+ lib: {
+ css: [
+ 'public/lib/bootstrap/dist/css/bootstrap.min.css',
+ 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css',
+ ],
+ js: [
+ 'public/lib/angular/angular.min.js',
+ 'public/lib/angular-resource/angular-resource.min.js',
+ 'public/lib/angular-cookies/angular-cookies.min.js',
+ 'public/lib/angular-animate/angular-animate.min.js',
+ 'public/lib/angular-touch/angular-touch.min.js',
+ 'public/lib/angular-sanitize/angular-sanitize.min.js',
+ 'public/lib/angular-ui-router/release/angular-ui-router.min.js',
+ 'public/lib/angular-ui-utils/ui-utils.min.js',
+ 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js'
+ ]
+ },
+ css: 'public/dist/application.min.css',
+ js: 'public/dist/application.min.js'
+ },
+ facebook: {
+ clientID: process.env.FACEBOOK_ID || 'APP_ID',
+ clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/facebook/callback'
+ },
+ twitter: {
+ clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
+ clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
+ callbackURL: 'http://localhost:3000/auth/twitter/callback'
+ },
+ google: {
+ clientID: process.env.GOOGLE_ID || 'APP_ID',
+ clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/google/callback'
+ },
+ linkedin: {
+ clientID: process.env.LINKEDIN_ID || 'APP_ID',
+ clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/linkedin/callback'
+ }
+};
\ No newline at end of file
diff --git a/config/env/test.js b/config/env/test.js
new file mode 100644
index 0000000..fe42ddb
--- /dev/null
+++ b/config/env/test.js
@@ -0,0 +1,29 @@
+'use strict';
+
+module.exports = {
+ db: 'mongodb://localhost/paps-test',
+ port: 3001,
+ app: {
+ title: 'Paps - Test Environment'
+ },
+ facebook: {
+ clientID: process.env.FACEBOOK_ID || 'APP_ID',
+ clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/facebook/callback'
+ },
+ twitter: {
+ clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
+ clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
+ callbackURL: 'http://localhost:3000/auth/twitter/callback'
+ },
+ google: {
+ clientID: process.env.GOOGLE_ID || 'APP_ID',
+ clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/google/callback'
+ },
+ linkedin: {
+ clientID: process.env.LINKEDIN_ID || 'APP_ID',
+ clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
+ callbackURL: 'http://localhost:3000/auth/linkedin/callback'
+ }
+};
\ No newline at end of file
diff --git a/config/express.js b/config/express.js
new file mode 100755
index 0000000..89fa433
--- /dev/null
+++ b/config/express.js
@@ -0,0 +1,140 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var express = require('express'),
+ morgan = require('morgan'),
+ bodyParser = require('body-parser'),
+ session = require('express-session'),
+ compress = require('compression'),
+ methodOverride = require('method-override'),
+ cookieParser = require('cookie-parser'),
+ helmet = require('helmet'),
+ passport = require('passport'),
+ mongoStore = require('connect-mongo')({
+ session: session
+ }),
+ flash = require('connect-flash'),
+ config = require('./config'),
+ consolidate = require('consolidate'),
+ path = require('path');
+
+module.exports = function(db) {
+ // Initialize express app
+ var app = express();
+
+ // Globbing model files
+ config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
+ require(path.resolve(modelPath));
+ });
+
+ // Setting application local variables
+ app.locals.title = config.app.title;
+ app.locals.description = config.app.description;
+ app.locals.keywords = config.app.keywords;
+ app.locals.facebookAppId = config.facebook.clientID;
+ app.locals.jsFiles = config.getJavaScriptAssets();
+ app.locals.cssFiles = config.getCSSAssets();
+
+ // Passing the request url to environment locals
+ app.use(function(req, res, next) {
+ res.locals.url = req.protocol + ':// ' + req.headers.host + req.url;
+ next();
+ });
+
+ // Should be placed before express.static
+ app.use(compress({
+ filter: function(req, res) {
+ return (/json|text|javascript|css/).test(res.getHeader('Content-Type'));
+ },
+ level: 9
+ }));
+
+ // Showing stack errors
+ app.set('showStackError', true);
+
+ // Set swig as the template engine
+ app.engine('server.view.html', consolidate[config.templateEngine]);
+
+ // Set views path and view engine
+ app.set('view engine', 'server.view.html');
+ app.set('views', './app/views');
+
+ // Environment dependent middleware
+ if (process.env.NODE_ENV === 'development') {
+ // Enable logger (morgan)
+ app.use(morgan('dev'));
+
+ // Disable views cache
+ app.set('view cache', false);
+ } else if (process.env.NODE_ENV === 'production') {
+ app.locals.cache = 'memory';
+ }
+
+ // Request body parsing middleware should be above methodOverride
+ app.use(bodyParser.urlencoded());
+ app.use(bodyParser.json());
+ app.use(methodOverride());
+
+ // Enable jsonp
+ app.enable('jsonp callback');
+
+ // CookieParser should be above session
+ app.use(cookieParser());
+
+ // Express MongoDB session storage
+ app.use(session({
+ secret: config.sessionSecret,
+ store: new mongoStore({
+ db: db.connection.db,
+ collection: config.sessionCollection
+ })
+ }));
+
+ // use passport session
+ app.use(passport.initialize());
+ app.use(passport.session());
+
+ // connect flash for flash messages
+ app.use(flash());
+
+ // Use helmet to secure Express headers
+ app.use(helmet.xframe());
+ app.use(helmet.iexss());
+ app.use(helmet.contentTypeOptions());
+ app.use(helmet.ienoopen());
+ app.disable('x-powered-by');
+
+ // Setting the app router and static folder
+ app.use(express.static(path.resolve('./public')));
+
+ // Globbing routing files
+ config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) {
+ require(path.resolve(routePath))(app);
+ });
+
+ // 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.
+ app.use(function(err, req, res, next) {
+ // If the error object doesn't exists
+ if (!err) return next();
+
+ // Log it
+ console.error(err.stack);
+
+ // Error page
+ res.status(500).render('500', {
+ error: err.stack
+ });
+ });
+
+ // Assume 404 since no middleware responded
+ app.use(function(req, res) {
+ res.status(404).render('404', {
+ url: req.originalUrl,
+ error: 'Not Found'
+ });
+ });
+
+ return app;
+};
diff --git a/config/init.js b/config/init.js
new file mode 100644
index 0000000..d8c05ab
--- /dev/null
+++ b/config/init.js
@@ -0,0 +1,40 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var glob = require('glob');
+
+/**
+ * Module init function.
+ */
+module.exports = function() {
+ /**
+ * Before we begin, lets set the environment variable
+ * We'll Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV
+ */
+ glob('./config/env/' + process.env.NODE_ENV + '.js', {
+ sync: true
+ }, function(err, environmentFiles) {
+ console.log();
+ if (!environmentFiles.length) {
+ if(process.env.NODE_ENV) {
+ console.log('\x1b[31m', 'No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead');
+ } else {
+ console.log('\x1b[31m', 'NODE_ENV is not defined! Using default development environment');
+ }
+
+ process.env.NODE_ENV = 'development';
+ } else {
+ console.log('\x1b[7m', 'Application loaded using the "' + process.env.NODE_ENV + '" environment configuration');
+ }
+ console.log('\x1b[0m');
+ });
+
+ /**
+ * Add our server node extensions
+ */
+ require.extensions['.server.controller.js'] = require.extensions['.js'];
+ require.extensions['.server.model.js'] = require.extensions['.js'];
+ require.extensions['.server.routes.js'] = require.extensions['.js'];
+};
\ No newline at end of file
diff --git a/config/passport.js b/config/passport.js
new file mode 100755
index 0000000..9e3a31a
--- /dev/null
+++ b/config/passport.js
@@ -0,0 +1,27 @@
+'use strict';
+
+var passport = require('passport'),
+ User = require('mongoose').model('User'),
+ path = require('path'),
+ config = require('./config');
+
+module.exports = function() {
+ // Serialize sessions
+ passport.serializeUser(function(user, done) {
+ done(null, user.id);
+ });
+
+ // Deserialize sessions
+ passport.deserializeUser(function(id, done) {
+ User.findOne({
+ _id: id
+ }, '-salt -password', function(err, user) {
+ done(err, user);
+ });
+ });
+
+ // Initialize strategies
+ config.getGlobbedFiles('./config/strategies/**/*.js').forEach(function(strategy) {
+ require(path.resolve(strategy))();
+ });
+};
\ No newline at end of file
diff --git a/config/strategies/facebook.js b/config/strategies/facebook.js
new file mode 100644
index 0000000..72cef80
--- /dev/null
+++ b/config/strategies/facebook.js
@@ -0,0 +1,42 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var passport = require('passport'),
+ url = require('url'),
+ FacebookStrategy = require('passport-facebook').Strategy,
+ config = require('../config'),
+ users = require('../../app/controllers/users');
+
+module.exports = function() {
+ // Use facebook strategy
+ passport.use(new FacebookStrategy({
+ clientID: config.facebook.clientID,
+ clientSecret: config.facebook.clientSecret,
+ callbackURL: config.facebook.callbackURL,
+ passReqToCallback: true
+ },
+ function(req, accessToken, refreshToken, profile, done) {
+ // Set the provider data and include tokens
+ var providerData = profile._json;
+ providerData.accessToken = accessToken;
+ providerData.refreshToken = refreshToken;
+
+ // Create the user OAuth profile
+ var providerUserProfile = {
+ firstName: profile.name.givenName,
+ lastName: profile.name.familyName,
+ displayName: profile.displayName,
+ email: profile.emails[0].value,
+ username: profile.username,
+ provider: 'facebook',
+ providerIdentifierField: 'id',
+ providerData: providerData
+ };
+
+ // Save the user OAuth profile
+ users.saveOAuthUserProfile(req, providerUserProfile, done);
+ }
+ ));
+};
diff --git a/config/strategies/google.js b/config/strategies/google.js
new file mode 100644
index 0000000..86d22f6
--- /dev/null
+++ b/config/strategies/google.js
@@ -0,0 +1,42 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var passport = require('passport'),
+ url = require('url'),
+ GoogleStrategy = require('passport-google-oauth').OAuth2Strategy,
+ config = require('../config'),
+ users = require('../../app/controllers/users');
+
+module.exports = function() {
+ // Use google strategy
+ passport.use(new GoogleStrategy({
+ clientID: config.google.clientID,
+ clientSecret: config.google.clientSecret,
+ callbackURL: config.google.callbackURL,
+ passReqToCallback: true
+ },
+ function(req, accessToken, refreshToken, profile, done) {
+ // Set the provider data and include tokens
+ var providerData = profile._json;
+ providerData.accessToken = accessToken;
+ providerData.refreshToken = refreshToken;
+
+ // Create the user OAuth profile
+ var providerUserProfile = {
+ firstName: profile.name.givenName,
+ lastName: profile.name.familyName,
+ displayName: profile.displayName,
+ email: profile.emails[0].value,
+ username: profile.username,
+ provider: 'google',
+ providerIdentifierField: 'id',
+ providerData: providerData
+ };
+
+ // Save the user OAuth profile
+ users.saveOAuthUserProfile(req, providerUserProfile, done);
+ }
+ ));
+};
\ No newline at end of file
diff --git a/config/strategies/linkedin.js b/config/strategies/linkedin.js
new file mode 100644
index 0000000..7018112
--- /dev/null
+++ b/config/strategies/linkedin.js
@@ -0,0 +1,43 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var passport = require('passport'),
+ url = require('url'),
+ LinkedInStrategy = require('passport-linkedin').Strategy,
+ config = require('../config'),
+ users = require('../../app/controllers/users');
+
+module.exports = function() {
+ // Use linkedin strategy
+ passport.use(new LinkedInStrategy({
+ consumerKey: config.linkedin.clientID,
+ consumerSecret: config.linkedin.clientSecret,
+ callbackURL: config.linkedin.callbackURL,
+ passReqToCallback: true,
+ profileFields: ['id', 'first-name', 'last-name', 'email-address']
+ },
+ function(req, accessToken, refreshToken, profile, done) {
+ // Set the provider data and include tokens
+ var providerData = profile._json;
+ providerData.accessToken = accessToken;
+ providerData.refreshToken = refreshToken;
+
+ // Create the user OAuth profile
+ var providerUserProfile = {
+ firstName: profile.name.givenName,
+ lastName: profile.name.familyName,
+ displayName: profile.displayName,
+ email: profile.emails[0].value,
+ username: profile.username,
+ provider: 'linkedin',
+ providerIdentifierField: 'id',
+ providerData: providerData
+ };
+
+ // Save the user OAuth profile
+ users.saveOAuthUserProfile(req, providerUserProfile, done);
+ }
+ ));
+};
diff --git a/config/strategies/local.js b/config/strategies/local.js
new file mode 100644
index 0000000..97f8d43
--- /dev/null
+++ b/config/strategies/local.js
@@ -0,0 +1,38 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var passport = require('passport'),
+ LocalStrategy = require('passport-local').Strategy,
+ User = require('mongoose').model('User');
+
+module.exports = function() {
+ // Use local strategy
+ passport.use(new LocalStrategy({
+ usernameField: 'username',
+ passwordField: 'password'
+ },
+ function(username, password, done) {
+ User.findOne({
+ username: username
+ }, function(err, user) {
+ if (err) {
+ return done(err);
+ }
+ if (!user) {
+ return done(null, false, {
+ message: 'Unknown user'
+ });
+ }
+ if (!user.authenticate(password)) {
+ return done(null, false, {
+ message: 'Invalid password'
+ });
+ }
+
+ return done(null, user);
+ });
+ }
+ ));
+};
\ No newline at end of file
diff --git a/config/strategies/twitter.js b/config/strategies/twitter.js
new file mode 100644
index 0000000..9d7bcd7
--- /dev/null
+++ b/config/strategies/twitter.js
@@ -0,0 +1,39 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var passport = require('passport'),
+ url = require('url'),
+ TwitterStrategy = require('passport-twitter').Strategy,
+ config = require('../config'),
+ users = require('../../app/controllers/users');
+
+module.exports = function() {
+ // Use twitter strategy
+ passport.use(new TwitterStrategy({
+ consumerKey: config.twitter.clientID,
+ consumerSecret: config.twitter.clientSecret,
+ callbackURL: config.twitter.callbackURL,
+ passReqToCallback: true
+ },
+ function(req, token, tokenSecret, profile, done) {
+ // Set the provider data and include tokens
+ var providerData = profile._json;
+ providerData.token = token;
+ providerData.tokenSecret = tokenSecret;
+
+ // Create the user OAuth profile
+ var providerUserProfile = {
+ displayName: profile.displayName,
+ username: profile.username,
+ provider: 'twitter',
+ providerIdentifierField: 'id_str',
+ providerData: providerData
+ };
+
+ // Save the user OAuth profile
+ users.saveOAuthUserProfile(req, providerUserProfile, done);
+ }
+ ));
+};
diff --git a/gruntfile.js b/gruntfile.js
new file mode 100644
index 0000000..c338b7e
--- /dev/null
+++ b/gruntfile.js
@@ -0,0 +1,134 @@
+'use strict';
+
+module.exports = function(grunt) {
+ // Project Configuration
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+ watch: {
+ serverViews: {
+ files: ['app/views/**'],
+ options: {
+ livereload: true,
+ }
+ },
+ serverJS: {
+ files: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js'],
+ tasks: ['jshint'],
+ options: {
+ livereload: true,
+ }
+ },
+ clientViews: {
+ files: ['public/modules/**/views/*.html'],
+ options: {
+ livereload: true,
+ }
+ },
+ clientJS: {
+ files: ['public/js/**/*.js', 'public/modules/**/*.js'],
+ tasks: ['jshint'],
+ options: {
+ livereload: true,
+ }
+ },
+ clientCSS: {
+ files: ['public/**/css/*.css'],
+ tasks: ['csslint'],
+ options: {
+ livereload: true,
+ }
+ }
+ },
+ jshint: {
+ all: {
+ src: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', 'public/js/**/*.js', 'public/modules/**/*.js'],
+ options: {
+ jshintrc: true
+ }
+ }
+ },
+ csslint: {
+ options: {
+ csslintrc: '.csslintrc',
+ },
+ all: {
+ src: ['public/modules/**/css/*.css']
+ }
+ },
+ uglify: {
+ production: {
+ options: {
+ mangle: false
+ },
+ files: {
+ 'public/dist/application.min.js': '<%= applicationJavaScriptFiles %>'
+ }
+ }
+ },
+ cssmin: {
+ combine: {
+ files: {
+ 'public/dist/application.min.css': '<%= applicationCSSFiles %>'
+ }
+ }
+ },
+ nodemon: {
+ dev: {
+ script: 'server.js',
+ options: {
+ nodeArgs: ['--debug']
+ }
+ }
+ },
+ concurrent: {
+ tasks: ['nodemon', 'watch'],
+ options: {
+ logConcurrentOutput: true
+ }
+ },
+ env: {
+ test: {
+ NODE_ENV: 'test'
+ }
+ },
+ mochaTest: {
+ src: ['app/tests/**/*.js'],
+ options: {
+ reporter: 'spec',
+ require: 'server.js'
+ }
+ },
+ karma: {
+ unit: {
+ configFile: 'karma.conf.js'
+ }
+ }
+ });
+
+ // Load NPM tasks
+ require('load-grunt-tasks')(grunt);
+
+ // Making grunt default to force in order not to break the project.
+ grunt.option('force', true);
+
+ // A Task for loading the configuration object
+ grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() {
+ var init = require('./config/init')();
+ var config = require('./config/config');
+
+ grunt.config.set('applicationJavaScriptFiles', config.assets.js);
+ grunt.config.set('applicationCSSFiles', config.assets.css);
+ });
+
+ // Default task(s).
+ grunt.registerTask('default', ['jshint', 'csslint', 'concurrent']);
+
+ // Lint task(s).
+ grunt.registerTask('lint', ['jshint', 'csslint']);
+
+ // Build task(s).
+ grunt.registerTask('build', ['jshint', 'csslint', 'loadConfig' ,'uglify', 'cssmin']);
+
+ // Test task.
+ grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']);
+};
\ No newline at end of file
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..9910348
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,52 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var applicationConfiguration = require('./config/config');
+
+// Karma configuration
+module.exports = function(config) {
+ config.set({
+ // Frameworks to use
+ frameworks: ['jasmine'],
+
+ // List of files / patterns to load in the browser
+ files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests),
+
+ // Test results reporter to use
+ // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
+ //reporters: ['progress'],
+ reporters: ['progress'],
+
+ // Web server port
+ port: 9876,
+
+ // Enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+ // Level of logging
+ // Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+ // Enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+ // Start these browsers, currently available:
+ // - Chrome
+ // - ChromeCanary
+ // - Firefox
+ // - Opera
+ // - Safari (only Mac)
+ // - PhantomJS
+ // - IE (only Windows)
+ browsers: ['PhantomJS'],
+
+ // If browser does not capture in given timeout [ms], kill it
+ captureTimeout: 60000,
+
+ // Continuous Integration mode
+ // If true, it capture browsers, run tests and exit
+ singleRun: true
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f19a22c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "paps",
+ "description": "Paps en tous genres",
+ "version": "0.0.1",
+ "author": "Th3o SMith",
+ "engines": {
+ "node": "0.10.x",
+ "npm": "1.4.x"
+ },
+ "scripts": {
+ "start": "grunt",
+ "test": "grunt test",
+ "postinstall": "bower install --config.interactive=false"
+ },
+ "dependencies": {
+ "express": "~4.1.0",
+ "express-session": "~1.0.2",
+ "body-parser": "~1.0.1",
+ "cookie-parser": "~1.0.1",
+ "compression": "~1.0.1",
+ "method-override": "~1.0.0",
+ "morgan": "~1.0.0",
+ "connect-mongo": "~0.4.0",
+ "connect-flash": "~0.1.1",
+ "helmet": "~0.2.1",
+ "consolidate": "~0.10.0",
+ "swig": "~1.3.2",
+ "mongoose": "~3.8.8",
+ "passport": "~0.2.0",
+ "passport-local": "~1.0.0",
+ "passport-facebook": "~1.0.2",
+ "passport-twitter": "~1.0.2",
+ "passport-linkedin": "~0.1.3",
+ "passport-google-oauth": "~0.1.5",
+ "lodash": "~2.4.1",
+ "forever": "~0.11.0",
+ "bower": "~1.3.1",
+ "grunt-cli": "~0.1.13",
+ "glob": "~3.2.9"
+ },
+ "devDependencies": {
+ "supertest": "~0.10.0",
+ "should": "~3.2.0",
+ "grunt-env": "~0.4.1",
+ "grunt-node-inspector": "~0.1.3",
+ "grunt-contrib-watch": "~0.6.1",
+ "grunt-contrib-jshint": "~0.10.0",
+ "grunt-contrib-csslint": "^0.2.0",
+ "grunt-contrib-uglify": "~0.4.0",
+ "grunt-contrib-cssmin": "~0.9.0",
+ "grunt-nodemon": "~0.2.0",
+ "grunt-concurrent": "~0.5.0",
+ "grunt-mocha-test": "~0.10.0",
+ "grunt-karma": "~0.8.2",
+ "load-grunt-tasks": "~0.4.0",
+ "karma": "~0.12.0",
+ "karma-jasmine": "~0.2.1",
+ "karma-coverage": "~0.2.0",
+ "karma-chrome-launcher": "~0.1.2",
+ "karma-firefox-launcher": "~0.1.3",
+ "karma-phantomjs-launcher": "~0.1.2"
+ }
+}
\ No newline at end of file
diff --git a/public/application.js b/public/application.js
new file mode 100644
index 0000000..19bb411
--- /dev/null
+++ b/public/application.js
@@ -0,0 +1,20 @@
+'use strict';
+
+//Start by defining the main module and adding the module dependencies
+angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies);
+
+// Setting HTML5 Location Mode
+angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider',
+ function($locationProvider) {
+ $locationProvider.hashPrefix('!');
+ }
+]);
+
+//Then define the init function for starting up the application
+angular.element(document).ready(function() {
+ //Fixing facebook bug with redirect
+ if (window.location.hash === '#_=_') window.location.hash = '#!';
+
+ //Then init the app
+ angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]);
+});
\ No newline at end of file
diff --git a/public/config.js b/public/config.js
new file mode 100644
index 0000000..7f5f189
--- /dev/null
+++ b/public/config.js
@@ -0,0 +1,23 @@
+'use strict';
+
+// Init the application configuration module for AngularJS application
+var ApplicationConfiguration = (function() {
+ // Init module configuration options
+ var applicationModuleName = 'paps';
+ var applicationModuleVendorDependencies = ['ngResource', 'ngCookies', 'ngAnimate', 'ngTouch', 'ngSanitize', 'ui.router', 'ui.bootstrap', 'ui.utils'];
+
+ // Add a new vertical module
+ var registerModule = function(moduleName) {
+ // Create angular module
+ angular.module(moduleName, []);
+
+ // Add the module to the AngularJS configuration file
+ angular.module(applicationModuleName).requires.push(moduleName);
+ };
+
+ return {
+ applicationModuleName: applicationModuleName,
+ applicationModuleVendorDependencies: applicationModuleVendorDependencies,
+ registerModule: registerModule
+ };
+})();
\ No newline at end of file
diff --git a/public/modules/core/config/core.client.routes.js b/public/modules/core/config/core.client.routes.js
new file mode 100755
index 0000000..894e3a6
--- /dev/null
+++ b/public/modules/core/config/core.client.routes.js
@@ -0,0 +1,16 @@
+'use strict';
+
+// Setting up route
+angular.module('core').config(['$stateProvider', '$urlRouterProvider',
+ function($stateProvider, $urlRouterProvider) {
+ // Redirect to home view when route not found
+ $urlRouterProvider.otherwise('/');
+
+ // Home state routing
+ $stateProvider.
+ state('home', {
+ url: '/',
+ templateUrl: 'modules/core/views/home.client.view.html'
+ });
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/core/controllers/header.client.controller.js b/public/modules/core/controllers/header.client.controller.js
new file mode 100644
index 0000000..4fe41f2
--- /dev/null
+++ b/public/modules/core/controllers/header.client.controller.js
@@ -0,0 +1,13 @@
+'use strict';
+
+angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus',
+ function($scope, Authentication, Menus) {
+ $scope.authentication = Authentication;
+ $scope.isCollapsed = false;
+ $scope.menu = Menus.getMenu('topbar');
+
+ $scope.toggleCollapsibleMenu = function() {
+ $scope.isCollapsed = !$scope.isCollapsed;
+ };
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/core/controllers/home.client.controller.js b/public/modules/core/controllers/home.client.controller.js
new file mode 100644
index 0000000..7c5edf3
--- /dev/null
+++ b/public/modules/core/controllers/home.client.controller.js
@@ -0,0 +1,5 @@
+'use strict';
+
+angular.module('core').controller('HomeController', ['$scope', 'Authentication', function ($scope, Authentication) {
+ $scope.authentication = Authentication;
+}]);
\ No newline at end of file
diff --git a/public/modules/core/core.client.module.js b/public/modules/core/core.client.module.js
new file mode 100755
index 0000000..0edda04
--- /dev/null
+++ b/public/modules/core/core.client.module.js
@@ -0,0 +1,4 @@
+'use strict';
+
+// Use Applicaion configuration module to register a new module
+ApplicationConfiguration.registerModule('core');
\ No newline at end of file
diff --git a/public/modules/core/css/core.css b/public/modules/core/css/core.css
new file mode 100644
index 0000000..30aebaa
--- /dev/null
+++ b/public/modules/core/css/core.css
@@ -0,0 +1,9 @@
+.content {
+ margin-top: 50px;
+}
+.undecorated-link:hover {
+ text-decoration: none;
+}
+[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
+ display: none !important;
+}
\ No newline at end of file
diff --git a/public/modules/core/img/brand/favicon.ico b/public/modules/core/img/brand/favicon.ico
new file mode 100644
index 0000000..756ec7e
Binary files /dev/null and b/public/modules/core/img/brand/favicon.ico differ
diff --git a/public/modules/core/img/brand/logo.png b/public/modules/core/img/brand/logo.png
new file mode 100644
index 0000000..4bf6cde
Binary files /dev/null and b/public/modules/core/img/brand/logo.png differ
diff --git a/public/modules/core/img/loaders/loader.gif b/public/modules/core/img/loaders/loader.gif
new file mode 100644
index 0000000..f89b233
Binary files /dev/null and b/public/modules/core/img/loaders/loader.gif differ
diff --git a/public/modules/core/services/menus.client.service.js b/public/modules/core/services/menus.client.service.js
new file mode 100644
index 0000000..66d6bac
--- /dev/null
+++ b/public/modules/core/services/menus.client.service.js
@@ -0,0 +1,114 @@
+'use strict';
+
+//Menu service used for managing menus
+angular.module('core').service('Menus', [
+ function() {
+ // Define a set of default roles
+ this.defaultRoles = ['user'];
+
+ // Define the menus object
+ this.menus = {};
+
+ // A private function for rendering decision
+ var shouldRender = function(user) {
+ if(user) {
+ for (var userRoleIndex in user.roles) {
+ for (var roleIndex in this.roles) {
+ if(this.roles[roleIndex] === user.roles[userRoleIndex]) {
+ return true;
+ }
+ }
+ }
+ } else {
+ return this.isPublic;
+ }
+
+ return false;
+ };
+
+ // Validate menu existance
+ this.validateMenuExistance = function(menuId) {
+ if (menuId && menuId.length) {
+ if (this.menus[menuId]) {
+ return true;
+ } else {
+ throw new Error('Menu does not exists');
+ }
+ } else {
+ throw new Error('MenuId was not provided');
+ }
+
+ return false;
+ };
+
+ // Get the menu object by menu id
+ this.getMenu = function(menuId) {
+ // Validate that the menu exists
+ this.validateMenuExistance(menuId);
+
+ // Return the menu object
+ return this.menus[menuId];
+ };
+
+ // Add new menu object by menu id
+ this.addMenu = function(menuId, isPublic, roles) {
+ // Create the new menu
+ this.menus[menuId] = {
+ isPublic: isPublic || false,
+ roles: roles || this.defaultRoles,
+ items: [],
+ shouldRender: shouldRender
+ };
+
+ // Return the menu object
+ return this.menus[menuId];
+ };
+
+ // Remove existing menu object by menu id
+ this.removeMenu = function(menuId) {
+ // Validate that the menu exists
+ this.validateMenuExistance(menuId);
+
+ // Return the menu object
+ delete this.menus[menuId];
+ };
+
+ // Add menu item object
+ this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles) {
+ // Validate that the menu exists
+ this.validateMenuExistance(menuId);
+
+ // Push new menu item
+ this.menus[menuId].items.push({
+ title: menuItemTitle,
+ link: menuItemURL,
+ uiRoute: menuItemUIRoute || ('/' + menuItemURL),
+ isPublic: isPublic || this.menus[menuId].isPublic,
+ roles: roles || this.defaultRoles,
+ shouldRender: shouldRender
+ });
+
+ // Return the menu object
+ return this.menus[menuId];
+ };
+
+ // Remove existing menu object by menu id
+ this.removeMenuItem = function(menuId, menuItemURL) {
+ // Validate that the menu exists
+ this.validateMenuExistance(menuId);
+
+ // Search for menu item to remove
+ for (var itemIndex in this.menus[menuId].items) {
+ if (this.menus[menuId].items[itemIndex].menuItemURL === menuItemURL) {
+ this.menus[menuId].items.splice(itemIndex, 1);
+ }
+ }
+
+ // Return the menu object
+ return this.menus[menuId];
+ };
+
+ //Adding the topbar menu
+ this.addMenu('topbar');
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/core/tests/header.client.controller.test.js b/public/modules/core/tests/header.client.controller.test.js
new file mode 100644
index 0000000..76ee4fb
--- /dev/null
+++ b/public/modules/core/tests/header.client.controller.test.js
@@ -0,0 +1,24 @@
+'use strict';
+
+(function() {
+ describe('HeaderController', function() {
+ //Initialize global variables
+ var scope,
+ HeaderController;
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ beforeEach(inject(function($controller, $rootScope) {
+ scope = $rootScope.$new();
+
+ HeaderController = $controller('HeaderController', {
+ $scope: scope
+ });
+ }));
+
+ it('should expose the authentication service', function() {
+ expect(scope.authentication).toBeTruthy();
+ });
+ });
+})();
\ No newline at end of file
diff --git a/public/modules/core/tests/home.client.controller.test.js b/public/modules/core/tests/home.client.controller.test.js
new file mode 100644
index 0000000..a5b1a56
--- /dev/null
+++ b/public/modules/core/tests/home.client.controller.test.js
@@ -0,0 +1,24 @@
+'use strict';
+
+(function() {
+ describe('HomeController', function() {
+ //Initialize global variables
+ var scope,
+ HomeController;
+
+ // Load the main application module
+ beforeEach(module(ApplicationConfiguration.applicationModuleName));
+
+ beforeEach(inject(function($controller, $rootScope) {
+ scope = $rootScope.$new();
+
+ HomeController = $controller('HomeController', {
+ $scope: scope
+ });
+ }));
+
+ it('should expose the authentication service', function() {
+ expect(scope.authentication).toBeTruthy();
+ });
+ });
+})();
\ No newline at end of file
diff --git a/public/modules/core/views/header.client.view.html b/public/modules/core/views/header.client.view.html
new file mode 100644
index 0000000..a41571d
--- /dev/null
+++ b/public/modules/core/views/header.client.view.html
@@ -0,0 +1,49 @@
+
\ No newline at end of file
diff --git a/public/modules/core/views/home.client.view.html b/public/modules/core/views/home.client.view.html
new file mode 100644
index 0000000..7d02d4b
--- /dev/null
+++ b/public/modules/core/views/home.client.view.html
@@ -0,0 +1,32 @@
+
+
THANK YOU FOR DOWNLOADING MEAN.JS
+
+
+ Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application:
+
+ When you're done with those resources and feel you understand the basic principals continue to the MEAN.JS Documentation.
+
+
+ Enjoy & Keep Us Updated,
+ The MEAN.JS Team.
+
+
\ No newline at end of file
diff --git a/public/modules/users/config/users.client.config.js b/public/modules/users/config/users.client.config.js
new file mode 100644
index 0000000..0bfc8b6
--- /dev/null
+++ b/public/modules/users/config/users.client.config.js
@@ -0,0 +1,30 @@
+'use strict';
+
+// Config HTTP Error Handling
+angular.module('users').config(['$httpProvider',
+ function($httpProvider) {
+ // Set the httpProvider "not authorized" interceptor
+ $httpProvider.interceptors.push(['$q', '$location', 'Authentication',
+ function($q, $location, Authentication) {
+ return {
+ responseError: function(rejection) {
+ switch (rejection.status) {
+ case 401:
+ // Deauthenticate the global user
+ Authentication.user = null;
+
+ // Redirect to signin page
+ $location.path('signin');
+ break;
+ case 403:
+ // Add unauthorized behaviour
+ break;
+ }
+
+ return $q.reject(rejection);
+ }
+ };
+ }
+ ]);
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/users/config/users.client.routes.js b/public/modules/users/config/users.client.routes.js
new file mode 100755
index 0000000..e715870
--- /dev/null
+++ b/public/modules/users/config/users.client.routes.js
@@ -0,0 +1,29 @@
+'use strict';
+
+// Setting up route
+angular.module('users').config(['$stateProvider',
+ function($stateProvider) {
+ // Users state routing
+ $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'
+ });
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/users/controllers/authentication.client.controller.js b/public/modules/users/controllers/authentication.client.controller.js
new file mode 100644
index 0000000..a4ac880
--- /dev/null
+++ b/public/modules/users/controllers/authentication.client.controller.js
@@ -0,0 +1,34 @@
+'use strict';
+
+angular.module('users').controller('AuthenticationController', ['$scope', '$http', '$location', 'Authentication',
+ function($scope, $http, $location, Authentication) {
+ $scope.authentication = Authentication;
+
+ //If user is signed in then redirect back home
+ if ($scope.authentication.user) $location.path('/');
+
+ $scope.signup = function() {
+ $http.post('/auth/signup', $scope.credentials).success(function(response) {
+ //If successful we assign the response to the global user model
+ $scope.authentication.user = response;
+
+ //And redirect to the index page
+ $location.path('/');
+ }).error(function(response) {
+ $scope.error = response.message;
+ });
+ };
+
+ $scope.signin = function() {
+ $http.post('/auth/signin', $scope.credentials).success(function(response) {
+ //If successful we assign the response to the global user model
+ $scope.authentication.user = response;
+
+ //And redirect to the index page
+ $location.path('/');
+ }).error(function(response) {
+ $scope.error = response.message;
+ });
+ };
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/users/controllers/settings.client.controller.js b/public/modules/users/controllers/settings.client.controller.js
new file mode 100644
index 0000000..02fd1fd
--- /dev/null
+++ b/public/modules/users/controllers/settings.client.controller.js
@@ -0,0 +1,67 @@
+'use strict';
+
+angular.module('users').controller('SettingsController', ['$scope', '$http', '$location', 'Users', 'Authentication',
+ function($scope, $http, $location, Users, Authentication) {
+ $scope.user = Authentication.user;
+
+ // If user is not signed in then redirect back home
+ if (!$scope.user) $location.path('/');
+
+ // Check if there are additional accounts
+ $scope.hasConnectedAdditionalSocialAccounts = function(provider) {
+ for (var i in $scope.user.additionalProvidersData) {
+ return true;
+ }
+
+ return false;
+ };
+
+ // Check if provider is already in use with current user
+ $scope.isConnectedSocialAccount = function(provider) {
+ return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]);
+ };
+
+ // Remove a user social account
+ $scope.removeUserSocialAccount = function(provider) {
+ $scope.success = $scope.error = null;
+
+ $http.delete('/users/accounts', {
+ params: {
+ provider: provider
+ }
+ }).success(function(response) {
+ // If successful show success message and clear form
+ $scope.success = true;
+ $scope.user = Authentication.user = response;
+ }).error(function(response) {
+ $scope.error = response.message;
+ });
+ };
+
+ // Update a user profile
+ $scope.updateUserProfile = function() {
+ $scope.success = $scope.error = null;
+ var user = new Users($scope.user);
+
+ user.$update(function(response) {
+ $scope.success = true;
+ Authentication.user = response;
+ }, function(response) {
+ $scope.error = response.data.message;
+ });
+ };
+
+ // Change user password
+ $scope.changeUserPassword = function() {
+ $scope.success = $scope.error = null;
+
+ $http.post('/users/password', $scope.passwordDetails).success(function(response) {
+ // If successful show success message and clear form
+ $scope.success = true;
+ $scope.passwordDetails = null;
+ }).error(function(response) {
+ $scope.error = response.message;
+ });
+ };
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/users/css/users.css b/public/modules/users/css/users.css
new file mode 100644
index 0000000..7dd49e2
--- /dev/null
+++ b/public/modules/users/css/users.css
@@ -0,0 +1,16 @@
+@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;
+}
diff --git a/public/modules/users/img/buttons/facebook.png b/public/modules/users/img/buttons/facebook.png
new file mode 100644
index 0000000..8ebea4f
Binary files /dev/null and b/public/modules/users/img/buttons/facebook.png differ
diff --git a/public/modules/users/img/buttons/google.png b/public/modules/users/img/buttons/google.png
new file mode 100644
index 0000000..0c00712
Binary files /dev/null and b/public/modules/users/img/buttons/google.png differ
diff --git a/public/modules/users/img/buttons/linkedin.png b/public/modules/users/img/buttons/linkedin.png
new file mode 100644
index 0000000..ff039a4
Binary files /dev/null and b/public/modules/users/img/buttons/linkedin.png differ
diff --git a/public/modules/users/img/buttons/twitter.png b/public/modules/users/img/buttons/twitter.png
new file mode 100644
index 0000000..2e399c1
Binary files /dev/null and b/public/modules/users/img/buttons/twitter.png differ
diff --git a/public/modules/users/services/authentication.client.service.js b/public/modules/users/services/authentication.client.service.js
new file mode 100644
index 0000000..4418b2d
--- /dev/null
+++ b/public/modules/users/services/authentication.client.service.js
@@ -0,0 +1,14 @@
+'use strict';
+
+// Authentication service for user variables
+angular.module('users').factory('Authentication', [
+ function() {
+ var _this = this;
+
+ _this._data = {
+ user: window.user
+ };
+
+ return _this._data;
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/users/services/users.client.service.js b/public/modules/users/services/users.client.service.js
new file mode 100644
index 0000000..664828f
--- /dev/null
+++ b/public/modules/users/services/users.client.service.js
@@ -0,0 +1,12 @@
+'use strict';
+
+// Users service used for communicating with the users REST endpoint
+angular.module('users').factory('Users', ['$resource',
+ function($resource) {
+ return $resource('users', {}, {
+ update: {
+ method: 'PUT'
+ }
+ });
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/users/users.client.module.js b/public/modules/users/users.client.module.js
new file mode 100755
index 0000000..b119986
--- /dev/null
+++ b/public/modules/users/users.client.module.js
@@ -0,0 +1,4 @@
+'use strict';
+
+// Use Applicaion configuration module to register a new module
+ApplicationConfiguration.registerModule('users');
\ No newline at end of file
diff --git a/public/modules/users/views/settings/change-password.client.view.html b/public/modules/users/views/settings/change-password.client.view.html
new file mode 100644
index 0000000..485cb7b
--- /dev/null
+++ b/public/modules/users/views/settings/change-password.client.view.html
@@ -0,0 +1,30 @@
+
+
Change your password
+
+
+
+
\ No newline at end of file
diff --git a/public/modules/users/views/settings/edit-profile.client.view.html b/public/modules/users/views/settings/edit-profile.client.view.html
new file mode 100644
index 0000000..5006dc9
--- /dev/null
+++ b/public/modules/users/views/settings/edit-profile.client.view.html
@@ -0,0 +1,34 @@
+
+
Edit your profile
+
+
+
+
\ No newline at end of file
diff --git a/public/modules/users/views/settings/social-accounts.client.view.html b/public/modules/users/views/settings/social-accounts.client.view.html
new file mode 100644
index 0000000..ffc1c8f
--- /dev/null
+++ b/public/modules/users/views/settings/social-accounts.client.view.html
@@ -0,0 +1,26 @@
+
+
+
\ No newline at end of file
diff --git a/public/modules/users/views/signin.client.view.html b/public/modules/users/views/signin.client.view.html
new file mode 100644
index 0000000..bdf035a
--- /dev/null
+++ b/public/modules/users/views/signin.client.view.html
@@ -0,0 +1,39 @@
+
+
+
\ No newline at end of file
diff --git a/public/modules/users/views/signup.client.view.html b/public/modules/users/views/signup.client.view.html
new file mode 100644
index 0000000..7ba6481
--- /dev/null
+++ b/public/modules/users/views/signup.client.view.html
@@ -0,0 +1,51 @@
+
+