diff --git a/.editorconfig b/.editorconfig
index ed90a06898..5f5f16c70c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,25 +1,36 @@
# EditorConfig is awesome: http://EditorConfig.org
-# How-to with your editor: http://editorconfig.org/#download
+# Howto with your editor: http://editorconfig.org/#download
+# Sublime: https://github.com/sindresorhus/editorconfig-sublime
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
-[*]
+[**]
end_of_line = lf
-indent_style = tab
insert_final_newline = true
-[{Dockerfile,Procfile}]
-trim_trailing_whitespace = true
-
-# Standard at: https://github.com/felixge/node-style-guide
-[{*.js,*.json}]
+# Standard at: https://github.com/felixge/node-style-guide
+[**.js, **.json]
trim_trailing_whitespace = true
+indent_style = tab
quote_type = single
curly_bracket_next_line = false
spaces_around_operators = true
space_after_control_statements = true
space_after_anonymous_functions = false
spaces_in_brackets = false
+
+# No Standard. Please document a standard if different from .js
+[**.yml, **.html, **.css]
+trim_trailing_whitespace = true
+indent_style = tab
+
+# No standard. Please document a standard if different from .js
+[**.md]
+indent_style = tab
+
+# Standard at:
+[Makefile]
+indent_style = tab
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index c519f47471..8eb8d6fd26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,9 @@ app/tests/coverage/
config/sslcerts/*.pem
access.log
public/dist/
+uploads
+modules/users/client/img/profile/uploads
+*.pem
# Sublime editor
# ==============
diff --git a/.jshintrc b/.jshintrc
index 4cd07cdcab..b3a00dc361 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,5 +1,7 @@
{
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
+ "mocha": true, // Enable globals available when code is running inside of the Mocha tests.
+ "jasmine": true, // Enable globals available when code is running inside of the Jasmine tests.
"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.).
@@ -18,25 +20,17 @@
"trailing": true, // Prohibit trailing whitespaces.
"smarttabs": false, // Suppresses warnings about mixed tabs and spaces
"globals": { // Globals variables.
- "jasmine": true,
"angular": true,
+ "io": true,
"ApplicationConfiguration": true
},
"predef": [ // Extra globals.
- "define",
- "require",
- "exports",
- "module",
- "describe",
- "before",
- "beforeEach",
- "after",
- "afterEach",
- "it",
"inject",
- "expect"
+ "by",
+ "browser",
+ "element"
],
"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
index e4e50baab8..4611d35f44 100644
--- a/.slugignore
+++ b/.slugignore
@@ -1 +1 @@
-/app/tests
\ No newline at end of file
+/app/tests
diff --git a/Procfile b/Procfile
old mode 100755
new mode 100644
diff --git a/README.md b/README.md
index 4b1a5accbe..276db76565 100644
--- a/README.md
+++ b/README.md
@@ -64,12 +64,12 @@ $ 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 install command to install all the front-end modules needed for the application.
+* Finally, when the install process is over, npm will initiate a bower install command 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:
+After the install process is over, you'll be able to run your application using Grunt, just run grunt default task:
-```bash
+```
$ grunt
```
diff --git a/app/controllers/core.server.controller.js b/app/controllers/core.server.controller.js
deleted file mode 100644
index 5dfdd5e494..0000000000
--- a/app/controllers/core.server.controller.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-exports.index = function(req, res) {
- res.render('index', {
- user: req.user || null,
- request: req
- });
-};
diff --git a/app/controllers/users/users.authorization.server.controller.js b/app/controllers/users/users.authorization.server.controller.js
deleted file mode 100644
index 932e49061e..0000000000
--- a/app/controllers/users/users.authorization.server.controller.js
+++ /dev/null
@@ -1,52 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var _ = require('lodash'),
- mongoose = require('mongoose'),
- User = mongoose.model('User');
-
-/**
- * User middleware
- */
-exports.userByID = function(req, res, next, id) {
- User.findById(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.status(401).send({
- 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.status(403).send({
- message: 'User is not authorized'
- });
- }
- });
- };
-};
diff --git a/app/controllers/users/users.profile.server.controller.js b/app/controllers/users/users.profile.server.controller.js
deleted file mode 100644
index 8e438f7c17..0000000000
--- a/app/controllers/users/users.profile.server.controller.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var _ = require('lodash'),
- errorHandler = require('../errors.server.controller.js'),
- mongoose = require('mongoose'),
- passport = require('passport'),
- User = mongoose.model('User');
-
-/**
- * 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.status(400).send({
- message: errorHandler.getErrorMessage(err)
- });
- } else {
- req.login(user, function(err) {
- if (err) {
- res.status(400).send(err);
- } else {
- res.json(user);
- }
- });
- }
- });
- } else {
- res.status(400).send({
- message: 'User is not signed in'
- });
- }
-};
-
-/**
- * Send User
- */
-exports.me = function(req, res) {
- res.json(req.user || null);
-};
diff --git a/app/routes/articles.server.routes.js b/app/routes/articles.server.routes.js
deleted file mode 100644
index 9a93d05985..0000000000
--- a/app/routes/articles.server.routes.js
+++ /dev/null
@@ -1,22 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var users = require('../../app/controllers/users.server.controller'),
- articles = require('../../app/controllers/articles.server.controller');
-
-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);
-};
diff --git a/app/routes/core.server.routes.js b/app/routes/core.server.routes.js
deleted file mode 100644
index 7138822689..0000000000
--- a/app/routes/core.server.routes.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-module.exports = function(app) {
- // Root routing
- var core = require('../../app/controllers/core.server.controller');
- app.route('/').get(core.index);
-};
diff --git a/app/routes/users.server.routes.js b/app/routes/users.server.routes.js
deleted file mode 100644
index a3005346d8..0000000000
--- a/app/routes/users.server.routes.js
+++ /dev/null
@@ -1,57 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var passport = require('passport');
-
-module.exports = function(app) {
- // User Routes
- var users = require('../../app/controllers/users.server.controller');
-
- // Setting up the users profile api
- app.route('/users/me').get(users.me);
- app.route('/users').put(users.update);
- app.route('/users/accounts').delete(users.removeOAuthProvider);
-
- // Setting up the users password api
- app.route('/users/password').post(users.changePassword);
- app.route('/auth/forgot').post(users.forgot);
- app.route('/auth/reset/:token').get(users.validateResetToken);
- app.route('/auth/reset/:token').post(users.reset);
-
- // Setting up the users authentication 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'));
-
- // Setting the github oauth routes
- app.route('/auth/github').get(passport.authenticate('github'));
- app.route('/auth/github/callback').get(users.oauthCallback('github'));
-
- // Finish by binding the user middleware
- app.param('userId', users.userByID);
-};
diff --git a/bower.json b/bower.json
index 51e865536a..3a959bd1b0 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "meanjs",
- "version": "0.3.2",
+ "version": "0.4.0",
"description": "Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.",
"dependencies": {
"bootstrap": "~3",
@@ -9,8 +9,8 @@
"angular-animate": "~1.2",
"angular-mocks": "~1.2",
"angular-bootstrap": "~0.11.2",
- "angular-bootstrap": "~0.12.0",
"angular-ui-utils": "~0.1.1",
- "angular-ui-router": "~0.2.11"
+ "angular-ui-router": "~0.2.11",
+ "angular-file-upload": "~1.1.5"
}
-}
+}
\ No newline at end of file
diff --git a/config/assets/default.js b/config/assets/default.js
new file mode 100644
index 0000000000..42f1b1062a
--- /dev/null
+++ b/config/assets/default.js
@@ -0,0 +1,47 @@
+'use strict';
+
+module.exports = {
+ client: {
+ 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',
+ 'public/lib/angular-file-upload/angular-file-upload.js'
+ ],
+ tests: ['public/lib/angular-mocks/angular-mocks.js']
+ },
+ css: [
+ 'modules/*/client/css/*.css'
+ ],
+ less: [
+ 'modules/*/client/less/*.less'
+ ],
+ sass: [
+ 'modules/*/client/scss/*.scss'
+ ],
+ js: [
+ 'modules/core/client/app/config.js',
+ 'modules/core/client/app/init.js',
+ 'modules/*/client/*.js',
+ 'modules/*/client/**/*.js'
+ ],
+ views: ['modules/*/client/views/**/*.html']
+ },
+ server: {
+ allJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'modules/*/server/**/*.js'],
+ models: 'modules/*/server/models/**/*.js',
+ routes: ['modules/!(core)/server/routes/**/*.js', 'modules/core/server/routes/**/*.js'],
+ sockets: 'modules/*/server/sockets/**/*.js',
+ config: 'modules/*/server/config/*.js',
+ policies: 'modules/*/server/policies/*.js',
+ views: 'modules/*/server/views/*.html'
+ }
+};
diff --git a/config/assets/development.js b/config/assets/development.js
new file mode 100644
index 0000000000..b521b6577d
--- /dev/null
+++ b/config/assets/development.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ // Development assets
+};
diff --git a/config/assets/production.js b/config/assets/production.js
new file mode 100644
index 0000000000..9a664c455b
--- /dev/null
+++ b/config/assets/production.js
@@ -0,0 +1,23 @@
+'use strict';
+
+module.exports = {
+ client: {
+ 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',
+ 'public/lib/angular-file-upload/angular-file-upload.min.js'
+ ]
+ },
+ css: 'public/dist/application.min.css',
+ js: 'public/dist/application.min.js'
+ }
+};
diff --git a/config/assets/test.js b/config/assets/test.js
new file mode 100644
index 0000000000..29ffa89c9e
--- /dev/null
+++ b/config/assets/test.js
@@ -0,0 +1,9 @@
+'use strict';
+
+module.exports = {
+ tests: {
+ client: ['modules/*/tests/client/**/*.js'],
+ server: ['modules/*/tests/server/**/*.js'],
+ e2e: ['modules/*/tests/e2e/**/*.js']
+ }
+};
diff --git a/config/config.js b/config/config.js
index 3a22a2cdb3..85458853b9 100644
--- a/config/config.js
+++ b/config/config.js
@@ -4,87 +4,161 @@
* Module dependencies.
*/
var _ = require('lodash'),
- glob = require('glob'),
- fs = require('fs');
+ chalk = require('chalk'),
+ glob = require('glob'),
+ fs = require('fs'),
+ path = require('path');
/**
- * Resolve environment configuration by extending each env configuration file,
- * and lastly merge/override that with any local repository configuration that exists
- * in local.js
+ * Get files by glob patterns
*/
-var resolvingConfig = function() {
- var conf = {};
+var getGlobbedPaths = function(globPatterns, excludes) {
+ // URL paths regex
+ var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i');
+
+ // The output array
+ var output = [];
- conf = _.extend(
- require('./env/all'),
- require('./env/' + process.env.NODE_ENV) || {}
- );
+ // 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, getGlobbedPaths(globPattern, excludes));
+ });
+ } else if (_.isString(globPatterns)) {
+ if (urlRegex.test(globPatterns)) {
+ output.push(globPatterns);
+ } else {
+ var files = glob.sync(globPatterns);
+ if (excludes) {
+ files = files.map(function(file) {
+ if (_.isArray(excludes)) {
+ for (var i in excludes) {
+ file = file.replace(excludes[i], '');
+ }
+ } else {
+ file = file.replace(excludes, '');
+ }
+ return file;
+ });
+ }
+ output = _.union(output, files);
+ }
+ }
- return _.merge(conf, (fs.existsSync('./config/env/local.js') && require('./env/local.js')) || {});
+ return output;
};
/**
- * Load app configurations
+ * Validate NODE_ENV existance
*/
-module.exports = resolvingConfig();
+var validateEnvironmentVariable = function() {
+ var environmentFiles = glob.sync('./config/env/' + process.env.NODE_ENV + '.js');
+ console.log();
+ if (!environmentFiles.length) {
+ if (process.env.NODE_ENV) {
+ console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead'));
+ } else {
+ console.error(chalk.red('NODE_ENV is not defined! Using default development environment'));
+ }
+ process.env.NODE_ENV = 'development';
+ } else {
+ console.log(chalk.bold('Application loaded using the "' + process.env.NODE_ENV + '" environment configuration'));
+ }
+ // Reset console color
+ console.log(chalk.white(''));
+};
/**
- * Get files by glob patterns
+ * Initialize global configuration files
*/
-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;
+var initGlobalConfigFolders = function(config, assets) {
+ // Appending files
+ config.folders = {
+ server: {},
+ client: {}
+ };
+
+ // Setting globbed client paths
+ config.folders.client = getGlobbedPaths(path.join(process.cwd(), 'modules/*/client/'), process.cwd().replace(new RegExp(/\\/g),'/'));
};
/**
- * Get the modules JavaScript files
+ * Initialize global configuration files
*/
-module.exports.getJavaScriptAssets = function(includeTests) {
- var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js), 'public/');
+var initGlobalConfigFiles = function(config, assets) {
+ // Appending files
+ config.files = {
+ server: {},
+ client: {}
+ };
+
+ // Setting Globbed model files
+ config.files.server.models = getGlobbedPaths(assets.server.models);
+
+ // Setting Globbed route files
+ config.files.server.routes = getGlobbedPaths(assets.server.routes);
- // To include tests
- if (includeTests) {
- output = _.union(output, this.getGlobbedFiles(this.assets.tests));
- }
+ // Setting Globbed config files
+ config.files.server.configs = getGlobbedPaths(assets.server.config);
- return output;
+ // Setting Globbed socket files
+ config.files.server.sockets = getGlobbedPaths(assets.server.sockets);
+
+ // Setting Globbed policies files
+ config.files.server.policies = getGlobbedPaths(assets.server.policies);
+
+ // Setting Globbed js files
+ config.files.client.js = getGlobbedPaths(assets.client.lib.js, 'public/').concat(getGlobbedPaths(assets.client.js, ['client/', 'public/']));
+
+ // Setting Globbed css files
+ config.files.client.css = getGlobbedPaths(assets.client.lib.css, 'public/').concat(getGlobbedPaths(assets.client.css, ['client/', 'public/']));
+
+ // Setting Globbed test files
+ config.files.client.tests = getGlobbedPaths(assets.client.tests);
};
/**
- * Get the modules CSS files
+ * Initialize global configuration
*/
-module.exports.getCSSAssets = function() {
- var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/');
- return output;
+var initGlobalConfig = function() {
+ // Validate NDOE_ENV existance
+ validateEnvironmentVariable();
+
+ // Get the default assets
+ var defaultAssets = require(path.join(process.cwd(), 'config/assets/default'));
+
+ // Get the current assets
+ var environmentAssets = require(path.join(process.cwd(), 'config/assets/', process.env.NODE_ENV)) || {};
+
+ // Merge assets
+ var assets = _.extend(defaultAssets, environmentAssets);
+
+ // Get the default config
+ var defaultConfig = require(path.join(process.cwd(), 'config/env/default'));
+
+ // Get the current config
+ var environmentConfig = require(path.join(process.cwd(), 'config/env/', process.env.NODE_ENV)) || {};
+
+ // Merge config files
+ var envConf = _.extend(defaultConfig, environmentConfig);
+
+ var config = _.merge(envConf, (fs.existsSync(path.join(process.cwd(), 'config/env/local.js')) && require(path.join(process.cwd(), 'config/env/local.js'))) || {});
+
+ // Initialize global globbed files
+ initGlobalConfigFiles(config, assets);
+
+ // Initialize global globbed folders
+ initGlobalConfigFolders(config, assets);
+
+ // Expose configuration utilities
+ config.utils = {
+ getGlobbedPaths: getGlobbedPaths
+ };
+
+ return config;
};
+
+/**
+ * Set configuration object
+ */
+module.exports = initGlobalConfig();
diff --git a/config/env/all.js b/config/env/all.js
deleted file mode 100644
index 34bc24e7a6..0000000000
--- a/config/env/all.js
+++ /dev/null
@@ -1,72 +0,0 @@
-'use strict';
-
-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'
- },
- port: process.env.PORT || 3000,
- templateEngine: 'swig',
- // The secret should be set to a non-guessable string that
- // is used to compute a session hash
- sessionSecret: 'MEAN',
- // The name of the MongoDB collection to store sessions in
- sessionCollection: 'sessions',
- // The session cookie settings
- sessionCookie: {
- path: '/',
- httpOnly: true,
- // If secure is set to true then it will cause the cookie to be set
- // only when SSL-enabled (HTTPS) is used, and otherwise it won't
- // set a cookie. 'true' is recommended yet it requires the above
- // mentioned pre-requisite.
- secure: false,
- // Only set the maxAge to null if the cookie shouldn't be expired
- // at all. The cookie will expunge when the browser is closed.
- maxAge: null,
- // To set the cookie in a specific domain uncomment the following
- // setting:
- // domain: 'yourdomain.com'
- },
- // The session cookie name
- sessionName: 'connect.sid',
- log: {
- // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
- format: 'combined',
- // Stream defaults to process.stdout
- // Uncomment to enable logging to a log on the file system
- options: {
- stream: 'access.log'
- }
- },
- 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/default.js b/config/env/default.js
new file mode 100644
index 0000000000..8acd131a0b
--- /dev/null
+++ b/config/env/default.js
@@ -0,0 +1,14 @@
+'use strict';
+
+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',
+ googleAnalyticsTrackingID: process.env.GOOGLE_ANALYTICS_TRACKING_ID || 'GOOGLE_ANALYTICS_TRACKING_ID'
+ },
+ port: process.env.PORT || 3000,
+ templateEngine: 'swig',
+ sessionSecret: 'MEAN',
+ sessionCollection: 'sessions'
+};
diff --git a/config/env/development.js b/config/env/development.js
index 35c08b7e8e..9b31b3b04f 100644
--- a/config/env/development.js
+++ b/config/env/development.js
@@ -1,13 +1,7 @@
'use strict';
module.exports = {
- db: {
- uri: 'mongodb://localhost/mean-dev',
- options: {
- user: '',
- pass: ''
- }
- },
+ db: 'mongodb://localhost/mean-dev',
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'dev',
@@ -21,30 +15,30 @@ module.exports = {
title: 'MEAN.JS - Development Environment'
},
facebook: {
- clientID: process.env.FACEBOOK_ID || 'APP_ID',
- clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
- callbackURL: '/auth/facebook/callback'
- },
- twitter: {
- clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
- clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
- callbackURL: '/auth/twitter/callback'
- },
- google: {
- clientID: process.env.GOOGLE_ID || 'APP_ID',
- clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
- callbackURL: '/auth/google/callback'
- },
- linkedin: {
- clientID: process.env.LINKEDIN_ID || 'APP_ID',
- clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
- callbackURL: '/auth/linkedin/callback'
- },
- github: {
- clientID: process.env.GITHUB_ID || 'APP_ID',
- clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
- callbackURL: '/auth/github/callback'
- },
+ clientID: process.env.FACEBOOK_ID || 'APP_ID',
+ clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
+ callbackURL: '/api/auth/facebook/callback'
+ },
+ twitter: {
+ clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
+ clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
+ callbackURL: '/api/auth/twitter/callback'
+ },
+ google: {
+ clientID: process.env.GOOGLE_ID || 'APP_ID',
+ clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
+ callbackURL: '/api/auth/google/callback'
+ },
+ linkedin: {
+ clientID: process.env.LINKEDIN_ID || 'APP_ID',
+ clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
+ callbackURL: '/api/auth/linkedin/callback'
+ },
+ github: {
+ clientID: process.env.GITHUB_ID || 'APP_ID',
+ clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
+ callbackURL: '/api/auth/github/callback'
+ },
mailer: {
from: process.env.MAILER_FROM || 'MAILER_FROM',
options: {
diff --git a/config/env/production.js b/config/env/production.js
index a56a48f0e5..275be8084d 100644
--- a/config/env/production.js
+++ b/config/env/production.js
@@ -1,6 +1,8 @@
'use strict';
module.exports = {
+ secure: true,
+ port: process.env.PORT || 8443,
db: {
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean',
options: {
@@ -38,27 +40,27 @@ module.exports = {
facebook: {
clientID: process.env.FACEBOOK_ID || 'APP_ID',
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
- callbackURL: '/auth/facebook/callback'
+ callbackURL: '/api/auth/facebook/callback'
},
twitter: {
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
- callbackURL: '/auth/twitter/callback'
+ callbackURL: '/api/auth/twitter/callback'
},
google: {
clientID: process.env.GOOGLE_ID || 'APP_ID',
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
- callbackURL: '/auth/google/callback'
+ callbackURL: '/api/auth/google/callback'
},
linkedin: {
clientID: process.env.LINKEDIN_ID || 'APP_ID',
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
- callbackURL: '/auth/linkedin/callback'
+ callbackURL: '/api/auth/linkedin/callback'
},
github: {
clientID: process.env.GITHUB_ID || 'APP_ID',
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
- callbackURL: '/auth/github/callback'
+ callbackURL: '/api/auth/github/callback'
},
mailer: {
from: process.env.MAILER_FROM || 'MAILER_FROM',
diff --git a/config/env/secure.js b/config/env/secure.js
deleted file mode 100644
index 320d0fb5d7..0000000000
--- a/config/env/secure.js
+++ /dev/null
@@ -1,74 +0,0 @@
-'use strict';
-
-module.exports = {
- port: 8443,
- db: {
- uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean',
- options: {
- user: '',
- pass: ''
- }
- },
- log: {
- // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
- format: 'combined',
- // Stream defaults to process.stdout
- // Uncomment to enable logging to a log on the file system
- options: {
- stream: 'access.log'
- }
- },
- 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: process.env.FACEBOOK_ID || 'APP_ID',
- clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
- callbackURL: 'https://localhost:443/auth/facebook/callback'
- },
- twitter: {
- clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
- clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
- callbackURL: 'https://localhost:443/auth/twitter/callback'
- },
- google: {
- clientID: process.env.GOOGLE_ID || 'APP_ID',
- clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
- callbackURL: 'https://localhost:443/auth/google/callback'
- },
- linkedin: {
- clientID: process.env.LINKEDIN_ID || 'APP_ID',
- clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
- callbackURL: 'https://localhost:443/auth/linkedin/callback'
- },
- github: {
- clientID: process.env.GITHUB_ID || 'APP_ID',
- clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
- callbackURL: 'https://localhost:443/auth/github/callback'
- },
- mailer: {
- from: process.env.MAILER_FROM || 'MAILER_FROM',
- options: {
- service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER',
- auth: {
- user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID',
- pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD'
- }
- }
- }
-};
diff --git a/config/env/test.js b/config/env/test.js
index f9e3116f73..3959a5bfc6 100644
--- a/config/env/test.js
+++ b/config/env/test.js
@@ -9,42 +9,33 @@ module.exports = {
}
},
port: 3001,
- log: {
- // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
- format: 'dev',
- // Stream defaults to process.stdout
- // Uncomment to enable logging to a log on the file system
- options: {
- //stream: 'access.log'
- }
- },
app: {
title: 'MEAN.JS - Test Environment'
},
facebook: {
clientID: process.env.FACEBOOK_ID || 'APP_ID',
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
- callbackURL: '/auth/facebook/callback'
+ callbackURL: '/api/auth/facebook/callback'
},
twitter: {
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
- callbackURL: '/auth/twitter/callback'
+ callbackURL: '/api/auth/twitter/callback'
},
google: {
clientID: process.env.GOOGLE_ID || 'APP_ID',
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
- callbackURL: '/auth/google/callback'
+ callbackURL: '/api/auth/google/callback'
},
linkedin: {
clientID: process.env.LINKEDIN_ID || 'APP_ID',
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
- callbackURL: '/auth/linkedin/callback'
+ callbackURL: '/api/auth/linkedin/callback'
},
github: {
clientID: process.env.GITHUB_ID || 'APP_ID',
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
- callbackURL: '/auth/github/callback'
+ callbackURL: '/api/auth/github/callback'
},
mailer: {
from: process.env.MAILER_FROM || 'MAILER_FROM',
diff --git a/config/express.js b/config/express.js
deleted file mode 100755
index 6b5e1b851a..0000000000
--- a/config/express.js
+++ /dev/null
@@ -1,165 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var fs = require('fs'),
- http = require('http'),
- https = require('https'),
- express = require('express'),
- morgan = require('morgan'),
- logger = require('./logger'),
- bodyParser = require('body-parser'),
- session = require('express-session'),
- compression = 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(compression({
- // only compress files for the following content types
- filter: function(req, res) {
- return (/json|text|javascript|css/).test(res.getHeader('Content-Type'));
- },
- // zlib option for compression level
- level: 3
- }));
-
- // 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');
-
- // Enable logger (morgan)
- app.use(morgan(logger.getLogFormat(), logger.getLogOptions()));
-
- // Environment dependent middleware
- if (process.env.NODE_ENV === 'development') {
- // 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({
- extended: true
- }));
- app.use(bodyParser.json());
- app.use(methodOverride());
-
- // Use helmet to secure Express headers
- app.use(helmet.xframe());
- app.use(helmet.xssFilter());
- app.use(helmet.nosniff());
- app.use(helmet.ienoopen());
- app.disable('x-powered-by');
-
- // Setting the app router and static folder
- app.use(express.static(path.resolve('./public')));
-
- // CookieParser should be above session
- app.use(cookieParser());
-
- // Express MongoDB session storage
- app.use(session({
- saveUninitialized: true,
- resave: true,
- secret: config.sessionSecret,
- store: new mongoStore({
- db: db.connection.db,
- collection: config.sessionCollection
- }),
- cookie: config.sessionCookie,
- name: config.sessionName
- }));
-
- // use passport session
- app.use(passport.initialize());
- app.use(passport.session());
-
- // connect flash for flash messages
- app.use(flash());
-
- // 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'
- });
- });
-
- if (process.env.NODE_ENV === 'secure') {
- // Load SSL key and certificate
- var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8');
- var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8');
-
- // Create HTTPS Server
- var httpsServer = https.createServer({
- key: privateKey,
- cert: certificate
- }, app);
-
- // Return HTTPS server instance
- return httpsServer;
- }
-
- // Return Express server instance
- return app;
-};
diff --git a/config/init.js b/config/init.js
deleted file mode 100644
index 3a5b1e5203..0000000000
--- a/config/init.js
+++ /dev/null
@@ -1,31 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-var glob = require('glob'),
- chalk = require('chalk');
-
-/**
- * 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) {
- if (!environmentFiles.length) {
- if (process.env.NODE_ENV) {
- console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead'));
- } else {
- console.error(chalk.red('NODE_ENV is not defined! Using default development environment'));
- }
-
- process.env.NODE_ENV = 'development';
- }
- });
-
-};
diff --git a/config/lib/express.js b/config/lib/express.js
new file mode 100644
index 0000000000..a208695df4
--- /dev/null
+++ b/config/lib/express.js
@@ -0,0 +1,252 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var config = require('../config'),
+ express = require('express'),
+ morgan = require('morgan'),
+ bodyParser = require('body-parser'),
+ session = require('express-session'),
+ MongoStore = require('connect-mongo')(session),
+ multer = require('multer'),
+ favicon = require('serve-favicon'),
+ compress = require('compression'),
+ methodOverride = require('method-override'),
+ cookieParser = require('cookie-parser'),
+ helmet = require('helmet'),
+ passport = require('passport'),
+ flash = require('connect-flash'),
+ consolidate = require('consolidate'),
+ path = require('path');
+
+/**
+ * Initialize local variables
+ */
+module.exports.initLocalVariables = function (app) {
+ // Setting application local variables
+ app.locals.title = config.app.title;
+ app.locals.description = config.app.description;
+ app.locals.secure = config.secure;
+ app.locals.keywords = config.app.keywords;
+ app.locals.googleAnalyticsTrackingID = config.app.googleAnalyticsTrackingID;
+ app.locals.facebookAppId = config.facebook.clientID;
+ app.locals.jsFiles = config.files.client.js;
+ app.locals.cssFiles = config.files.client.css;
+
+ // Passing the request url to environment locals
+ app.use(function (req, res, next) {
+ res.locals.host = req.protocol + '://' + req.hostname;
+ res.locals.url = req.protocol + '://' + req.headers.host + req.originalUrl;
+ next();
+ });
+};
+
+/**
+ * Initialize application middleware
+ */
+module.exports.initMiddleware = function (app) {
+ // Showing stack errors
+ app.set('showStackError', true);
+
+ // Enable jsonp
+ app.enable('jsonp callback');
+
+ // Should be placed before express.static
+ app.use(compress({
+ filter: function (req, res) {
+ return (/json|text|javascript|css|font|svg/).test(res.getHeader('Content-Type'));
+ },
+ level: 9
+ }));
+
+ // Initialize favicon middleware
+ app.use(favicon('./modules/core/client/img/brand/favicon.ico'));
+
+ // 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({
+ extended: true
+ }));
+ app.use(bodyParser.json());
+ app.use(methodOverride());
+
+ // Add the cookie parser and flash middleware
+ app.use(cookieParser());
+ app.use(flash());
+
+ // Add multipart handling middleware
+ app.use(multer({
+ dest: './uploads/',
+ inMemory: true
+ }));
+};
+
+/**
+ * Configure view engine
+ */
+module.exports.initViewEngine = function (app) {
+ // 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', './');
+};
+
+/**
+ * Configure Express session
+ */
+module.exports.initSession = function (app, db) {
+ // Express MongoDB session storage
+ app.use(session({
+ saveUninitialized: true,
+ resave: true,
+ secret: config.sessionSecret,
+ store: new MongoStore({
+ mongooseConnection: db.connection,
+ collection: config.sessionCollection
+ })
+ }));
+};
+
+/**
+ * Invoke modules server configuration
+ */
+module.exports.initModulesConfiguration = function (app, db) {
+ config.files.server.configs.forEach(function (configPath) {
+ require(path.resolve(configPath))(app, db);
+ });
+};
+
+/**
+ * Configure Helmet headers configuration
+ */
+module.exports.initHelmetHeaders = function (app) {
+ // Use helmet to secure Express headers
+ app.use(helmet.xframe());
+ app.use(helmet.xssFilter());
+ app.use(helmet.nosniff());
+ app.use(helmet.ienoopen());
+ app.disable('x-powered-by');
+};
+
+/**
+ * Configure the modules static routes
+ */
+module.exports.initModulesClientRoutes = function (app) {
+ // Setting the app router and static folder
+ app.use('/', express.static(path.resolve('./public')));
+
+ // Globbing static routing
+ config.folders.client.forEach(function (staticPath) {
+ app.use(staticPath.replace('/client', ''), express.static(path.resolve('./' + staticPath)));
+ });
+};
+
+/**
+ * Configure the modules ACL policies
+ */
+module.exports.initModulesServerPolicies = function (app) {
+ // Globbing policy files
+ config.files.server.policies.forEach(function (policyPath) {
+ require(path.resolve(policyPath)).invokeRolesPolicies();
+ });
+};
+
+/**
+ * Configure the modules server routes
+ */
+module.exports.initModulesServerRoutes = function (app) {
+ // Globbing routing files
+ config.files.server.routes.forEach(function (routePath) {
+ require(path.resolve(routePath))(app);
+ });
+};
+
+/**
+ * Configure error handling
+ */
+module.exports.initErrorRoutes = function (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);
+
+ // Redirect to error page
+ res.redirect('/server-error');
+ });
+
+ // Assume 404 since no middleware responded
+ app.use(function (req, res) {
+ // Redirect to not found page
+ res.redirect('/not-found');
+ });
+};
+
+/**
+ * Configure Socket.io
+ */
+module.exports.configureSocketIO = function (app, db) {
+ // Load the Socket.io configuration
+ var server = require('./socket.io')(app, db);
+
+ // Return server object
+ return server;
+};
+
+/**
+ * Initialize the Express application
+ */
+module.exports.init = function (db) {
+ // Initialize express app
+ var app = express();
+
+ // Initialize local variables
+ this.initLocalVariables(app);
+
+ // Initialize Express middleware
+ this.initMiddleware(app);
+
+ // Initialize Express view engine
+ this.initViewEngine(app);
+
+ // Initialize Express session
+ this.initSession(app, db);
+
+ // Initialize Modules configuration
+ this.initModulesConfiguration(app);
+
+ // Initialize Helmet security headers
+ this.initHelmetHeaders(app);
+
+ // Initialize modules static client routes
+ this.initModulesClientRoutes(app);
+
+ // Initialize modules server authorization policies
+ this.initModulesServerPolicies(app);
+
+ // Initialize modules server routes
+ this.initModulesServerRoutes(app);
+
+ // Initialize error routes
+ this.initErrorRoutes(app);
+
+ // Configure Socket.io
+ app = this.configureSocketIO(app, db);
+
+ return app;
+};
diff --git a/config/lib/mongoose.js b/config/lib/mongoose.js
new file mode 100644
index 0000000000..51eb37f76a
--- /dev/null
+++ b/config/lib/mongoose.js
@@ -0,0 +1,43 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var config = require('../config'),
+ chalk = require('chalk'),
+ path = require('path'),
+ mongoose = require('mongoose');
+
+// Load the mongoose models
+module.exports.loadModels = function() {
+ // Globbing model files
+ config.files.server.models.forEach(function(modelPath) {
+ require(path.resolve(modelPath));
+ });
+};
+
+// Initialize Mongoose
+module.exports.connect = function(cb) {
+ var _this = this;
+
+ var db = mongoose.connect(config.db, function (err) {
+ // Log Error
+ if (err) {
+ console.error(chalk.red('Could not connect to MongoDB!'));
+ console.log(err);
+ } else {
+ // Load modules
+ _this.loadModels();
+
+ // Call callback FN
+ if (cb) cb(db);
+ }
+ });
+};
+
+module.exports.disconnect = function(cb) {
+ mongoose.disconnect(function(err) {
+ console.info(chalk.yellow('Disconnected from MongoDB.'));
+ cb(err);
+ });
+};
diff --git a/config/lib/socket.io.js b/config/lib/socket.io.js
new file mode 100644
index 0000000000..18adffd0d0
--- /dev/null
+++ b/config/lib/socket.io.js
@@ -0,0 +1,76 @@
+'use strict';
+
+// Load the module dependencies
+var config = require('../config'),
+ path = require('path'),
+ fs = require('fs'),
+ http = require('http'),
+ https = require('https'),
+ cookieParser = require('cookie-parser'),
+ passport = require('passport'),
+ socketio = require('socket.io'),
+ session = require('express-session'),
+ MongoStore = require('connect-mongo')(session);
+
+// Define the Socket.io configuration method
+module.exports = function(app, db) {
+ var server;
+ if (config.secure === true) {
+ // Load SSL key and certificate
+ var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8');
+ var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8');
+ var options = {
+ key: privateKey,
+ cert: certificate
+ };
+
+ // Create new HTTPS Server
+ server = https.createServer(options, app);
+ } else {
+ // Create a new HTTP server
+ server = http.createServer(app);
+ }
+ // Create a new Socket.io server
+ var io = socketio.listen(server);
+
+ // Create a MongoDB storage object
+ var mongoStore = new MongoStore({
+ mongooseConnection: db.connection,
+ collection: config.sessionCollection
+ });
+
+ // Intercept Socket.io's handshake request
+ io.use(function(socket, next) {
+ // Use the 'cookie-parser' module to parse the request cookies
+ cookieParser(config.sessionSecret)(socket.request, {}, function(err) {
+ // Get the session id from the request cookies
+ var sessionId = socket.request.signedCookies['connect.sid'];
+
+ // Use the mongoStorage instance to get the Express session information
+ mongoStore.get(sessionId, function(err, session) {
+ // Set the Socket.io session information
+ socket.request.session = session;
+
+ // Use Passport to populate the user details
+ passport.initialize()(socket.request, {}, function() {
+ passport.session()(socket.request, {}, function() {
+ if (socket.request.user) {
+ next(null, true);
+ } else {
+ next(new Error('User is not authenticated'), false);
+ }
+ });
+ });
+ });
+ });
+ });
+
+ // Add an event listener to the 'connection' event
+ io.on('connection', function(socket) {
+ config.files.server.sockets.forEach(function(socketConfiguration) {
+ require(path.resolve(socketConfiguration))(io, socket);
+ });
+ });
+
+ return server;
+};
diff --git a/config/logger.js b/config/logger.js
deleted file mode 100644
index e6d1f1dcbc..0000000000
--- a/config/logger.js
+++ /dev/null
@@ -1,36 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies.
- */
-
-var morgan = require('morgan');
-var config = require('./config');
-var fs = require('fs');
-
-/**
- * Module init function.
- */
-module.exports = {
-
- getLogFormat: function() {
- return config.log.format;
- },
-
- getLogOptions: function() {
- var options = {};
-
- try {
- if ('stream' in config.log.options) {
- options = {
- stream: fs.createWriteStream(process.cwd() + '/' + config.log.options.stream, {flags: 'a'})
- };
- }
- } catch (e) {
- options = {};
- }
-
- return options;
- }
-
-};
diff --git a/gruntfile.js b/gruntfile.js
index c028ff7751..e8abfe71ab 100644
--- a/gruntfile.js
+++ b/gruntfile.js
@@ -1,65 +1,102 @@
'use strict';
-var fs = require('fs');
-
-module.exports = function(grunt) {
- // Unified Watch Object
- var watchFiles = {
- serverViews: ['app/views/**/*.*'],
- serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', '!app/tests/'],
- clientViews: ['public/modules/**/views/**/*.html'],
- clientJS: ['public/js/*.js', 'public/modules/**/*.js'],
- clientCSS: ['public/modules/**/*.css'],
- mochaTests: ['app/tests/**/*.js']
- };
+/**
+ * Module dependencies.
+ */
+var _ = require('lodash'),
+ defaultAssets = require('./config/assets/default'),
+ testAssets = require('./config/assets/test'),
+ fs = require('fs');
+module.exports = function (grunt) {
// Project Configuration
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
+ env: {
+ test: {
+ NODE_ENV: 'test'
+ },
+ dev: {
+ NODE_ENV: 'development'
+ },
+ prod: {
+ NODE_ENV: 'production'
+ }
+ },
watch: {
serverViews: {
- files: watchFiles.serverViews,
+ files: defaultAssets.server.views,
options: {
livereload: true
}
},
serverJS: {
- files: watchFiles.serverJS,
+ files: defaultAssets.server.allJS,
tasks: ['jshint'],
options: {
livereload: true
}
},
clientViews: {
- files: watchFiles.clientViews,
+ files: defaultAssets.client.views,
options: {
livereload: true
}
},
clientJS: {
- files: watchFiles.clientJS,
+ files: defaultAssets.client.js,
tasks: ['jshint'],
options: {
livereload: true
}
},
clientCSS: {
- files: watchFiles.clientCSS,
+ files: defaultAssets.client.css,
tasks: ['csslint'],
options: {
livereload: true
}
},
- mochaTests: {
- files: watchFiles.mochaTests,
- tasks: ['test:server'],
+ clientSCSS: {
+ files: defaultAssets.client.sass,
+ tasks: ['sass', 'csslint'],
+ options: {
+ livereload: true
+ }
+ },
+ clientLESS: {
+ files: defaultAssets.client.less,
+ tasks: ['less', 'csslint'],
+ options: {
+ livereload: true
+ }
+ }
+ },
+ nodemon: {
+ dev: {
+ script: 'server.js',
+ options: {
+ nodeArgs: ['--debug'],
+ ext: 'js,html',
+ watch: _.union(defaultAssets.server.views, defaultAssets.server.allJS, defaultAssets.server.config)
+ }
+ }
+ },
+ concurrent: {
+ default: ['nodemon', 'watch'],
+ debug: ['nodemon', 'watch', 'node-inspector'],
+ options: {
+ logConcurrentOutput: true
}
},
jshint: {
all: {
- src: watchFiles.clientJS.concat(watchFiles.serverJS),
+ src: _.union(defaultAssets.server.allJS, defaultAssets.client.js, testAssets.tests.server, testAssets.tests.client, testAssets.tests.e2e),
options: {
- jshintrc: true
+ jshintrc: true,
+ node: true,
+ mocha: true,
+ jasmine: true
}
}
},
@@ -68,7 +105,14 @@ module.exports = function(grunt) {
csslintrc: '.csslintrc'
},
all: {
- src: watchFiles.clientCSS
+ src: defaultAssets.client.css
+ }
+ },
+ ngAnnotate: {
+ production: {
+ files: {
+ 'public/dist/application.js': defaultAssets.client.js
+ }
}
},
uglify: {
@@ -84,18 +128,32 @@ module.exports = function(grunt) {
cssmin: {
combine: {
files: {
- 'public/dist/application.min.css': '<%= applicationCSSFiles %>'
+ 'public/dist/application.min.css': defaultAssets.client.css
}
}
},
- nodemon: {
- dev: {
- script: 'server.js',
- options: {
- nodeArgs: ['--debug'],
- ext: 'js,html',
- watch: watchFiles.serverViews.concat(watchFiles.serverJS)
- }
+ sass: {
+ dist: {
+ files: [{
+ expand: true,
+ src: defaultAssets.client.sass,
+ ext: '.css',
+ rename: function(base, src) {
+ return src.replace('/scss/', '/css/');
+ }
+ }]
+ }
+ },
+ less: {
+ dist: {
+ files: [{
+ expand: true,
+ src: defaultAssets.client.less,
+ ext: '.css',
+ rename: function(base, src) {
+ return src.replace('/less/', '/css/');
+ }
+ }]
}
},
'node-inspector': {
@@ -111,34 +169,10 @@ module.exports = function(grunt) {
}
}
},
- ngAnnotate: {
- production: {
- files: {
- 'public/dist/application.js': '<%= applicationJavaScriptFiles %>'
- }
- }
- },
- concurrent: {
- default: ['nodemon', 'watch'],
- debug: ['nodemon', 'watch', 'node-inspector'],
- options: {
- logConcurrentOutput: true,
- limit: 10
- }
- },
- env: {
- test: {
- NODE_ENV: 'test'
- },
- secure: {
- NODE_ENV: 'secure'
- }
- },
mochaTest: {
- src: watchFiles.mochaTests,
+ src: testAssets.tests.server,
options: {
- reporter: 'spec',
- require: 'server.js'
+ reporter: 'spec'
}
},
karma: {
@@ -146,6 +180,18 @@ module.exports = function(grunt) {
configFile: 'karma.conf.js'
}
},
+ protractor: {
+ options: {
+ configFile: 'protractor.conf.js',
+ keepAlive: true,
+ noColor: false
+ },
+ e2e: {
+ options: {
+ args: {} // Target-specific arguments
+ }
+ }
+ },
copy: {
localConfig: {
src: 'config/env/local.example.js',
@@ -153,42 +199,45 @@ module.exports = function(grunt) {
filter: function() {
return !fs.existsSync('config/env/local.js');
}
- }
+ }
}
});
- // Load NPM tasks
+ // 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');
+ // Connect to the MongoDB instance and load the models
+ grunt.task.registerTask('mongoose', 'Task that connects to the MongoDB instance and loads the application models.', function() {
+ // Get the callback
+ var done = this.async();
+
+ // Use mongoose configuration
+ var mongoose = require('./config/lib/mongoose.js');
- grunt.config.set('applicationJavaScriptFiles', config.assets.js);
- grunt.config.set('applicationCSSFiles', config.assets.css);
+ // Connect to database
+ mongoose.connect(function(db) {
+ done();
+ });
});
- // Default task(s).
- grunt.registerTask('default', ['lint', 'copy:localConfig', 'concurrent:default']);
+ // Lint CSS and JavaScript files.
+ grunt.registerTask('lint', ['sass', 'less', 'jshint', 'csslint']);
- // Debug task.
- grunt.registerTask('debug', ['lint', 'copy:localConfig', 'concurrent:debug']);
+ // Lint project files and minify them into two production files.
+ grunt.registerTask('build', ['env:dev', 'lint', 'ngAnnotate', 'uglify', 'cssmin']);
- // Secure task(s).
- grunt.registerTask('secure', ['env:secure', 'lint', 'copy:localConfig', 'concurrent:default']);
+ // Run the project tests
+ grunt.registerTask('test', ['env:test', 'copy:localConfig', 'mongoose', 'mochaTest', 'karma:unit']);
- // Lint task(s).
- grunt.registerTask('lint', ['jshint', 'csslint']);
+ // Run the project in development mode
+ grunt.registerTask('default', ['env:dev', 'lint', 'copy:localConfig', 'concurrent:default']);
- // Build task(s).
- grunt.registerTask('build', ['lint', 'loadConfig', 'ngAnnotate', 'uglify', 'cssmin']);
+ // Run the project in debug mode
+ grunt.registerTask('debug', ['env:dev', 'lint', 'copy:localConfig', 'concurrent:debug']);
- // Test task.
- grunt.registerTask('test', ['copy:localConfig', 'test:server', 'test:client']);
- grunt.registerTask('test:server', ['env:test', 'mochaTest']);
- grunt.registerTask('test:client', ['env:test', 'karma:unit']);
+ // Run the project in production mode
+ grunt.registerTask('prod', ['build', 'env:prod', 'copy:localConfig', 'concurrent:default']);
};
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000000..c9b0fe6c12
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,193 @@
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+var _ = require('lodash'),
+ defaultAssets = require('./config/assets/default'),
+ testAssets = require('./config/assets/test'),
+ gulp = require('gulp'),
+ gulpLoadPlugins = require('gulp-load-plugins'),
+ runSequence = require('run-sequence'),
+ plugins = gulpLoadPlugins();
+
+// Set NODE_ENV to 'test'
+gulp.task('env:test', function () {
+ process.env.NODE_ENV = 'test';
+});
+
+// Set NODE_ENV to 'development'
+gulp.task('env:dev', function () {
+ process.env.NODE_ENV = 'development';
+});
+
+// Set NODE_ENV to 'production'
+gulp.task('env:prod', function () {
+ process.env.NODE_ENV = 'production';
+});
+
+// Nodemon task
+gulp.task('nodemon', function () {
+ return plugins.nodemon({
+ script: 'server.js',
+ nodeArgs: ['--debug'],
+ ext: 'js,html',
+ watch: _.union(defaultAssets.server.views, defaultAssets.server.allJS, defaultAssets.server.config)
+ });
+});
+
+// Watch Files For Changes
+gulp.task('watch', function() {
+ // Start livereload
+ plugins.livereload.listen();
+
+ // Add watch rules
+ gulp.watch(defaultAssets.server.views).on('change', plugins.livereload.changed);
+ gulp.watch(defaultAssets.server.allJS, ['jshint']).on('change', plugins.livereload.changed);
+ gulp.watch(defaultAssets.client.views).on('change', plugins.livereload.changed);
+ gulp.watch(defaultAssets.client.js, ['jshint']).on('change', plugins.livereload.changed);
+ gulp.watch(defaultAssets.client.css, ['csslint']).on('change', plugins.livereload.changed);
+ gulp.watch(defaultAssets.client.sass, ['sass', 'csslint']).on('change', plugins.livereload.changed);
+ gulp.watch(defaultAssets.client.less, ['less', 'csslint']).on('change', plugins.livereload.changed);
+});
+
+// CSS linting task
+gulp.task('csslint', function (done) {
+ return gulp.src(defaultAssets.client.css)
+ .pipe(plugins.csslint('.csslintrc'))
+ .pipe(plugins.csslint.reporter())
+ .pipe(plugins.csslint.reporter(function (file) {
+ if (!file.csslint.errorCount) {
+ done();
+ }
+ }));
+});
+
+// JS linting task
+gulp.task('jshint', function () {
+ return gulp.src(_.union(defaultAssets.server.allJS, defaultAssets.client.js, testAssets.tests.server, testAssets.tests.client, testAssets.tests.e2e))
+ .pipe(plugins.jshint())
+ .pipe(plugins.jshint.reporter('default'))
+ .pipe(plugins.jshint.reporter('fail'));
+});
+
+
+// JS minifying task
+gulp.task('uglify', function () {
+ return gulp.src(defaultAssets.client.js)
+ .pipe(plugins.ngAnnotate())
+ .pipe(plugins.uglify({
+ mangle: false
+ }))
+ .pipe(plugins.concat('application.min.js'))
+ .pipe(gulp.dest('public/dist'));
+});
+
+// CSS minifying task
+gulp.task('cssmin', function () {
+ return gulp.src(defaultAssets.client.css)
+ .pipe(plugins.cssmin())
+ .pipe(plugins.concat('application.min.css'))
+ .pipe(gulp.dest('public/dist'));
+});
+
+// Sass task
+gulp.task('sass', function () {
+ return gulp.src(defaultAssets.client.sass)
+ .pipe(plugins.sass())
+ .pipe(plugins.rename(function (path) {
+ path.dirname = path.dirname.replace('/scss', '/css');
+ }))
+ .pipe(gulp.dest('./modules/'));
+});
+
+// Less task
+gulp.task('less', function () {
+ return gulp.src(defaultAssets.client.less)
+ .pipe(plugins.less())
+ .pipe(plugins.rename(function (path) {
+ path.dirname = path.dirname.replace('/less', '/css');
+ }))
+ .pipe(gulp.dest('./modules/'));
+});
+
+// Mocha tests task
+gulp.task('mocha', function (done) {
+ // Open mongoose connections
+ var mongoose = require('./config/lib/mongoose.js');
+ var error;
+
+ // Connect mongoose
+ mongoose.connect(function() {
+ // Run the tests
+ gulp.src(testAssets.tests.server)
+ .pipe(plugins.mocha({
+ reporter: 'spec'
+ }))
+ .on('error', function (err) {
+ // If an error occurs, save it
+ error = err;
+ })
+ .on('end', function() {
+ // When the tests are done, disconnect mongoose and pass the error state back to gulp
+ mongoose.disconnect(function() {
+ done(error);
+ });
+ });
+ });
+
+});
+
+// Karma test runner task
+gulp.task('karma', function (done) {
+ return gulp.src([])
+ .pipe(plugins.karma({
+ configFile: 'karma.conf.js',
+ action: 'run',
+ singleRun: true
+ }));
+});
+
+// Selenium standalone WebDriver update task
+gulp.task('webdriver-update', plugins.protractor.webdriver_update);
+
+// Protractor test runner task
+gulp.task('protractor', function () {
+ gulp.src([])
+ .pipe(plugins.protractor.protractor({
+ configFile: 'protractor.conf.js'
+ }))
+ .on('error', function (e) {
+ throw e;
+ });
+});
+
+// Lint CSS and JavaScript files.
+gulp.task('lint', function(done) {
+ runSequence('less', 'sass', ['csslint', 'jshint'], done);
+});
+
+// Lint project files and minify them into two production files.
+gulp.task('build', function(done) {
+ runSequence('env:dev' ,'lint', ['uglify', 'cssmin'], done);
+});
+
+// Run the project tests
+gulp.task('test', function(done) {
+ runSequence('env:test', ['karma', 'mocha'], done);
+});
+
+// Run the project in development mode
+gulp.task('default', function(done) {
+ runSequence('env:dev', 'lint', ['nodemon', 'watch'], done);
+});
+
+// Run the project in debug mode
+gulp.task('debug', function(done) {
+ runSequence('env:dev', 'lint', ['nodemon', 'watch'], done);
+});
+
+// Run the project in production mode
+gulp.task('prod', function(done) {
+ runSequence('build', 'lint', ['nodemon', 'watch'], done);
+});
diff --git a/karma.conf.js b/karma.conf.js
index 0f5ab311fd..2e926ad5f3 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -3,16 +3,18 @@
/**
* Module dependencies.
*/
-var applicationConfiguration = require('./config/config');
+var _ = require('lodash'),
+ defaultAssets = require('./config/assets/default'),
+ testAssets = require('./config/assets/test');
// Karma configuration
-module.exports = function(config) {
- config.set({
+module.exports = function(karmaConfig) {
+ karmaConfig.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),
+ files: _.union(defaultAssets.client.lib.js, defaultAssets.client.lib.tests, defaultAssets.client.js, testAssets.tests.client),
// Test results reporter to use
// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
@@ -25,8 +27,8 @@ module.exports = function(config) {
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,
+ // Possible values: karmaConfig.LOG_DISABLE || karmaConfig.LOG_ERROR || karmaConfig.LOG_WARN || karmaConfig.LOG_INFO || karmaConfig.LOG_DEBUG
+ logLevel: karmaConfig.LOG_INFO,
// Enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
diff --git a/public/modules/articles/articles.client.module.js b/modules/articles/client/articles.client.module.js
old mode 100755
new mode 100644
similarity index 51%
rename from public/modules/articles/articles.client.module.js
rename to modules/articles/client/articles.client.module.js
index 3f4c63fdbb..3c94d0cb57
--- a/public/modules/articles/articles.client.module.js
+++ b/modules/articles/client/articles.client.module.js
@@ -1,4 +1,4 @@
'use strict';
-// Use Application configuration module to register a new module
+// Use Applicaion configuration module to register a new module
ApplicationConfiguration.registerModule('articles');
diff --git a/modules/articles/client/config/articles.client.config.js b/modules/articles/client/config/articles.client.config.js
new file mode 100644
index 0000000000..d43e0893f4
--- /dev/null
+++ b/modules/articles/client/config/articles.client.config.js
@@ -0,0 +1,25 @@
+'use strict';
+
+// Configuring the Articles module
+angular.module('articles').run(['Menus',
+ function(Menus) {
+ // Add the articles dropdown item
+ Menus.addMenuItem('topbar', {
+ title: 'Articles',
+ state: 'articles',
+ type: 'dropdown'
+ });
+
+ // Add the dropdown list item
+ Menus.addSubMenuItem('topbar', 'articles', {
+ title: 'List Articles',
+ state: 'articles.list'
+ });
+
+ // Add the dropdown create item
+ Menus.addSubMenuItem('topbar', 'articles', {
+ title: 'Create Articles',
+ state: 'articles.create'
+ });
+ }
+]);
diff --git a/public/modules/articles/config/articles.client.routes.js b/modules/articles/client/config/articles.client.routes.js
old mode 100755
new mode 100644
similarity index 64%
rename from public/modules/articles/config/articles.client.routes.js
rename to modules/articles/client/config/articles.client.routes.js
index 1531a9a57c..c6890be3ef
--- a/public/modules/articles/config/articles.client.routes.js
+++ b/modules/articles/client/config/articles.client.routes.js
@@ -5,21 +5,26 @@ angular.module('articles').config(['$stateProvider',
function($stateProvider) {
// Articles state routing
$stateProvider.
- state('listArticles', {
+ state('articles', {
+ abstract: true,
url: '/articles',
+ template: '