diff --git a/.csslintrc b/.csslintrc
new file mode 100644
index 0000000000..0dab227ebb
--- /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
+}
diff --git a/README.md b/README.md
index 1241cf2ba8..05ac4b3b0e 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,7 @@ Browse the live MEAN.JS example on [http://meanjs.herokuapp.com](http://meanjs.h
## 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)
diff --git a/app/controllers/articles.js b/app/controllers/articles.server.controller.js
similarity index 68%
rename from app/controllers/articles.js
rename to app/controllers/articles.server.controller.js
index 6cdc331873..c60e93035b 100644
--- a/app/controllers/articles.js
+++ b/app/controllers/articles.server.controller.js
@@ -7,6 +7,30 @@ var mongoose = require('mongoose'),
Article = mongoose.model('Article'),
_ = 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 = 'Article 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;
+};
+
/**
* Create a article
*/
@@ -16,9 +40,8 @@ exports.create = function(req, res) {
article.save(function(err) {
if (err) {
- return res.send('users/signup', {
- errors: err.errors,
- article: article
+ return res.send(400, {
+ message: getErrorMessage(err)
});
} else {
res.jsonp(article);
@@ -43,8 +66,8 @@ exports.update = function(req, res) {
article.save(function(err) {
if (err) {
- res.render('error', {
- status: 500
+ return res.send(400, {
+ message: getErrorMessage(err)
});
} else {
res.jsonp(article);
@@ -60,8 +83,8 @@ exports.delete = function(req, res) {
article.remove(function(err) {
if (err) {
- res.render('error', {
- status: 500
+ return res.send(400, {
+ message: getErrorMessage(err)
});
} else {
res.jsonp(article);
@@ -75,8 +98,8 @@ exports.delete = function(req, res) {
exports.list = function(req, res) {
Article.find().sort('-created').populate('user', 'displayName').exec(function(err, articles) {
if (err) {
- res.render('error', {
- status: 500
+ return res.send(400, {
+ message: getErrorMessage(err)
});
} else {
res.jsonp(articles);
@@ -101,7 +124,9 @@ exports.articleByID = function(req, res, next, id) {
*/
exports.hasAuthorization = function(req, res, next) {
if (req.article.user.id !== req.user.id) {
- return res.send(403, 'User is not authorized');
+ return res.send(403, {
+ message: 'User is not authorized'
+ });
}
next();
};
\ No newline at end of file
diff --git a/app/controllers/core.js b/app/controllers/core.server.controller.js
similarity index 80%
rename from app/controllers/core.js
rename to app/controllers/core.server.controller.js
index 7073572639..db02fb5ec7 100644
--- a/app/controllers/core.js
+++ b/app/controllers/core.server.controller.js
@@ -4,7 +4,7 @@
* Module dependencies.
*/
exports.index = function(req, res) {
- res.render('index.html', {
+ res.render('index', {
user: req.user || null
});
};
\ No newline at end of file
diff --git a/app/controllers/users.js b/app/controllers/users.server.controller.js
similarity index 93%
rename from app/controllers/users.js
rename to app/controllers/users.server.controller.js
index 9faaca577a..b8b4c76f6f 100755
--- a/app/controllers/users.js
+++ b/app/controllers/users.server.controller.js
@@ -36,6 +36,9 @@ var getErrorMessage = function(err) {
* 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;
@@ -44,6 +47,7 @@ exports.signup = function(req, res) {
user.provider = 'local';
user.displayName = user.firstName + ' ' + user.lastName;
+ // Then save the user
user.save(function(err) {
if (err) {
return res.send(400, {
@@ -96,6 +100,9 @@ exports.update = function(req, res) {
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);
@@ -233,7 +240,9 @@ exports.userByID = function(req, res, next, id) {
*/
exports.requiresLogin = function(req, res, next) {
if (!req.isAuthenticated()) {
- return res.send(401, 'User is not logged in');
+ return res.send(401, {
+ message: 'User is not logged in'
+ });
}
next();
@@ -242,12 +251,20 @@ exports.requiresLogin = function(req, res, next) {
/**
* User authorizations routing middleware
*/
-exports.hasAuthorization = function(req, res, next) {
- if (req.profile.id !== req.user.id) {
- return res.send(403, 'User is not authorized');
- }
+exports.hasAuthorization = function(roles) {
+ var _this = this;
- next();
+ 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'
+ });
+ }
+ });
+ };
};
/**
@@ -339,7 +356,7 @@ exports.removeOAuthProvider = function(req, res, next) {
// 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');
}
diff --git a/app/models/article.js b/app/models/article.server.model.js
similarity index 100%
rename from app/models/article.js
rename to app/models/article.server.model.js
diff --git a/app/models/user.js b/app/models/user.server.model.js
similarity index 93%
rename from app/models/user.js
rename to app/models/user.server.model.js
index b5cfdff313..6cfe08c231 100755
--- a/app/models/user.js
+++ b/app/models/user.server.model.js
@@ -68,6 +68,13 @@ var UserSchema = new Schema({
},
providerData: {},
additionalProvidersData: {},
+ roles: {
+ type: [{
+ type: String,
+ enum: ['user', 'admin']
+ }],
+ default: ['user']
+ },
updated: {
type: Date
},
@@ -114,8 +121,10 @@ UserSchema.statics.findUniqueUsername = function(username, suffix, callback) {
var _this = this;
var possibleUsername = username + (suffix || '');
- _this.findOne({username: possibleUsername}, function(err, user) {
- if(!err) {
+ _this.findOne({
+ username: possibleUsername
+ }, function(err, user) {
+ if (!err) {
if (!user) {
callback(possibleUsername);
} else {
diff --git a/app/routes/articles.js b/app/routes/articles.js
deleted file mode 100644
index c0d39095a9..0000000000
--- a/app/routes/articles.js
+++ /dev/null
@@ -1,19 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var users = require('../../app/controllers/users'),
- articles = require('../../app/controllers/articles');
-
-module.exports = function(app) {
- // Article Routes
- app.get('/articles', articles.list);
- app.post('/articles', users.requiresLogin, articles.create);
- app.get('/articles/:articleId', articles.read);
- app.put('/articles/:articleId', users.requiresLogin, articles.hasAuthorization, articles.update);
- app.del('/articles/:articleId', users.requiresLogin, articles.hasAuthorization, articles.delete);
-
- // Finish by binding the article middleware
- app.param('articleId', articles.articleByID);
-};
\ No newline at end of file
diff --git a/app/routes/articles.server.routes.js b/app/routes/articles.server.routes.js
new file mode 100644
index 0000000000..e393414177
--- /dev/null
+++ b/app/routes/articles.server.routes.js
@@ -0,0 +1,22 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var users = require('../../app/controllers/users'),
+ articles = require('../../app/controllers/articles');
+
+module.exports = function(app) {
+ // Article Routes
+ app.route('/articles')
+ .get(articles.list)
+ .post(users.requiresLogin, articles.create);
+
+ app.route('/articles/:articleId')
+ .get(articles.read)
+ .put(users.requiresLogin, articles.hasAuthorization, articles.update)
+ .delete(users.requiresLogin, articles.hasAuthorization, articles.delete);
+
+ // Finish by binding the article middleware
+ app.param('articleId', articles.articleByID);
+};
\ No newline at end of file
diff --git a/app/routes/core.js b/app/routes/core.server.routes.js
similarity index 78%
rename from app/routes/core.js
rename to app/routes/core.server.routes.js
index ba232004ee..4cd9616aa9 100644
--- a/app/routes/core.js
+++ b/app/routes/core.server.routes.js
@@ -3,5 +3,5 @@
module.exports = function(app) {
// Root routing
var core = require('../../app/controllers/core');
- app.get('/', core.index);
+ app.route('/').get(core.index);
};
\ No newline at end of file
diff --git a/app/routes/users.js b/app/routes/users.js
deleted file mode 100644
index 23f8126b52..0000000000
--- a/app/routes/users.js
+++ /dev/null
@@ -1,46 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var passport = require('passport');
-
-module.exports = function(app) {
- // User Routes
- var users = require('../../app/controllers/users');
- app.get('/users/me', users.me);
- app.put('/users', users.update);
- app.post('/users/password', users.changePassword);
- app.del('/users/accounts', users.removeOAuthProvider);
-
- // Setting up the users api
- app.post('/auth/signup', users.signup);
- app.post('/auth/signin', users.signin);
- app.get('/auth/signout', users.signout);
-
- // Setting the facebook oauth routes
- app.get('/auth/facebook', passport.authenticate('facebook', {
- scope: ['email']
- }));
- app.get('/auth/facebook/callback', users.oauthCallback('facebook'));
-
- // Setting the twitter oauth routes
- app.get('/auth/twitter', passport.authenticate('twitter'));
- app.get('/auth/twitter/callback', users.oauthCallback('twitter'));
-
- // Setting the google oauth routes
- app.get('/auth/google', passport.authenticate('google', {
- scope: [
- 'https://www.googleapis.com/auth/userinfo.profile',
- 'https://www.googleapis.com/auth/userinfo.email'
- ]
- }));
- app.get('/auth/google/callback', users.oauthCallback('google'));
-
- // Setting the linkedin oauth routes
- app.get('/auth/linkedin', passport.authenticate('linkedin'));
- app.get('/auth/linkedin/callback', users.oauthCallback('linkedin'));
-
- // Finish by binding the user middleware
- app.param('userId', users.userByID);
-};
diff --git a/app/routes/users.server.routes.js b/app/routes/users.server.routes.js
new file mode 100644
index 0000000000..f925eb0618
--- /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/articles.js b/app/tests/article.server.model.test.js
similarity index 100%
rename from app/tests/articles.js
rename to app/tests/article.server.model.test.js
diff --git a/app/tests/users.js b/app/tests/user.server.model.test.js
similarity index 100%
rename from app/tests/users.js
rename to app/tests/user.server.model.test.js
diff --git a/app/views/404.html b/app/views/404.server.view.html
similarity index 71%
rename from app/views/404.html
rename to app/views/404.server.view.html
index c993001842..0074fa456d 100644
--- a/app/views/404.html
+++ b/app/views/404.server.view.html
@@ -1,4 +1,4 @@
-{% extends 'layout.html' %}
+{% extends 'layout.server.view.html' %}
{% block content %}
Page Not Found
diff --git a/app/views/500.html b/app/views/500.server.view.html
similarity index 66%
rename from app/views/500.html
rename to app/views/500.server.view.html
index 7bbded9e38..8e6711b72a 100644
--- a/app/views/500.html
+++ b/app/views/500.server.view.html
@@ -1,4 +1,4 @@
-{% extends 'layout.html' %}
+{% extends 'layout.server.view.html' %}
{% block content %}
Server Error
diff --git a/app/views/index.html b/app/views/index.server.view.html
similarity index 63%
rename from app/views/index.html
rename to app/views/index.server.view.html
index 832522f4d0..7e60893b1f 100644
--- a/app/views/index.html
+++ b/app/views/index.server.view.html
@@ -1,4 +1,4 @@
-{% extends 'layout.html' %}
+{% extends 'layout.server.view.html' %}
{% block content %}
diff --git a/app/views/layout.html b/app/views/layout.html
deleted file mode 100644
index 62848d038b..0000000000
--- a/app/views/layout.html
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
- {{title}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% for modulesCSSFile in modulesCSSFiles %}
- {% endfor %}
-
-
-
-
-
-
-
-
-
- {% block content %}{% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% for modulesJSFile in modulesJSFiles %}
-
- {% endfor %}
-
- {% if process.env.NODE_ENV === 'development' %}
-
-
- {% endif %}
-
-
-
\ No newline at end of file
diff --git a/app/views/layout.server.view.html b/app/views/layout.server.view.html
new file mode 100644
index 0000000000..e13028aed1
--- /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
index 5a6215eb31..e0c9250ba8 100644
--- a/bower.json
+++ b/bower.json
@@ -1,11 +1,10 @@
{
"name": "meanjs",
- "version": "0.2.2",
+ "version": "0.3.0",
"description": "Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.",
"dependencies": {
"bootstrap": "~3",
"angular": "~1.2",
- "angular-cookies": "~1.2",
"angular-resource": "~1.2",
"angular-animate": "~1.2",
"angular-mocks": "~1.2",
diff --git a/config/config.js b/config/config.js
index afe7eb09c0..e694bf4715 100644
--- a/config/config.js
+++ b/config/config.js
@@ -1,15 +1,76 @@
'use strict';
+/**
+ * Module dependencies.
+ */
var _ = require('lodash'),
- utilities = require('./utilities');
+ glob = require('glob');
-// Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV
-process.env.NODE_ENV = ~utilities.walk('./config/env', /(.*)\.js$/).map(function(file) {
- return file.split('/').pop().slice(0, -3);
-}).indexOf(process.env.NODE_ENV) ? process.env.NODE_ENV : 'development';
-
-// Load app configurations
+/**
+ * Load app configurations
+ */
module.exports = _.extend(
- require('./env/all'),
- require('./env/' + process.env.NODE_ENV) || {}
-);
\ No newline at end of file
+ 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
index 46815116cb..aaf9262cf7 100644
--- a/config/env/all.js
+++ b/config/env/all.js
@@ -1,17 +1,42 @@
'use strict';
-var path = require('path'),
- rootPath = path.normalize(__dirname + '/../..');
-
module.exports = {
app: {
title: 'MEAN.JS',
description: 'Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js',
keywords: 'mongodb, express, angularjs, node.js, mongoose, passport'
},
- root: rootPath,
port: process.env.PORT || 3000,
templateEngine: 'swig',
sessionSecret: 'MEAN',
- sessionCollection: 'sessions'
-};
\ No newline at end of file
+ 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-animate/angular-animate.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'
+ ]
+ }
+};
diff --git a/config/env/development.js b/config/env/development.js
index dc5837249b..43dc47d03e 100644
--- a/config/env/development.js
+++ b/config/env/development.js
@@ -6,23 +6,23 @@ module.exports = {
title: 'MEAN.JS - Development Environment'
},
facebook: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/facebook/callback'
+ clientID: process.env.FACEBOOK_ID || 'APP_ID',
+ clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/facebook/callback'
},
twitter: {
- clientID: 'CONSUMER_KEY',
- clientSecret: 'CONSUMER_SECRET',
- callbackURL: 'http://localhost:3000/auth/twitter/callback'
+ clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
+ clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
+ callbackPath: '/auth/twitter/callback'
},
google: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/google/callback'
+ clientID: process.env.GOOGLE_ID || 'APP_ID',
+ clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/google/callback'
},
linkedin: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/linkedin/callback'
+ clientID: process.env.LINKEDIN_ID || 'APP_ID',
+ clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/linkedin/callback'
}
-};
\ No newline at end of file
+};
diff --git a/config/env/production.js b/config/env/production.js
index 32f970d69c..876efd1141 100644
--- a/config/env/production.js
+++ b/config/env/production.js
@@ -1,25 +1,43 @@
'use strict';
module.exports = {
- db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean',
+ db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean',
+ 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-animate/angular-animate.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: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/facebook/callback'
+ clientID: process.env.FACEBOOK_ID || 'APP_ID',
+ clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/facebook/callback'
},
twitter: {
- clientID: 'CONSUMER_KEY',
- clientSecret: 'CONSUMER_SECRET',
- callbackURL: 'http://localhost:3000/auth/twitter/callback'
+ clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
+ clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
+ callbackPath: '/auth/twitter/callback'
},
google: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/google/callback'
+ clientID: process.env.GOOGLE_ID || 'APP_ID',
+ clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/google/callback'
},
linkedin: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/linkedin/callback'
+ clientID: process.env.LINKEDIN_ID || 'APP_ID',
+ clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/linkedin/callback'
}
-};
\ No newline at end of file
+};
diff --git a/config/env/test.js b/config/env/test.js
index a5a01a2998..a0f72fcb3a 100644
--- a/config/env/test.js
+++ b/config/env/test.js
@@ -7,23 +7,23 @@ module.exports = {
title: 'MEAN.JS - Test Environment'
},
facebook: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/facebook/callback'
+ clientID: process.env.FACEBOOK_ID || 'APP_ID',
+ clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/facebook/callback'
},
twitter: {
- clientID: 'CONSUMER_KEY',
- clientSecret: 'CONSUMER_SECRET',
- callbackURL: 'http://localhost:3000/auth/twitter/callback'
+ clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
+ clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
+ callbackPath: '/auth/twitter/callback'
},
google: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/google/callback'
+ clientID: process.env.GOOGLE_ID || 'APP_ID',
+ clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/google/callback'
},
linkedin: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/linkedin/callback'
+ clientID: process.env.LINKEDIN_ID || 'APP_ID',
+ clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
+ callbackPath: '/auth/linkedin/callback'
}
-};
\ No newline at end of file
+};
diff --git a/config/env/travis.js b/config/env/travis.js
deleted file mode 100644
index cc03946045..0000000000
--- a/config/env/travis.js
+++ /dev/null
@@ -1,29 +0,0 @@
-'use strict';
-
-module.exports = {
- db: 'mongodb://localhost/mean-travis',
- port: 3001,
- app: {
- title: 'MEAN.JS - Travis Environment'
- },
- facebook: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/facebook/callback'
- },
- twitter: {
- clientID: 'CONSUMER_KEY',
- clientSecret: 'CONSUMER_SECRET',
- callbackURL: 'http://localhost:3000/auth/twitter/callback'
- },
- google: {
- clientID: 'APP_ID',
- clientSecret: 'APP_SECRET',
- callbackURL: 'http://localhost:3000/auth/google/callback'
- },
- linkedin: {
- clientID: 'APP_ID',
- clientSecret: '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
index d83ee8fb92..89fa4337d6 100755
--- a/config/express.js
+++ b/config/express.js
@@ -4,32 +4,38 @@
* 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')(express),
+ mongoStore = require('connect-mongo')({
+ session: session
+ }),
flash = require('connect-flash'),
config = require('./config'),
consolidate = require('consolidate'),
- path = require('path'),
- utilities = require('./utilities');
+ path = require('path');
module.exports = function(db) {
// Initialize express app
var app = express();
- // Initialize models
- utilities.walk('./app/models').forEach(function(modelPath) {
+ // Globbing model files
+ config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
require(path.resolve(modelPath));
});
- // Setting the environment locals
- app.locals({
- title: config.app.title,
- description: config.app.description,
- keywords: config.app.keywords,
- facebookAppId: config.facebook.clientID,
- modulesJSFiles: utilities.walk('./public/modules', /(.*)\.(js)/, /(.*)\.(spec.js)/, './public'),
- modulesCSSFiles: utilities.walk('./public/modules', /(.*)\.(css)/, null, './public')
- });
+ // 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) {
@@ -38,7 +44,7 @@ module.exports = function(db) {
});
// Should be placed before express.static
- app.use(express.compress({
+ app.use(compress({
filter: function(req, res) {
return (/json|text|javascript|css/).test(res.getHeader('Content-Type'));
},
@@ -49,41 +55,36 @@ module.exports = function(db) {
app.set('showStackError', true);
// Set swig as the template engine
- app.engine('html', consolidate[config.templateEngine]);
+ app.engine('server.view.html', consolidate[config.templateEngine]);
// Set views path and view engine
- app.set('view engine', 'html');
- app.set('views', config.root + '/app/views');
+ app.set('view engine', 'server.view.html');
+ app.set('views', './app/views');
- // Application Configuration for development environment
- app.configure('development', function() {
- // Enable logger
- app.use(express.logger('dev'));
+ // Environment dependent middleware
+ if (process.env.NODE_ENV === 'development') {
+ // Enable logger (morgan)
+ app.use(morgan('dev'));
// Disable views cache
app.set('view cache', false);
- });
-
- // Application Configuration for production environment
- app.configure('production', function() {
- app.locals({
- cache: 'memory' // To solve SWIG Cache Issues
- });
- });
+ } else if (process.env.NODE_ENV === 'production') {
+ app.locals.cache = 'memory';
+ }
- // request body parsing middleware should be above methodOverride
- app.use(express.urlencoded());
- app.use(express.json());
- app.use(express.methodOverride());
+ // 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(express.cookieParser());
+ // CookieParser should be above session
+ app.use(cookieParser());
- // express/mongo session storage
- app.use(express.session({
+ // Express MongoDB session storage
+ app.use(session({
secret: config.sessionSecret,
store: new mongoStore({
db: db.connection.db,
@@ -98,14 +99,18 @@ module.exports = function(db) {
// connect flash for flash messages
app.use(flash());
- // routes should be at the last
- app.use(app.router);
-
+ // 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(config.root + '/public'));
+ app.use(express.static(path.resolve('./public')));
- // Load Routes
- utilities.walk('./app/routes').forEach(function(routePath) {
+ // Globbing routing files
+ config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) {
require(path.resolve(routePath))(app);
});
@@ -118,14 +123,14 @@ module.exports = function(db) {
console.error(err.stack);
// Error page
- res.status(500).render('500.html', {
+ res.status(500).render('500', {
error: err.stack
});
});
// Assume 404 since no middleware responded
app.use(function(req, res) {
- res.status(404).render('404.html', {
+ res.status(404).render('404', {
url: req.originalUrl,
error: 'Not Found'
});
diff --git a/config/init.js b/config/init.js
new file mode 100644
index 0000000000..9a2ae984e9
--- /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 envrionment 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 + '" envrionment using develpoment instead');
+ } else {
+ console.log('\x1b[31m', 'NODE_ENV is not defined! Using default develpoment envrionment');
+ }
+
+ process.env.NODE_ENV = 'development';
+ } else {
+ console.log('\x1b[7m', 'Application loaded using the "' + process.env.NODE_ENV + '" envrionment 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
index cbd25e6a90..9e3a31ae5a 100755
--- a/config/passport.js
+++ b/config/passport.js
@@ -3,7 +3,7 @@
var passport = require('passport'),
User = require('mongoose').model('User'),
path = require('path'),
- utilities = require('./utilities');
+ config = require('./config');
module.exports = function() {
// Serialize sessions
@@ -21,7 +21,7 @@ module.exports = function() {
});
// Initialize strategies
- utilities.walk('./config/strategies').forEach(function(strategyPath) {
- require(path.resolve(strategyPath))();
+ 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
index 113cb74b25..51d5f1725e 100644
--- a/config/strategies/facebook.js
+++ b/config/strategies/facebook.js
@@ -1,6 +1,10 @@
'use strict';
+/**
+ * Module dependencies.
+ */
var passport = require('passport'),
+ url = require('url'),
FacebookStrategy = require('passport-facebook').Strategy,
config = require('../config'),
users = require('../../app/controllers/users');
@@ -10,7 +14,7 @@ module.exports = function() {
passport.use(new FacebookStrategy({
clientID: config.facebook.clientID,
clientSecret: config.facebook.clientSecret,
- callbackURL: config.facebook.callbackURL,
+ callbackURL: config.facebook.callbackPath,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
@@ -35,4 +39,4 @@ module.exports = function() {
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
-};
\ No newline at end of file
+};
diff --git a/config/strategies/google.js b/config/strategies/google.js
index f06c0998fd..dceb96c708 100644
--- a/config/strategies/google.js
+++ b/config/strategies/google.js
@@ -1,38 +1,42 @@
'use strict';
+/**
+ * Module dependencies.
+ */
var passport = require('passport'),
- GoogleStrategy = require('passport-google-oauth').OAuth2Strategy,
- config = require('../config'),
- users = require('../../app/controllers/users');
+ 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
- };
+ // Use google strategy
+ passport.use(new GoogleStrategy({
+ clientID: config.google.clientID,
+ clientSecret: config.google.clientSecret,
+ callbackURL: config.google.callbackPath,
+ 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;
- // Save the user OAuth profile
- users.saveOAuthUserProfile(req, providerUserProfile, done);
- }
- ));
+ // 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
index 2ba5736d06..e29d5d433d 100644
--- a/config/strategies/linkedin.js
+++ b/config/strategies/linkedin.js
@@ -1,6 +1,10 @@
'use strict';
+/**
+ * Module dependencies.
+ */
var passport = require('passport'),
+ url = require('url'),
LinkedInStrategy = require('passport-linkedin').Strategy,
config = require('../config'),
users = require('../../app/controllers/users');
@@ -10,7 +14,7 @@ module.exports = function() {
passport.use(new LinkedInStrategy({
consumerKey: config.linkedin.clientID,
consumerSecret: config.linkedin.clientSecret,
- callbackURL: config.linkedin.callbackURL,
+ callbackURL: config.linkedin.callbackPath,
passReqToCallback: true,
profileFields: ['id', 'first-name', 'last-name', 'email-address']
},
@@ -36,4 +40,4 @@ module.exports = function() {
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
-};
\ No newline at end of file
+};
diff --git a/config/strategies/local.js b/config/strategies/local.js
index c4850a6e5d..97f8d43098 100644
--- a/config/strategies/local.js
+++ b/config/strategies/local.js
@@ -1,10 +1,12 @@
'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({
diff --git a/config/strategies/twitter.js b/config/strategies/twitter.js
index cf3e6cc2b1..fa62fc9bf1 100644
--- a/config/strategies/twitter.js
+++ b/config/strategies/twitter.js
@@ -1,6 +1,10 @@
'use strict';
+/**
+ * Module dependencies.
+ */
var passport = require('passport'),
+ url = require('url'),
TwitterStrategy = require('passport-twitter').Strategy,
config = require('../config'),
users = require('../../app/controllers/users');
@@ -10,7 +14,7 @@ module.exports = function() {
passport.use(new TwitterStrategy({
consumerKey: config.twitter.clientID,
consumerSecret: config.twitter.clientSecret,
- callbackURL: config.twitter.callbackURL,
+ callbackURL: config.twitter.callbackPath,
passReqToCallback: true
},
function(req, token, tokenSecret, profile, done) {
@@ -32,4 +36,4 @@ module.exports = function() {
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
-};
\ No newline at end of file
+};
diff --git a/config/utilities.js b/config/utilities.js
deleted file mode 100644
index cb6da59221..0000000000
--- a/config/utilities.js
+++ /dev/null
@@ -1,39 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var fs = require('fs');
-
-// Walk function to recursively get files
-var _walk = function(root, includeRegex, excludeRegex, removePath) {
- var output = [];
- var directories = [];
- includeRegex = includeRegex || /(.*)\.(js|coffee)$/;
-
- // First read through files
- fs.readdirSync(root).forEach(function(file) {
- var newPath = root + '/' + file;
- var stat = fs.statSync(newPath);
-
- if (stat.isFile()) {
- if (includeRegex.test(file) && (!excludeRegex || !excludeRegex.test(file))) {
- output.push(newPath.replace(removePath, ''));
- }
- } else if (stat.isDirectory()) {
- directories.push(newPath);
- }
- });
-
- // Then recursively add directories
- directories.forEach(function(directory) {
- output = output.concat(_walk(directory, includeRegex, excludeRegex, removePath));
- });
-
- return output;
-};
-
-/**
- * Exposing the walk function
- */
-exports.walk = _walk;
diff --git a/gruntfile.js b/gruntfile.js
index 3778d02f2e..9dff768f99 100644
--- a/gruntfile.js
+++ b/gruntfile.js
@@ -1,99 +1,133 @@
'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'],
- options: {
- livereload: true,
- }
- }
- },
- jshint: {
- all: {
- src: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', 'public/js/**/*.js', 'public/modules/**/*.js'],
- options: {
- jshintrc: true
- }
- }
- },
- 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'
- }
- }
- });
+ // 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
- grunt.loadNpmTasks('grunt-contrib-watch');
- grunt.loadNpmTasks('grunt-contrib-jshint');
- grunt.loadNpmTasks('grunt-mocha-test');
- grunt.loadNpmTasks('grunt-karma');
- grunt.loadNpmTasks('grunt-nodemon');
- grunt.loadNpmTasks('grunt-concurrent');
- grunt.loadNpmTasks('grunt-env');
+ // Load NPM tasks
+ require('load-grunt-tasks')(grunt);
- //Making grunt default to force in order not to break the project.
- grunt.option('force', true);
+ // Making grunt default to force in order not to break the project.
+ grunt.option('force', true);
- //Default task(s).
- grunt.registerTask('default', ['jshint', 'concurrent']);
+ // A Task for loading the configuration object
+ grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() {
+ var config = require('./config/config');
- //Test task.
- grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']);
+ 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
index e4c06df54a..99103482ff 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -3,10 +3,7 @@
/**
* Module dependencies.
*/
-var utilities = require('./config/utilities');
-
-// Grabbing module files using the walk function
-var modulesJSFiles = utilities.walk('./public/modules', /(.*)\.js$/);
+var applicationConfiguration = require('./config/config');
// Karma configuration
module.exports = function(config) {
@@ -15,18 +12,7 @@ module.exports = function(config) {
frameworks: ['jasmine'],
// List of files / patterns to load in the browser
- files: [
- 'public/lib/angular/angular.js',
- 'public/lib/angular-animate/angular-animate.js',
- 'public/lib/angular-mocks/angular-mocks.js',
- 'public/lib/angular-cookies/angular-cookies.js',
- 'public/lib/angular-resource/angular-resource.js',
- 'public/lib/angular-bootstrap/ui-bootstrap.js',
- 'public/lib/angular-ui-utils/ui-utils.js',
- 'public/lib/angular-ui-router/release/angular-ui-router.js',
- 'public/js/config.js',
- 'public/js/application.js',
- ].concat(modulesJSFiles),
+ files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests),
// Test results reporter to use
// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
diff --git a/package.json b/package.json
index 2261bfaa38..bf7aed985c 100755
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "meanjs",
"description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js.",
- "version": "0.2.3",
+ "version": "0.3.0",
"private": false,
"author": "https://github.com/meanjs/mean/graphs/contributors",
"repository": {
@@ -18,12 +18,19 @@
"postinstall": "bower install --config.interactive=false"
},
"dependencies": {
- "express": "~3.5.1",
+ "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",
- "connect-mongo": "~0.4.0",
- "connect-flash": "~0.1.1",
"passport": "~0.2.0",
"passport-local": "~1.0.0",
"passport-facebook": "~1.0.2",
@@ -31,9 +38,10 @@
"passport-linkedin": "~0.1.3",
"passport-google-oauth": "~0.1.5",
"lodash": "~2.4.1",
- "forever": "~0.11.00",
+ "forever": "~0.11.0",
"bower": "~1.3.1",
- "grunt-cli": "~0.1.13"
+ "grunt-cli": "~0.1.13",
+ "glob": "~3.2.9"
},
"devDependencies": {
"supertest": "~0.10.0",
@@ -42,10 +50,14 @@
"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",
diff --git a/public/js/application.js b/public/application.js
similarity index 100%
rename from public/js/application.js
rename to public/application.js
diff --git a/public/js/config.js b/public/config.js
similarity index 83%
rename from public/js/config.js
rename to public/config.js
index e429f257f1..f61ff77ac9 100644
--- a/public/js/config.js
+++ b/public/config.js
@@ -4,7 +4,7 @@
var ApplicationConfiguration = (function() {
// Init module configuration options
var applicationModuleName = 'mean';
- var applicationModuleVendorDependencies = ['ngResource', 'ngCookies', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils'];
+ var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils'];
// Add a new vertical module
var registerModule = function(moduleName) {
diff --git a/public/dist/application.min.css b/public/dist/application.min.css
new file mode 100644
index 0000000000..7769639b99
--- /dev/null
+++ b/public/dist/application.min.css
@@ -0,0 +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}@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}
\ No newline at end of file
diff --git a/public/dist/application.min.js b/public/dist/application.min.js
new file mode 100644
index 0000000000..62a5f946a5
--- /dev/null
+++ b/public/dist/application.min.js
@@ -0,0 +1 @@
+"use strict";var ApplicationConfiguration=function(){return{applicationModuleName:"mean",applicationModuleVendorDependencies:["ngResource","ngAnimate","ui.router","ui.bootstrap","ui.utils"],registerModule:function(moduleName){angular.module(moduleName,[]),angular.module(this.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("articles"),ApplicationConfiguration.registerModule("core"),ApplicationConfiguration.registerModule("users"),angular.module("articles").run(["Menus",function(Menus){Menus.addMenuItem("topbar","Articles","articles"),Menus.addMenuItem("topbar","New Article","articles/create")}]),angular.module("articles").config(["$stateProvider",function($stateProvider){$stateProvider.state("listArticles",{url:"/articles",templateUrl:"modules/articles/views/list-articles.client.view.html"}).state("createArticle",{url:"/articles/create",templateUrl:"modules/articles/views/create-article.client.view.html"}).state("viewArticle",{url:"/articles/:articleId",templateUrl:"modules/articles/views/view-article.client.view.html"}).state("editArticle",{url:"/articles/:articleId/edit",templateUrl:"modules/articles/views/edit-article.client.view.html"})}]),angular.module("articles").controller("ArticlesController",["$scope","$stateParams","$location","Authentication","Articles",function($scope,$stateParams,$location,Authentication,Articles){$scope.authentication=Authentication,$scope.create=function(){var article=new Articles({title:this.title,content:this.content});article.$save(function(response){$location.path("articles/"+response._id)},function(errorResponse){$scope.error=errorResponse.data.message}),this.title="",this.content=""},$scope.remove=function(article){if(article){article.$remove();for(var i in $scope.articles)$scope.articles[i]===article&&$scope.articles.splice(i,1)}else $scope.article.$remove(function(){$location.path("articles")})},$scope.update=function(){var article=$scope.article;article.updated||(article.updated=[]),article.updated.push((new Date).getTime()),article.$update(function(){$location.path("articles/"+article._id)},function(errorResponse){$scope.error=errorResponse.data.message})},$scope.find=function(){Articles.query(function(articles){$scope.articles=articles})},$scope.findOne=function(){Articles.get({articleId:$stateParams.articleId},function(article){$scope.article=article})}}]),angular.module("articles").factory("Articles",["$resource",function($resource){return $resource("articles/:articleId",{articleId:"@_id"},{update:{method:"PUT"}})}]),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}}]),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.requiresAuthentication;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,requiresAuthentication,roles){return this.menus[menuId]={requiresAuthentication:requiresAuthentication||!0,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,menuItemUIRoute,requiresAuthentication,roles){return this.validateMenuExistance(menuId),this.menus[menuId].items.push({title:menuItemTitle,link:menuItemURL,uiRoute:menuItemUIRoute||"/"+menuItemURL,requiresAuthentication:requiresAuthentication||!1,roles:roles||this.defaultRoles,shouldRender:shouldRender}),this.menus[menuId]},this.removeMenuItem=function(menuId,menuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].menuItemURL===menuItemURL&&this.menus[menuId].items.splice(itemIndex,1);return this.menus[menuId]},this.addMenu("topbar")}]),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");break;case 403:}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(){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;var user=new Users($scope.user);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(){$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"}})}]);
\ No newline at end of file
diff --git a/public/img/.gitignore b/public/img/.gitignore
deleted file mode 100755
index e69de29bb2..0000000000
diff --git a/public/modules/articles/articles.js b/public/modules/articles/articles.client.module.js
similarity index 100%
rename from public/modules/articles/articles.js
rename to public/modules/articles/articles.client.module.js
diff --git a/public/modules/articles/config/articles.client.config.js b/public/modules/articles/config/articles.client.config.js
new file mode 100644
index 0000000000..b69ee99354
--- /dev/null
+++ b/public/modules/articles/config/articles.client.config.js
@@ -0,0 +1,10 @@
+'use strict';
+
+// Configuring the Articles module
+angular.module('articles').run(['Menus',
+ function(Menus) {
+ // Set top bar menu items
+ Menus.addMenuItem('topbar', 'Articles', 'articles');
+ Menus.addMenuItem('topbar', 'New Article', 'articles/create');
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/articles/config/routes.js b/public/modules/articles/config/articles.client.routes.js
similarity index 59%
rename from public/modules/articles/config/routes.js
rename to public/modules/articles/config/articles.client.routes.js
index 5d5b8dad4e..1531a9a57c 100755
--- a/public/modules/articles/config/routes.js
+++ b/public/modules/articles/config/articles.client.routes.js
@@ -7,19 +7,19 @@ angular.module('articles').config(['$stateProvider',
$stateProvider.
state('listArticles', {
url: '/articles',
- templateUrl: 'modules/articles/views/list.html'
+ templateUrl: 'modules/articles/views/list-articles.client.view.html'
}).
state('createArticle', {
url: '/articles/create',
- templateUrl: 'modules/articles/views/create.html'
+ templateUrl: 'modules/articles/views/create-article.client.view.html'
}).
state('viewArticle', {
url: '/articles/:articleId',
- templateUrl: 'modules/articles/views/view.html'
+ templateUrl: 'modules/articles/views/view-article.client.view.html'
}).
state('editArticle', {
url: '/articles/:articleId/edit',
- templateUrl: 'modules/articles/views/edit.html'
+ templateUrl: 'modules/articles/views/edit-article.client.view.html'
});
}
]);
\ No newline at end of file
diff --git a/public/modules/articles/controllers/articles.client.controller.js b/public/modules/articles/controllers/articles.client.controller.js
new file mode 100644
index 0000000000..02ab64c908
--- /dev/null
+++ b/public/modules/articles/controllers/articles.client.controller.js
@@ -0,0 +1,58 @@
+'use strict';
+
+angular.module('articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Authentication', 'Articles',
+ function($scope, $stateParams, $location, Authentication, Articles) {
+ $scope.authentication = Authentication;
+
+ $scope.create = function() {
+ var article = new Articles({
+ title: this.title,
+ content: this.content
+ });
+ article.$save(function(response) {
+ $location.path('articles/' + response._id);
+ }, function(errorResponse) {
+ $scope.error = errorResponse.data.message;
+ });
+
+ this.title = '';
+ this.content = '';
+ };
+
+ $scope.remove = function(article) {
+ if (article) {
+ article.$remove();
+
+ for (var i in $scope.articles) {
+ if ($scope.articles[i] === article) {
+ $scope.articles.splice(i, 1);
+ }
+ }
+ } else {
+ $scope.article.$remove(function() {
+ $location.path('articles');
+ });
+ }
+ };
+
+ $scope.update = function() {
+ var article = $scope.article;
+
+ article.$update(function() {
+ $location.path('articles/' + article._id);
+ }, function(errorResponse) {
+ $scope.error = errorResponse.data.message;
+ });
+ };
+
+ $scope.find = function() {
+ $scope.articles = Articles.query();
+ };
+
+ $scope.findOne = function() {
+ $scope.article = Articles.get({
+ articleId: $stateParams.articleId
+ });
+ };
+ }
+]);
\ No newline at end of file
diff --git a/public/modules/articles/controllers/articles.js b/public/modules/articles/controllers/articles.js
deleted file mode 100644
index 27eee82fdc..0000000000
--- a/public/modules/articles/controllers/articles.js
+++ /dev/null
@@ -1,62 +0,0 @@
-'use strict';
-
-angular.module('articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Authentication', 'Articles',
- function($scope, $stateParams, $location, Authentication, Articles) {
- $scope.authentication = Authentication;
-
- $scope.create = function() {
- var article = new Articles({
- title: this.title,
- content: this.content
- });
- article.$save(function(response) {
- $location.path('articles/' + response._id);
- });
-
- this.title = '';
- this.content = '';
- };
-
- $scope.remove = function(article) {
- if (article) {
- article.$remove();
-
- for (var i in $scope.articles) {
- if ($scope.articles[i] === article) {
- $scope.articles.splice(i, 1);
- }
- }
- } else {
- $scope.article.$remove(function() {
- $location.path('articles');
- });
- }
- };
-
- $scope.update = function() {
- var article = $scope.article;
- if (!article.updated) {
- article.updated = [];
- }
- article.updated.push(new Date().getTime());
-
- article.$update(function() {
- $location.path('articles/' + article._id);
- });
- };
-
- $scope.find = function() {
- Articles.query(function(articles) {
- $scope.articles = articles;
- });
- };
-
- $scope.findOne = function() {
- Articles.get({
- articleId: $stateParams.articleId
- }, function(article) {
- $scope.article = article;
- });
- };
- }
-]);
\ No newline at end of file
diff --git a/public/modules/articles/services/articles.js b/public/modules/articles/services/articles.client.service.js
similarity index 100%
rename from public/modules/articles/services/articles.js
rename to public/modules/articles/services/articles.client.service.js
diff --git a/public/modules/articles/tests/articles.spec.js b/public/modules/articles/tests/articles.client.controller.test.js
similarity index 100%
rename from public/modules/articles/tests/articles.spec.js
rename to public/modules/articles/tests/articles.client.controller.test.js
diff --git a/public/modules/articles/views/create.html b/public/modules/articles/views/create-article.client.view.html
similarity index 86%
rename from public/modules/articles/views/create.html
rename to public/modules/articles/views/create-article.client.view.html
index ed0bf7aa20..fa2331374c 100644
--- a/public/modules/articles/views/create.html
+++ b/public/modules/articles/views/create-article.client.view.html
@@ -3,7 +3,7 @@
New Article
-
+
No articles yet, why don't you
create one?
\ No newline at end of file
diff --git a/public/modules/articles/views/view.html b/public/modules/articles/views/view-article.client.view.html
similarity index 100%
rename from public/modules/articles/views/view.html
rename to public/modules/articles/views/view-article.client.view.html
diff --git a/public/modules/core/config/routes.js b/public/modules/core/config/core.client.routes.js
similarity index 84%
rename from public/modules/core/config/routes.js
rename to public/modules/core/config/core.client.routes.js
index 270503b413..894e3a6caf 100755
--- a/public/modules/core/config/routes.js
+++ b/public/modules/core/config/core.client.routes.js
@@ -10,7 +10,7 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
$stateProvider.
state('home', {
url: '/',
- templateUrl: 'modules/core/views/home.html'
+ templateUrl: 'modules/core/views/home.client.view.html'
});
}
]);
\ No newline at end of file
diff --git a/public/modules/core/controllers/header.js b/public/modules/core/controllers/header.client.controller.js
similarity index 51%
rename from public/modules/core/controllers/header.js
rename to public/modules/core/controllers/header.client.controller.js
index c603172914..4fe41f2af0 100644
--- a/public/modules/core/controllers/header.js
+++ b/public/modules/core/controllers/header.client.controller.js
@@ -1,19 +1,10 @@
'use strict';
-angular.module('core').controller('HeaderController', ['$scope', 'Authentication',
- function($scope, Authentication) {
+angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus',
+ function($scope, Authentication, Menus) {
$scope.authentication = Authentication;
$scope.isCollapsed = false;
-
- $scope.menu = [{
- title: 'Articles',
- link: 'articles',
- uiRoute: '/articles'
- }, {
- title: 'New Article',
- link: 'articles/create',
- uiRoute: '/articles/create'
- }];
+ $scope.menu = Menus.getMenu('topbar');
$scope.toggleCollapsibleMenu = function() {
$scope.isCollapsed = !$scope.isCollapsed;
diff --git a/public/modules/core/controllers/home.js b/public/modules/core/controllers/home.client.controller.js
similarity index 100%
rename from public/modules/core/controllers/home.js
rename to public/modules/core/controllers/home.client.controller.js
diff --git a/public/modules/core/core.js b/public/modules/core/core.client.module.js
similarity index 100%
rename from public/modules/core/core.js
rename to public/modules/core/core.client.module.js
diff --git a/public/css/common.css b/public/modules/core/css/core.css
similarity index 86%
rename from public/css/common.css
rename to public/modules/core/css/core.css
index a1a2546a61..30aebaaf0e 100644
--- a/public/css/common.css
+++ b/public/modules/core/css/core.css
@@ -1,7 +1,7 @@
.content {
margin-top: 50px;
}
-a.undecorated-link:hover {
+.undecorated-link:hover {
text-decoration: none;
}
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
diff --git a/public/img/brand/favicon.ico b/public/modules/core/img/brand/favicon.ico
similarity index 100%
rename from public/img/brand/favicon.ico
rename to public/modules/core/img/brand/favicon.ico
diff --git a/public/img/brand/logo.png b/public/modules/core/img/brand/logo.png
similarity index 100%
rename from public/img/brand/logo.png
rename to public/modules/core/img/brand/logo.png
diff --git a/public/img/loaders/loader.gif b/public/modules/core/img/loaders/loader.gif
similarity index 100%
rename from public/img/loaders/loader.gif
rename to public/modules/core/img/loaders/loader.gif
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 0000000000..66d6bac912
--- /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.spec.js b/public/modules/core/tests/header.client.controller.test.js
similarity index 100%
rename from public/modules/core/tests/header.spec.js
rename to public/modules/core/tests/header.client.controller.test.js
diff --git a/public/modules/core/tests/home.spec.js b/public/modules/core/tests/home.client.controller.test.js
similarity index 100%
rename from public/modules/core/tests/home.spec.js
rename to public/modules/core/tests/home.client.controller.test.js
diff --git a/public/modules/core/views/header.html b/public/modules/core/views/header.client.view.html
similarity index 87%
rename from public/modules/core/views/header.html
rename to public/modules/core/views/header.client.view.html
index 918c882be5..9c93c6e717 100644
--- a/public/modules/core/views/header.html
+++ b/public/modules/core/views/header.client.view.html
@@ -9,8 +9,8 @@
MEAN.JS