0.4.0 branch merged into master
@@ -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
|
||||
3
.gitignore
vendored
@@ -22,6 +22,9 @@ app/tests/coverage/
|
||||
config/sslcerts/*.pem
|
||||
access.log
|
||||
public/dist/
|
||||
uploads
|
||||
modules/users/client/img/profile/uploads
|
||||
*.pem
|
||||
|
||||
# Sublime editor
|
||||
# ==============
|
||||
|
||||
20
.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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
/app/tests
|
||||
/app/tests
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
exports.index = function(req, res) {
|
||||
res.render('index', {
|
||||
user: req.user || null,
|
||||
request: req
|
||||
});
|
||||
};
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
47
config/assets/default.js
Normal file
@@ -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'
|
||||
}
|
||||
};
|
||||
5
config/assets/development.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
// Development assets
|
||||
};
|
||||
23
config/assets/production.js
Normal file
@@ -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'
|
||||
}
|
||||
};
|
||||
9
config/assets/test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
tests: {
|
||||
client: ['modules/*/tests/client/**/*.js'],
|
||||
server: ['modules/*/tests/server/**/*.js'],
|
||||
e2e: ['modules/*/tests/e2e/**/*.js']
|
||||
}
|
||||
};
|
||||
210
config/config.js
@@ -4,87 +4,161 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
glob = require('glob'),
|
||||
fs = require('fs');
|
||||
|
||||
/**
|
||||
* Resolve environment configuration by extending each env configuration file,
|
||||
* and lastly merge/override that with any local repository configuration that exists
|
||||
* in local.js
|
||||
*/
|
||||
var resolvingConfig = function() {
|
||||
var conf = {};
|
||||
|
||||
conf = _.extend(
|
||||
require('./env/all'),
|
||||
require('./env/' + process.env.NODE_ENV) || {}
|
||||
);
|
||||
|
||||
return _.merge(conf, (fs.existsSync('./config/env/local.js') && require('./env/local.js')) || {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load app configurations
|
||||
*/
|
||||
module.exports = resolvingConfig();
|
||||
chalk = require('chalk'),
|
||||
glob = require('glob'),
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
/**
|
||||
* Get files by glob patterns
|
||||
*/
|
||||
module.exports.getGlobbedFiles = function(globPatterns, removeRoot) {
|
||||
// For context switching
|
||||
var _this = this;
|
||||
var getGlobbedPaths = function(globPatterns, excludes) {
|
||||
// URL paths regex
|
||||
var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i');
|
||||
|
||||
// URL paths regex
|
||||
var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i');
|
||||
// The output array
|
||||
var output = [];
|
||||
|
||||
// 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, 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modules JavaScript files
|
||||
* Validate NODE_ENV existance
|
||||
*/
|
||||
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;
|
||||
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 the modules CSS files
|
||||
* Initialize global configuration files
|
||||
*/
|
||||
module.exports.getCSSAssets = function() {
|
||||
var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/');
|
||||
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),'/'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize global configuration files
|
||||
*/
|
||||
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);
|
||||
|
||||
// Setting Globbed config files
|
||||
config.files.server.configs = getGlobbedPaths(assets.server.config);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize global configuration
|
||||
*/
|
||||
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();
|
||||
|
||||
72
config/env/all.js
vendored
@@ -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'
|
||||
]
|
||||
}
|
||||
};
|
||||
14
config/env/default.js
vendored
Normal file
@@ -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'
|
||||
};
|
||||
56
config/env/development.js
vendored
@@ -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: {
|
||||
|
||||
12
config/env/production.js
vendored
@@ -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',
|
||||
|
||||
74
config/env/secure.js
vendored
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
19
config/env/test.js
vendored
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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';
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
252
config/lib/express.js
Normal file
@@ -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;
|
||||
};
|
||||
43
config/lib/mongoose.js
Normal file
@@ -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);
|
||||
});
|
||||
};
|
||||
76
config/lib/socket.io.js
Normal file
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
209
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();
|
||||
|
||||
grunt.config.set('applicationJavaScriptFiles', config.assets.js);
|
||||
grunt.config.set('applicationCSSFiles', config.assets.css);
|
||||
// Use mongoose configuration
|
||||
var mongoose = require('./config/lib/mongoose.js');
|
||||
|
||||
// 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']);
|
||||
};
|
||||
|
||||
193
gulpfile.js
Normal file
@@ -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);
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
2
public/modules/articles/articles.client.module.js → modules/articles/client/articles.client.module.js
Executable file → Normal file
@@ -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');
|
||||
25
modules/articles/client/config/articles.client.config.js
Normal file
@@ -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'
|
||||
});
|
||||
}
|
||||
]);
|
||||
21
public/modules/articles/config/articles.client.routes.js → modules/articles/client/config/articles.client.routes.js
Executable file → Normal file
@@ -5,21 +5,26 @@ angular.module('articles').config(['$stateProvider',
|
||||
function($stateProvider) {
|
||||
// Articles state routing
|
||||
$stateProvider.
|
||||
state('listArticles', {
|
||||
state('articles', {
|
||||
abstract: true,
|
||||
url: '/articles',
|
||||
template: '<ui-view/>'
|
||||
}).
|
||||
state('articles.list', {
|
||||
url: '',
|
||||
templateUrl: 'modules/articles/views/list-articles.client.view.html'
|
||||
}).
|
||||
state('createArticle', {
|
||||
url: '/articles/create',
|
||||
state('articles.create', {
|
||||
url: '/create',
|
||||
templateUrl: 'modules/articles/views/create-article.client.view.html'
|
||||
}).
|
||||
state('viewArticle', {
|
||||
url: '/articles/:articleId',
|
||||
state('articles.view', {
|
||||
url: '/:articleId',
|
||||
templateUrl: 'modules/articles/views/view-article.client.view.html'
|
||||
}).
|
||||
state('editArticle', {
|
||||
url: '/articles/:articleId/edit',
|
||||
state('articles.edit', {
|
||||
url: '/:articleId/edit',
|
||||
templateUrl: 'modules/articles/views/edit-article.client.view.html'
|
||||
});
|
||||
}
|
||||
]);
|
||||
]);
|
||||
@@ -65,4 +65,4 @@ angular.module('articles').controller('ArticlesController', ['$scope', '$statePa
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
]);
|
||||
@@ -3,7 +3,7 @@
|
||||
//Articles service used for communicating with the articles REST endpoints
|
||||
angular.module('articles').factory('Articles', ['$resource',
|
||||
function($resource) {
|
||||
return $resource('articles/:articleId', {
|
||||
return $resource('api/articles/:articleId', {
|
||||
articleId: '@_id'
|
||||
}, {
|
||||
update: {
|
||||
@@ -11,4 +11,4 @@ angular.module('articles').factory('Articles', ['$resource',
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
||||
]);
|
||||
@@ -5,10 +5,10 @@
|
||||
<div class="col-md-12">
|
||||
<form name="articleForm" class="form-horizontal" data-ng-submit="create()" novalidate>
|
||||
<fieldset>
|
||||
<div class="form-group" ng-class="{ 'has-error': articleForm.title.$dirty && articleForm.title.$invalid }">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="title">Title</label>
|
||||
<div class="controls">
|
||||
<input name="title" type="text" data-ng-model="title" id="title" class="form-control" placeholder="Title" required>
|
||||
<input name="title" type="text" data-ng-model="title" id="title" class="form-control" placeholder="Title">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -26,4 +26,4 @@
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -3,24 +3,18 @@
|
||||
<h1>Edit Article</h1>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<form name="articleForm" class="form-horizontal" data-ng-submit="update(articleForm.$valid)" novalidate>
|
||||
<form name="articleForm" class="form-horizontal" data-ng-submit="update()" novalidate>
|
||||
<fieldset>
|
||||
<div class="form-group" ng-class="{ 'has-error' : submitted && articleForm.title.$invalid}">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="title">Title</label>
|
||||
<div class="controls">
|
||||
<input name="title" type="text" data-ng-model="article.title" id="title" class="form-control" placeholder="Title" required>
|
||||
</div>
|
||||
<div ng-show="submitted && articleForm.title.$invalid" class="help-block">
|
||||
<p ng-show="articleForm.title.$error.required" class="text-danger">Title is required</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error' : submitted && articleForm.content.$invalid}">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="content">Content</label>
|
||||
<div class="controls">
|
||||
<textarea name="content" data-ng-model="article.content" id="content" class="form-control" cols="30" rows="10" placeholder="Content" required></textarea>
|
||||
</div>
|
||||
<div ng-show="submitted && articleForm.content.$invalid" class="help-block">
|
||||
<p ng-show="articleForm.content.$error.required" class="text-danger">Content is required</p>
|
||||
<textarea name="content" data-ng-model="article.content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -32,4 +26,4 @@
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -3,7 +3,7 @@
|
||||
<h1>Articles</h1>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<a data-ng-repeat="article in articles" data-ng-href="#!/articles/{{article._id}}" class="list-group-item">
|
||||
<a data-ng-repeat="article in articles" data-ui-sref="articles.view({articleId: article._id})" class="list-group-item">
|
||||
<small class="list-group-item-text">
|
||||
Posted on
|
||||
<span data-ng-bind="article.created | date:'mediumDate'"></span>
|
||||
@@ -15,6 +15,6 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="alert alert-warning text-center" data-ng-if="articles.$resolved && !articles.length">
|
||||
No articles yet, why don't you <a href="/#!/articles/create">create one</a>?
|
||||
No articles yet, why don't you <a data-ui-sref="articles.create">create one</a>?
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -3,7 +3,7 @@
|
||||
<h1 data-ng-bind="article.title"></h1>
|
||||
</div>
|
||||
<div class="pull-right" data-ng-show="authentication.user._id == article.user._id">
|
||||
<a class="btn btn-primary" href="/#!/articles/{{article._id}}/edit">
|
||||
<a class="btn btn-primary" data-ui-sref="articles.edit({articleId: article._id})">
|
||||
<i class="glyphicon glyphicon-edit"></i>
|
||||
</a>
|
||||
<a class="btn btn-primary" data-ng-click="remove();">
|
||||
@@ -19,4 +19,4 @@
|
||||
</em>
|
||||
</small>
|
||||
<p class="lead" data-ng-bind="article.content"></p>
|
||||
</section>
|
||||
</section>
|
||||
@@ -3,10 +3,10 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
errorHandler = require('./errors.server.controller'),
|
||||
var path = require('path'),
|
||||
mongoose = require('mongoose'),
|
||||
Article = mongoose.model('Article'),
|
||||
_ = require('lodash');
|
||||
errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller'));
|
||||
|
||||
/**
|
||||
* Create a article
|
||||
@@ -39,7 +39,8 @@ exports.read = function(req, res) {
|
||||
exports.update = function(req, res) {
|
||||
var article = req.article;
|
||||
|
||||
article = _.extend(article, req.body);
|
||||
article.title = req.body.title;
|
||||
article.content = req.body.content;
|
||||
|
||||
article.save(function(err) {
|
||||
if (err) {
|
||||
@@ -91,7 +92,7 @@ exports.articleByID = function(req, res, next, id) {
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).send({
|
||||
message: 'Article is invalid'
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,22 +100,10 @@ exports.articleByID = function(req, res, next, id) {
|
||||
if (err) return next(err);
|
||||
if (!article) {
|
||||
return res.status(404).send({
|
||||
message: 'Article not found'
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
req.article = article;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Article authorization middleware
|
||||
*/
|
||||
exports.hasAuthorization = function(req, res, next) {
|
||||
if (req.article.user.id !== req.user.id) {
|
||||
return res.status(403).send({
|
||||
message: 'User is not authorized'
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
72
modules/articles/server/policies/articles.server.policy.js
Normal file
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var acl = require('acl');
|
||||
|
||||
// Using the memory backend
|
||||
acl = new acl(new acl.memoryBackend());
|
||||
|
||||
/**
|
||||
* Invoke Articles Permissions
|
||||
*/
|
||||
exports.invokeRolesPolicies = function() {
|
||||
acl.allow([{
|
||||
roles: ['admin'],
|
||||
allows: [{
|
||||
resources: '/api/articles',
|
||||
permissions: '*'
|
||||
}, {
|
||||
resources: '/api/articles/:articleId',
|
||||
permissions: '*'
|
||||
}]
|
||||
}, {
|
||||
roles: ['user'],
|
||||
allows: [{
|
||||
resources: '/api/articles',
|
||||
permissions: ['get', 'post']
|
||||
}, {
|
||||
resources: '/api/articles/:articleId',
|
||||
permissions: ['get']
|
||||
}]
|
||||
}, {
|
||||
roles: ['guest'],
|
||||
allows: [{
|
||||
resources: '/api/articles',
|
||||
permissions: ['get']
|
||||
}, {
|
||||
resources: '/api/articles/:articleId',
|
||||
permissions: ['get']
|
||||
}]
|
||||
}]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check If Articles Policy Allows
|
||||
*/
|
||||
exports.isAllowed = function(req, res, next) {
|
||||
var roles = (req.user) ? req.user.roles : ['guest'];
|
||||
|
||||
// If an article is being processed and the current user created it then allow any manipulation
|
||||
if (req.article && req.user && req.article.user.id === req.user.id) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Check for user roles
|
||||
acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase(), function(err, isAllowed) {
|
||||
if (err) {
|
||||
// An authorization error occurred.
|
||||
return res.status(500).send('Unexpected authorization error');
|
||||
} else {
|
||||
if (isAllowed) {
|
||||
// Access granted! Invoke next middleware
|
||||
return next();
|
||||
} else {
|
||||
return res.status(403).json({
|
||||
message: 'User is not authorized'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
23
modules/articles/server/routes/articles.server.routes.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var articlesPolicy = require('../policies/articles.server.policy'),
|
||||
articles = require('../controllers/articles.server.controller');
|
||||
|
||||
module.exports = function(app) {
|
||||
// Articles collection routes
|
||||
app.route('/api/articles').all(articlesPolicy.isAllowed)
|
||||
.get(articles.list)
|
||||
.post(articles.create);
|
||||
|
||||
// Single article routes
|
||||
app.route('/api/articles/:articleId').all(articlesPolicy.isAllowed)
|
||||
.get(articles.read)
|
||||
.put(articles.update)
|
||||
.delete(articles.delete);
|
||||
|
||||
// Finish by binding the article middleware
|
||||
app.param('articleId', articles.articleByID);
|
||||
};
|
||||
@@ -61,7 +61,7 @@
|
||||
var sampleArticles = [sampleArticle];
|
||||
|
||||
// Set GET response
|
||||
$httpBackend.expectGET('articles').respond(sampleArticles);
|
||||
$httpBackend.expectGET('api/articles').respond(sampleArticles);
|
||||
|
||||
// Run controller functionality
|
||||
scope.find();
|
||||
@@ -82,7 +82,7 @@
|
||||
$stateParams.articleId = '525a8422f6d0f87f0e407a33';
|
||||
|
||||
// Set GET response
|
||||
$httpBackend.expectGET(/articles\/([0-9a-fA-F]{24})$/).respond(sampleArticle);
|
||||
$httpBackend.expectGET(/api\/articles\/([0-9a-fA-F]{24})$/).respond(sampleArticle);
|
||||
|
||||
// Run controller functionality
|
||||
scope.findOne();
|
||||
@@ -111,7 +111,7 @@
|
||||
scope.content = 'MEAN rocks!';
|
||||
|
||||
// Set POST response
|
||||
$httpBackend.expectPOST('articles', sampleArticlePostData).respond(sampleArticleResponse);
|
||||
$httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(sampleArticleResponse);
|
||||
|
||||
// Run controller functionality
|
||||
scope.create();
|
||||
@@ -137,7 +137,7 @@
|
||||
scope.article = sampleArticlePutData;
|
||||
|
||||
// Set PUT response
|
||||
$httpBackend.expectPUT(/articles\/([0-9a-fA-F]{24})$/).respond();
|
||||
$httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond();
|
||||
|
||||
// Run controller functionality
|
||||
scope.update();
|
||||
@@ -157,7 +157,7 @@
|
||||
scope.articles = [sampleArticle];
|
||||
|
||||
// Set expected DELETE response
|
||||
$httpBackend.expectDELETE(/articles\/([0-9a-fA-F]{24})$/).respond(204);
|
||||
$httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204);
|
||||
|
||||
// Run controller functionality
|
||||
scope.remove(sampleArticle);
|
||||
@@ -167,4 +167,4 @@
|
||||
expect(scope.articles.length).toBe(0);
|
||||
}));
|
||||
});
|
||||
}());
|
||||
}());
|
||||
10
modules/articles/tests/e2e/articles.e2e.tests.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
describe('Articles E2E Tests:', function() {
|
||||
describe('Test articles page', function() {
|
||||
it('Should report missing credentials', function() {
|
||||
browser.get('http://localhost:3000/#!/articles');
|
||||
expect(element.all(by.repeater('article in articles')).count()).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,21 +2,29 @@
|
||||
|
||||
var should = require('should'),
|
||||
request = require('supertest'),
|
||||
app = require('../../server'),
|
||||
path = require('path'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Article = mongoose.model('Article'),
|
||||
agent = request.agent(app);
|
||||
express = require(path.resolve('./config/lib/express'));
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
var credentials, user, article;
|
||||
var app, agent, credentials, user, article;
|
||||
|
||||
/**
|
||||
* Article routes tests
|
||||
*/
|
||||
describe('Article CRUD tests', function() {
|
||||
before(function(done) {
|
||||
// Get application
|
||||
app = express.init(mongoose);
|
||||
agent = request.agent(app);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
beforeEach(function(done) {
|
||||
// Create user credentials
|
||||
credentials = {
|
||||
@@ -47,7 +55,7 @@ describe('Article CRUD tests', function() {
|
||||
});
|
||||
|
||||
it('should be able to save an article if logged in', function(done) {
|
||||
agent.post('/auth/signin')
|
||||
agent.post('/api/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
@@ -58,7 +66,7 @@ describe('Article CRUD tests', function() {
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/articles')
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
@@ -66,7 +74,7 @@ describe('Article CRUD tests', function() {
|
||||
if (articleSaveErr) done(articleSaveErr);
|
||||
|
||||
// Get a list of articles
|
||||
agent.get('/articles')
|
||||
agent.get('/api/articles')
|
||||
.end(function(articlesGetErr, articlesGetRes) {
|
||||
// Handle article save error
|
||||
if (articlesGetErr) done(articlesGetErr);
|
||||
@@ -86,9 +94,9 @@ describe('Article CRUD tests', function() {
|
||||
});
|
||||
|
||||
it('should not be able to save an article if not logged in', function(done) {
|
||||
agent.post('/articles')
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(401)
|
||||
.expect(403)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
// Call the assertion callback
|
||||
done(articleSaveErr);
|
||||
@@ -99,7 +107,7 @@ describe('Article CRUD tests', function() {
|
||||
// Invalidate title field
|
||||
article.title = '';
|
||||
|
||||
agent.post('/auth/signin')
|
||||
agent.post('/api/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
@@ -110,7 +118,7 @@ describe('Article CRUD tests', function() {
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/articles')
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(400)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
@@ -124,7 +132,7 @@ describe('Article CRUD tests', function() {
|
||||
});
|
||||
|
||||
it('should be able to update an article if signed in', function(done) {
|
||||
agent.post('/auth/signin')
|
||||
agent.post('/api/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
@@ -135,7 +143,7 @@ describe('Article CRUD tests', function() {
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/articles')
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
@@ -146,7 +154,7 @@ describe('Article CRUD tests', function() {
|
||||
article.title = 'WHY YOU GOTTA BE SO MEAN?';
|
||||
|
||||
// Update an existing article
|
||||
agent.put('/articles/' + articleSaveRes.body._id)
|
||||
agent.put('/api/articles/' + articleSaveRes.body._id)
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleUpdateErr, articleUpdateRes) {
|
||||
@@ -171,7 +179,7 @@ describe('Article CRUD tests', function() {
|
||||
// Save the article
|
||||
articleObj.save(function() {
|
||||
// Request articles
|
||||
request(app).get('/articles')
|
||||
request(app).get('/api/articles')
|
||||
.end(function(req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.an.Array.with.lengthOf(1);
|
||||
@@ -190,7 +198,7 @@ describe('Article CRUD tests', function() {
|
||||
|
||||
// Save the article
|
||||
articleObj.save(function() {
|
||||
request(app).get('/articles/' + articleObj._id)
|
||||
request(app).get('/api/articles/' + articleObj._id)
|
||||
.end(function(req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.an.Object.with.property('title', article.title);
|
||||
@@ -213,7 +221,7 @@ describe('Article CRUD tests', function() {
|
||||
});
|
||||
|
||||
it('should be able to delete an article if signed in', function(done) {
|
||||
agent.post('/auth/signin')
|
||||
agent.post('/api/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
@@ -224,7 +232,7 @@ describe('Article CRUD tests', function() {
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/articles')
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
@@ -232,7 +240,7 @@ describe('Article CRUD tests', function() {
|
||||
if (articleSaveErr) done(articleSaveErr);
|
||||
|
||||
// Delete an existing article
|
||||
agent.delete('/articles/' + articleSaveRes.body._id)
|
||||
agent.delete('/api/articles/' + articleSaveRes.body._id)
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleDeleteErr, articleDeleteRes) {
|
||||
@@ -259,11 +267,11 @@ describe('Article CRUD tests', function() {
|
||||
// Save the article
|
||||
articleObj.save(function() {
|
||||
// Try deleting article
|
||||
request(app).delete('/articles/' + articleObj._id)
|
||||
.expect(401)
|
||||
request(app).delete('/api/articles/' + articleObj._id)
|
||||
.expect(403)
|
||||
.end(function(articleDeleteErr, articleDeleteRes) {
|
||||
// Set message assertion
|
||||
(articleDeleteRes.body.message).should.match('User is not logged in');
|
||||
(articleDeleteRes.body.message).should.match('User is not authorized');
|
||||
|
||||
// Handle article error error
|
||||
done(articleDeleteErr);
|
||||
4
modules/chat/client/chat.client.module.js
Normal file
@@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
// Use Applicaion configuration module to register a new module
|
||||
ApplicationConfiguration.registerModule('chat');
|
||||
12
modules/chat/client/config/chat.client.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
// Configuring the Chat module
|
||||
angular.module('chat').run(['Menus',
|
||||
function(Menus) {
|
||||
// Set top bar menu items
|
||||
Menus.addMenuItem('topbar', {
|
||||
title: 'Chat',
|
||||
state: 'chat'
|
||||
});
|
||||
}
|
||||
]);
|
||||
12
modules/chat/client/config/chat.client.routes.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
// Configure the 'chat' module routes
|
||||
angular.module('chat').config(['$stateProvider',
|
||||
function($stateProvider) {
|
||||
$stateProvider.
|
||||
state('chat', {
|
||||
url: '/chat',
|
||||
templateUrl: 'modules/chat/views/chat.client.view.html'
|
||||
});
|
||||
}
|
||||
]);
|
||||
34
modules/chat/client/controllers/chat.client.controller.js
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
// Create the 'chat' controller
|
||||
angular.module('chat').controller('ChatController', ['$scope', 'Socket',
|
||||
function($scope, Socket) {
|
||||
// Create a messages array
|
||||
$scope.messages = [];
|
||||
|
||||
// Add an event listener to the 'chatMessage' event
|
||||
Socket.on('chatMessage', function(message) {
|
||||
$scope.messages.unshift(message);
|
||||
});
|
||||
|
||||
// Create a controller method for sending messages
|
||||
$scope.sendMessage = function() {
|
||||
// Create a new message object
|
||||
var message = {
|
||||
text: this.messageText
|
||||
};
|
||||
|
||||
// Emit a 'chatMessage' message event
|
||||
Socket.emit('chatMessage', message);
|
||||
|
||||
// Clear the message text
|
||||
this.messageText = '';
|
||||
};
|
||||
|
||||
// Remove the event listener when the controller instance is destroyed
|
||||
$scope.$on('$destroy', function() {
|
||||
Socket.removeListener('chatMessage');
|
||||
});
|
||||
|
||||
}
|
||||
]);
|
||||
18
modules/chat/client/css/chat.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.chat-message {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.chat-message:not(:first-child) {
|
||||
border-top: 1px solid #e7e7e7;
|
||||
}
|
||||
|
||||
.chat-message-details {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.chat-profile-image {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
28
modules/chat/client/views/chat.client.view.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!-- The chat view -->
|
||||
<section class="container" data-ng-controller="ChatController">
|
||||
<div class="page-header">
|
||||
<h1>Chat Example</h1>
|
||||
</div>
|
||||
<!-- The message form -->
|
||||
<form class="col-xs-12 col-md-offset-4 col-md-4" ng-submit="sendMessage();">
|
||||
<fieldset class="row">
|
||||
<div class="input-group">
|
||||
<input type="text" id="messageText" name="messageText" class="form-control" data-ng-model="messageText" placeholder="Enter new message">
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<ul class="list-unstyled">
|
||||
<!-- List all messages -->
|
||||
<li class="col-xs-12 col-md-offset-4 col-md-4 chat-message" data-ng-repeat="message in messages">
|
||||
<small class="pull-right text-muted" data-ng-bind="message.created | date:'mediumTime'"></small>
|
||||
<img data-ng-src="{{message.profileImageURL}}" alt="{{message.username}}" class="pull-left chat-profile-image"/>
|
||||
<div class="pull-left chat-message-details">
|
||||
<strong data-ng-bind="message.username"></strong><br>
|
||||
<span data-ng-bind="message.text"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
34
modules/chat/server/sockets/chat.server.socket.config.js
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
// Create the chat configuration
|
||||
module.exports = function(io, socket) {
|
||||
// Emit the status event when a new socket client is connected
|
||||
io.emit('chatMessage', {
|
||||
type: 'status',
|
||||
text: 'Is now connected',
|
||||
created: Date.now(),
|
||||
profileImageURL: socket.request.user.profileImageURL,
|
||||
username: socket.request.user.username
|
||||
});
|
||||
|
||||
// Send a chat messages to all connected sockets when a message is received
|
||||
socket.on('chatMessage', function(message) {
|
||||
message.type = 'message';
|
||||
message.created = Date.now();
|
||||
message.profileImageURL = socket.request.user.profileImageURL;
|
||||
message.username = socket.request.user.username;
|
||||
|
||||
// Emit the 'chatMessage' event
|
||||
io.emit('chatMessage', message);
|
||||
});
|
||||
|
||||
// Emit the status event when a socket client is disconnected
|
||||
socket.on('disconnect', function() {
|
||||
io.emit('chatMessage', {
|
||||
type: 'status',
|
||||
text: 'disconnected',
|
||||
created: Date.now(),
|
||||
username: socket.request.user.username
|
||||
});
|
||||
});
|
||||
};
|
||||
10
modules/chat/tests/client/chat.client.controller.tests.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Chat client controller tests
|
||||
*/
|
||||
(function() {
|
||||
describe('ChatController', function() {
|
||||
// TODO: Add chat client controller tests
|
||||
});
|
||||
}());
|
||||
8
modules/chat/tests/e2e/chat.e2e.tests.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Chat e2e tests
|
||||
*/
|
||||
describe('Chat E2E Tests:', function() {
|
||||
// TODO: Add chat e2e tests
|
||||
});
|
||||
8
modules/chat/tests/server/chat.socket.tests.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Chat socket tests
|
||||
*/
|
||||
describe('Chat Socket Tests:', function() {
|
||||
// TODO: Add chat socket tests
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
var ApplicationConfiguration = (function() {
|
||||
// Init module configuration options
|
||||
var applicationModuleName = 'mean';
|
||||
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils'];
|
||||
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils', 'angularFileUpload'];
|
||||
|
||||
// Add a new vertical module
|
||||
var registerModule = function(moduleName, dependencies) {
|
||||
@@ -20,4 +20,4 @@ var ApplicationConfiguration = (function() {
|
||||
applicationModuleVendorDependencies: applicationModuleVendorDependencies,
|
||||
registerModule: registerModule
|
||||
};
|
||||
})();
|
||||
})();
|
||||
@@ -6,7 +6,7 @@ angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfig
|
||||
// Setting HTML5 Location Mode
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider',
|
||||
function($locationProvider) {
|
||||
$locationProvider.hashPrefix('!');
|
||||
$locationProvider.html5Mode(true).hashPrefix('!');
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -17,4 +17,4 @@ angular.element(document).ready(function() {
|
||||
|
||||
//Then init the app
|
||||
angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]);
|
||||
});
|
||||
});
|
||||
2
public/modules/core/config/core.client.routes.js → modules/core/client/config/core.client.routes.js
Executable file → Normal file
@@ -13,4 +13,4 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
|
||||
templateUrl: 'modules/core/views/home.client.view.html'
|
||||
});
|
||||
}
|
||||
]);
|
||||
]);
|
||||
@@ -1,11 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus',
|
||||
function($scope, Authentication, Menus) {
|
||||
angular.module('core').controller('HeaderController', ['$scope', '$state', 'Authentication', 'Menus',
|
||||
function($scope, $state, Authentication, Menus) {
|
||||
// Expose view variables
|
||||
$scope.$state = $state;
|
||||
$scope.authentication = Authentication;
|
||||
$scope.isCollapsed = false;
|
||||
|
||||
// Get the topbar menu
|
||||
$scope.menu = Menus.getMenu('topbar');
|
||||
|
||||
// Toggle the menu items
|
||||
$scope.isCollapsed = false;
|
||||
$scope.toggleCollapsibleMenu = function() {
|
||||
$scope.isCollapsed = !$scope.isCollapsed;
|
||||
};
|
||||
@@ -15,4 +20,4 @@ angular.module('core').controller('HeaderController', ['$scope', 'Authentication
|
||||
$scope.isCollapsed = false;
|
||||
});
|
||||
}
|
||||
]);
|
||||
]);
|
||||
@@ -1,9 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
angular.module('core').controller('HomeController', ['$scope', 'Authentication',
|
||||
function($scope, Authentication) {
|
||||
// This provides Authentication context.
|
||||
$scope.authentication = Authentication;
|
||||
}
|
||||
]);
|
||||
]);
|
||||
4
modules/core/client/core.client.module.js
Normal file
@@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
// Use Applicaion configuration module to register a new module
|
||||
ApplicationConfiguration.registerModule('core');
|
||||
33
modules/core/client/css/core.css
Normal file
@@ -0,0 +1,33 @@
|
||||
.content {
|
||||
margin-top: 50px;
|
||||
}
|
||||
.undecorated-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
.ng-invalid.ng-dirty{
|
||||
border-color:#FA787E;
|
||||
}
|
||||
.ng-valid.ng-dirty{
|
||||
border-color:#78FA89;
|
||||
}
|
||||
|
||||
.header-profile-image {
|
||||
opacity: 0.8;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.open .header-profile-image,
|
||||
a:hover .header-profile-image {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.user-header-dropdown-toggle {
|
||||
padding-top: 11px !important;
|
||||
padding-bottom: 11px !important;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
179
modules/core/client/services/menus.client.service.js
Normal file
@@ -0,0 +1,179 @@
|
||||
'use strict';
|
||||
|
||||
//Menu service used for managing menus
|
||||
angular.module('core').service('Menus', [
|
||||
|
||||
function() {
|
||||
// Define a set of default roles
|
||||
this.defaultRoles = ['*'];
|
||||
|
||||
// Define the menus object
|
||||
this.menus = {};
|
||||
|
||||
// A private function for rendering decision
|
||||
var shouldRender = function(user) {
|
||||
if (user) {
|
||||
if (!!~this.roles.indexOf('*')) {
|
||||
return true;
|
||||
} else {
|
||||
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, options) {
|
||||
options = options || {};
|
||||
|
||||
// Create the new menu
|
||||
this.menus[menuId] = {
|
||||
isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? true : options.isPublic),
|
||||
roles: options.roles || this.defaultRoles,
|
||||
items: options.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, options) {
|
||||
options = options || {};
|
||||
|
||||
// Validate that the menu exists
|
||||
this.validateMenuExistance(menuId);
|
||||
|
||||
// Push new menu item
|
||||
this.menus[menuId].items.push({
|
||||
title: options.title || '',
|
||||
state: options.state || '',
|
||||
type: options.type || 'item',
|
||||
class: options.class,
|
||||
isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? this.menus[menuId].isPublic : options.isPublic),
|
||||
roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].roles : options.roles),
|
||||
position: options.position || 0,
|
||||
items: [],
|
||||
shouldRender: shouldRender
|
||||
});
|
||||
|
||||
// Add submenu items
|
||||
if (options.items) {
|
||||
for (var i in options.items) {
|
||||
this.addSubMenuItem(menuId, options.link, options.items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the menu object
|
||||
return this.menus[menuId];
|
||||
};
|
||||
|
||||
// Add submenu item object
|
||||
this.addSubMenuItem = function(menuId, parentItemState, options) {
|
||||
options = options || {};
|
||||
|
||||
// Validate that the menu exists
|
||||
this.validateMenuExistance(menuId);
|
||||
|
||||
// Search for menu item
|
||||
for (var itemIndex in this.menus[menuId].items) {
|
||||
if (this.menus[menuId].items[itemIndex].state === parentItemState) {
|
||||
// Push new submenu item
|
||||
this.menus[menuId].items[itemIndex].items.push({
|
||||
title: options.title || '',
|
||||
state: options.state|| '',
|
||||
isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : options.isPublic),
|
||||
roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : options.roles),
|
||||
position: options.position || 0,
|
||||
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].link === menuItemURL) {
|
||||
this.menus[menuId].items.splice(itemIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the menu object
|
||||
return this.menus[menuId];
|
||||
};
|
||||
|
||||
// Remove existing menu object by menu id
|
||||
this.removeSubMenuItem = function(menuId, submenuItemURL) {
|
||||
// Validate that the menu exists
|
||||
this.validateMenuExistance(menuId);
|
||||
|
||||
// Search for menu item to remove
|
||||
for (var itemIndex in this.menus[menuId].items) {
|
||||
for (var subitemIndex in this.menus[menuId].items[itemIndex].items) {
|
||||
if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) {
|
||||
this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the menu object
|
||||
return this.menus[menuId];
|
||||
};
|
||||
|
||||
//Adding the topbar menu
|
||||
this.addMenu('topbar', {
|
||||
isPublic: false
|
||||
});
|
||||
}
|
||||
]);
|
||||
38
modules/core/client/services/socket.io.client.service.js
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
// Create the Socket.io wrapper service
|
||||
angular.module('core').service('Socket', ['Authentication', '$state', '$timeout',
|
||||
function(Authentication, $state, $timeout) {
|
||||
// Connect to the Socket.io server only when authenticated
|
||||
if (Authentication.user) {
|
||||
this.socket = io();
|
||||
} else {
|
||||
$state.go('home');
|
||||
}
|
||||
|
||||
// Wrap the Socket.io 'on' method
|
||||
this.on = function(eventName, callback) {
|
||||
if (this.socket) {
|
||||
this.socket.on(eventName, function(data) {
|
||||
$timeout(function() {
|
||||
callback(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap the Socket.io 'emit' method
|
||||
this.emit = function(eventName, data) {
|
||||
if (this.socket) {
|
||||
this.socket.emit(eventName, data);
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap the Socket.io 'removeListener' method
|
||||
this.removeListener = function(eventName) {
|
||||
if (this.socket) {
|
||||
this.socket.removeListener(eventName);
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
62
modules/core/client/views/header.client.view.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<div class="container" data-ng-controller="HeaderController">
|
||||
<div class="navbar-header">
|
||||
<button class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a data-ui-sref="home" class="navbar-brand">MEAN.JS</a>
|
||||
</div>
|
||||
<nav class="collapse navbar-collapse" collapse="!isCollapsed" role="navigation">
|
||||
<ul class="nav navbar-nav" data-ng-if="menu.shouldRender(authentication.user);">
|
||||
<li data-ng-repeat="item in menu.items | orderBy: 'position'" data-ng-if="item.shouldRender(authentication.user);" ng-switch="item.type" data-ng-class="{active: $state.includes(item.state)}" class="{{item.class}}" dropdown="item.type === 'dropdown'">
|
||||
<a ng-switch-when="dropdown" class="dropdown-toggle">
|
||||
<span data-ng-bind="item.title"></span>
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul ng-switch-when="dropdown" class="dropdown-menu">
|
||||
<li data-ng-repeat="subitem in item.items | orderBy: 'position'" data-ng-if="subitem.shouldRender(authentication.user);" data-ui-sref-active="active">
|
||||
<a data-ui-sref="{{subitem.state}}" data-ng-bind="subitem.title"></a>
|
||||
</li>
|
||||
</ul>
|
||||
<a ng-switch-default data-ui-sref="{{item.state}}" data-ng-bind="item.title"></a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right" data-ng-hide="authentication.user">
|
||||
<li data-ui-sref-active="active">
|
||||
<a data-ui-sref="authentication.signup">Sign Up</a>
|
||||
</li>
|
||||
<li class="divider-vertical"></li>
|
||||
<li data-ui-sref-active="active">
|
||||
<a data-ui-sref="authentication.signin">Sign In</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right" data-ng-show="authentication.user">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle user-header-dropdown-toggle" data-toggle="dropdown">
|
||||
<img data-ng-src="{{authentication.user.profileImageURL}}" alt="{{authentication.user.displayName}}" class="header-profile-image"/>
|
||||
<span data-ng-bind="authentication.user.displayName"></span> <b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li data-ui-sref-active="active">
|
||||
<a data-ui-sref="settings.profile">Edit Profile</a>
|
||||
</li>
|
||||
<li data-ui-sref-active="active">
|
||||
<a data-ui-sref="settings.picture">Change Profile Picture</a>
|
||||
</li>
|
||||
<li data-ui-sref-active="active" data-ng-show="authentication.user.provider === 'local'">
|
||||
<a data-ui-sref="settings.password">Change Password</a>
|
||||
</li>
|
||||
<li data-ui-sref-active="active">
|
||||
<a data-ui-sref="settings.accounts">Manage Social Accounts</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href="/api/auth/signout" target="_self">Signout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Congrats! You've configured and ran the sample application successfully.</h2>
|
||||
<h2>Congrats! You've configured and run the sample application.</h2>
|
||||
<p>MEAN.JS is a web application boilerplate, which means you should start changing everything :-)</p>
|
||||
<p>This sample application tracks users and articles.</p>
|
||||
<ul>
|
||||
@@ -54,7 +54,11 @@
|
||||
<h2>
|
||||
<strong>E</strong>xpress
|
||||
</h2>
|
||||
<<<<<<< HEAD:public/modules/core/views/home.client.view.html
|
||||
<p><a target="_blank" href="http://expressjs.com/"> Express</a> is an app server. Check out <a target="_blank" href="http://expressjs.com/4x/api.html">The ExpressJS API reference for more information</a> or <a target="_blank" href="http://stackoverflow.com/questions/8144214/learning-express-for-node-js">StackOverflow</a> for more info.</p>
|
||||
=======
|
||||
<p><a target="_blank" href="http://expressjs.com/"> Express</a> is an app server. Check out <a target="_blank" href="http://expressjs.com/guide.html">The Express Guide</a> or <a target="_blank" href="http://stackoverflow.com/questions/8144214/learning-express-for-node-js">StackOverflow</a> for more info.</p>
|
||||
>>>>>>> 0.4.0:modules/core/client/views/home.client.view.html
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>
|
||||
28
modules/core/server/controllers/core.server.controller.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Render the main applicaion page
|
||||
*/
|
||||
exports.renderIndex = function(req, res) {
|
||||
res.render('modules/core/server/views/index', {
|
||||
user: req.user || null
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the server error page
|
||||
*/
|
||||
exports.renderServerError = function(req, res) {
|
||||
res.status(500).render('modules/core/server/views/500', {
|
||||
error: 'Oops! Something went wrong...'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the server not found page
|
||||
*/
|
||||
exports.renderNotFound = function(req, res) {
|
||||
res.status(404).render('modules/core/server/views/404', {
|
||||
url: req.originalUrl
|
||||
});
|
||||
};
|
||||
@@ -8,10 +8,10 @@ var getUniqueErrorMessage = function(err) {
|
||||
|
||||
try {
|
||||
var fieldName = err.err.substring(err.err.lastIndexOf('.$') + 2, err.err.lastIndexOf('_1'));
|
||||
output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists';
|
||||
output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exist';
|
||||
|
||||
} catch (ex) {
|
||||
output = 'Unique field already exists';
|
||||
} catch(ex) {
|
||||
output = 'Unique field already exist';
|
||||
}
|
||||
|
||||
return output;
|
||||
@@ -22,7 +22,7 @@ var getUniqueErrorMessage = function(err) {
|
||||
*/
|
||||
exports.getErrorMessage = function(err) {
|
||||
var message = '';
|
||||
|
||||
|
||||
if (err.code) {
|
||||
switch (err.code) {
|
||||
case 11000:
|
||||
13
modules/core/server/routes/core.server.routes.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(app) {
|
||||
// Root routing
|
||||
var core = require('../controllers/core.server.controller');
|
||||
|
||||
// Define error pages
|
||||
app.route('/server-error').get(core.renderServerError);
|
||||
app.route('/not-found').get(core.renderNotFound);
|
||||
|
||||
// Define application route
|
||||
app.route('/*').get(core.renderIndex);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>{{title}}</title>
|
||||
@@ -8,6 +8,10 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="fragment" content="!">
|
||||
<base href="/">
|
||||
|
||||
<!-- Responsive META -->
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
@@ -22,22 +26,20 @@
|
||||
<meta property="og:title" content="{{title}}">
|
||||
<meta property="og:description" content="{{description}}">
|
||||
<meta property="og:url" content="{{url}}">
|
||||
<meta property="og:image" content="/img/brand/logo.png">
|
||||
<meta property="og:image" content="{{url}}modules/core/img/brand/logo.png">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<!-- Twitter META -->
|
||||
<meta name="twitter:title" content="{{title}}">
|
||||
<meta name="twitter:description" content="{{description}}">
|
||||
<meta name="twitter:url" content="{{url}}">
|
||||
<meta name="twitter:image" content="/img/brand/logo.png">
|
||||
<meta name="twitter:image" content="{{url}}modules/core/img/brand/logo.png">
|
||||
|
||||
<!-- Fav Icon -->
|
||||
<link href="/modules/core/img/brand/favicon.ico" rel="shortcut icon" type="image/x-icon">
|
||||
|
||||
<!--Application CSS Files-->
|
||||
{% for cssFile in cssFiles %}
|
||||
<link rel="stylesheet" href="{{cssFile}}">
|
||||
{% endfor %}
|
||||
{% for cssFile in cssFiles %}<link rel="stylesheet" href="{{cssFile}}">{% endfor %}
|
||||
|
||||
<!-- HTML5 Shim -->
|
||||
<!--[if lt IE 9]>
|
||||
@@ -57,25 +59,17 @@
|
||||
<script type="text/javascript">
|
||||
var user = {{ user | json | safe }};
|
||||
</script>
|
||||
|
||||
<!--Load The Socket.io File-->
|
||||
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
|
||||
|
||||
<!--Application JavaScript Files-->
|
||||
{% for jsFile in jsFiles %}
|
||||
<script type="text/javascript" src="{{jsFile}}"></script>
|
||||
{% endfor %}
|
||||
{% for jsFile in jsFiles %}<script type="text/javascript" src="{{jsFile}}"></script>{% endfor %}
|
||||
|
||||
{% if process.env.NODE_ENV === 'development' %}
|
||||
<!--Livereload script rendered -->
|
||||
<script type="text/javascript" src="http://{{request.hostname}}:35729/livereload.js"></script>
|
||||
<script type="text/javascript" src="{{host}}:35729/livereload.js"></script>
|
||||
{% endif %}
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<section class="browsehappy jumbotron hide">
|
||||
<h1>Hello there!</h1>
|
||||
<p>You are using an old browser which we unfortunately do not support.</p>
|
||||
<p>Please <a href="http://browsehappy.com/">click here</a> to update your browser before using the website.</p>
|
||||
<p><a href="http://browsehappy.com" class="btn btn-primary btn-lg" role="button">Yes, upgrade my browser!</a></p>
|
||||
</section>
|
||||
<![endif]-->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -21,4 +21,4 @@
|
||||
expect(scope.authentication).toBeTruthy();
|
||||
});
|
||||
});
|
||||
})();
|
||||
})();
|
||||
@@ -21,4 +21,4 @@
|
||||
expect(scope.authentication).toBeTruthy();
|
||||
});
|
||||
});
|
||||
})();
|
||||
})();
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
// Config HTTP Error Handling
|
||||
angular.module('users').config(['$httpProvider',
|
||||
function($httpProvider) {
|
||||
function ($httpProvider) {
|
||||
// Set the httpProvider "not authorized" interceptor
|
||||
$httpProvider.interceptors.push(['$q', '$location', 'Authentication',
|
||||
function($q, $location, Authentication) {
|
||||
function ($q, $location, Authentication) {
|
||||
return {
|
||||
responseError: function(rejection) {
|
||||
responseError: function (rejection) {
|
||||
switch (rejection.status) {
|
||||
case 401:
|
||||
// Deauthenticate the global user
|
||||
@@ -17,7 +17,7 @@ angular.module('users').config(['$httpProvider',
|
||||
$location.path('signin');
|
||||
break;
|
||||
case 403:
|
||||
// Add unauthorized behaviour
|
||||
// Add unauthorized behaviour
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -27,4 +27,4 @@ angular.module('users').config(['$httpProvider',
|
||||
}
|
||||
]);
|
||||
}
|
||||
]);
|
||||
]);
|
||||
69
modules/users/client/config/users.client.routes.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
// Setting up route
|
||||
angular.module('users').config(['$stateProvider',
|
||||
function ($stateProvider) {
|
||||
// Users state routing
|
||||
$stateProvider.
|
||||
state('settings', {
|
||||
abstract: true,
|
||||
url: '/settings',
|
||||
templateUrl: 'modules/users/views/settings/settings.client.view.html'
|
||||
}).
|
||||
state('settings.profile', {
|
||||
url: '/profile',
|
||||
templateUrl: 'modules/users/views/settings/edit-profile.client.view.html'
|
||||
}).
|
||||
state('settings.password', {
|
||||
url: '/password',
|
||||
templateUrl: 'modules/users/views/settings/change-password.client.view.html'
|
||||
}).
|
||||
state('settings.accounts', {
|
||||
url: '/accounts',
|
||||
templateUrl: 'modules/users/views/settings/manage-social-accounts.client.view.html'
|
||||
}).
|
||||
state('settings.picture', {
|
||||
url: '/picture',
|
||||
templateUrl: 'modules/users/views/settings/change-profile-picture.client.view.html'
|
||||
}).
|
||||
state('authentication', {
|
||||
abstract: true,
|
||||
url: '/authentication',
|
||||
templateUrl: 'modules/users/views/authentication/authentication.client.view.html'
|
||||
}).
|
||||
state('authentication.signup', {
|
||||
url: '/signup',
|
||||
templateUrl: 'modules/users/views/authentication/signup.client.view.html'
|
||||
}).
|
||||
state('authentication.signin', {
|
||||
url: '/signin',
|
||||
templateUrl: 'modules/users/views/authentication/signin.client.view.html'
|
||||
}).
|
||||
state('password', {
|
||||
abstract: true,
|
||||
url: '/password',
|
||||
template: '<ui-view/>'
|
||||
}).
|
||||
state('password.forgot', {
|
||||
url: '/forgot',
|
||||
templateUrl: 'modules/users/views/password/forgot-password.client.view.html'
|
||||
}).
|
||||
state('password.reset', {
|
||||
abstract: true,
|
||||
url: '/reset',
|
||||
template: '<ui-view/>'
|
||||
}).
|
||||
state('password.reset.invalid', {
|
||||
url: '/invalid',
|
||||
templateUrl: 'modules/users/views/password/reset-password-invalid.client.view.html'
|
||||
}).
|
||||
state('password.reset.success', {
|
||||
url: '/success',
|
||||
templateUrl: 'modules/users/views/password/reset-password-success.client.view.html'
|
||||
}).
|
||||
state('password.reset.form', {
|
||||
url: '/:token',
|
||||
templateUrl: 'modules/users/views/password/reset-password.client.view.html'
|
||||
});
|
||||
}
|
||||
]);
|
||||
@@ -8,7 +8,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$http
|
||||
if ($scope.authentication.user) $location.path('/');
|
||||
|
||||
$scope.signup = function() {
|
||||
$http.post('/auth/signup', $scope.credentials).success(function(response) {
|
||||
$http.post('/api/auth/signup', $scope.credentials).success(function(response) {
|
||||
// If successful we assign the response to the global user model
|
||||
$scope.authentication.user = response;
|
||||
|
||||
@@ -20,7 +20,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$http
|
||||
};
|
||||
|
||||
$scope.signin = function() {
|
||||
$http.post('/auth/signin', $scope.credentials).success(function(response) {
|
||||
$http.post('/api/auth/signin', $scope.credentials).success(function(response) {
|
||||
// If successful we assign the response to the global user model
|
||||
$scope.authentication.user = response;
|
||||
|
||||
@@ -31,4 +31,4 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$http
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
]);
|
||||
@@ -11,7 +11,7 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam
|
||||
$scope.askForPasswordReset = function() {
|
||||
$scope.success = $scope.error = null;
|
||||
|
||||
$http.post('/auth/forgot', $scope.credentials).success(function(response) {
|
||||
$http.post('/api/auth/forgot', $scope.credentials).success(function(response) {
|
||||
// Show user success message and clear form
|
||||
$scope.credentials = null;
|
||||
$scope.success = response.message;
|
||||
@@ -27,7 +27,7 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam
|
||||
$scope.resetUserPassword = function() {
|
||||
$scope.success = $scope.error = null;
|
||||
|
||||
$http.post('/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) {
|
||||
$http.post('/api/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) {
|
||||
// If successful show success message and clear form
|
||||
$scope.passwordDetails = null;
|
||||
|
||||
@@ -41,4 +41,4 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
]);
|
||||
@@ -25,7 +25,7 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l
|
||||
$scope.removeUserSocialAccount = function(provider) {
|
||||
$scope.success = $scope.error = null;
|
||||
|
||||
$http.delete('/users/accounts', {
|
||||
$http.delete('/api/users/accounts', {
|
||||
params: {
|
||||
provider: provider
|
||||
}
|
||||
@@ -40,10 +40,10 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l
|
||||
|
||||
// Update a user profile
|
||||
$scope.updateUserProfile = function(isValid) {
|
||||
if (isValid) {
|
||||
if (isValid){
|
||||
$scope.success = $scope.error = null;
|
||||
var user = new Users($scope.user);
|
||||
|
||||
|
||||
user.$update(function(response) {
|
||||
$scope.success = true;
|
||||
Authentication.user = response;
|
||||
@@ -59,7 +59,7 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l
|
||||
$scope.changeUserPassword = function() {
|
||||
$scope.success = $scope.error = null;
|
||||
|
||||
$http.post('/users/password', $scope.passwordDetails).success(function(response) {
|
||||
$http.post('/api/users/password', $scope.passwordDetails).success(function(response) {
|
||||
// If successful show success message and clear form
|
||||
$scope.success = true;
|
||||
$scope.passwordDetails = null;
|
||||
@@ -68,4 +68,4 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
]);
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('users').controller('ChangePasswordController', ['$scope', '$http', '$location', 'Users', 'Authentication',
|
||||
function($scope, $http, $location, Users, Authentication) {
|
||||
$scope.user = Authentication.user;
|
||||
|
||||
// Change user password
|
||||
$scope.changeUserPassword = function() {
|
||||
$scope.success = $scope.error = null;
|
||||
|
||||
$http.post('/api/users/password', $scope.passwordDetails).success(function(response) {
|
||||
// If successful show success message and clear form
|
||||
$scope.success = true;
|
||||
$scope.passwordDetails = null;
|
||||
}).error(function(response) {
|
||||
$scope.error = response.message;
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('users').controller('ChangeProfilePictureController', ['$scope', '$timeout', '$window', 'Authentication', 'FileUploader',
|
||||
function ($scope, $timeout, $window, Authentication, FileUploader) {
|
||||
$scope.user = Authentication.user;
|
||||
$scope.imageURL = $scope.user.profileImageURL;
|
||||
|
||||
// Create file uploader instance
|
||||
$scope.uploader = new FileUploader({
|
||||
url: 'api/users/picture'
|
||||
});
|
||||
|
||||
// Set file uploader image filter
|
||||
$scope.uploader.filters.push({
|
||||
name: 'imageFilter',
|
||||
fn: function (item, options) {
|
||||
var type = '|' + item.type.slice(item.type.lastIndexOf('/') + 1) + '|';
|
||||
return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1;
|
||||
}
|
||||
});
|
||||
|
||||
// Called after the user selected a new picture file
|
||||
$scope.uploader.onAfterAddingFile = function (fileItem) {
|
||||
if ($window.FileReader) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.readAsDataURL(fileItem._file);
|
||||
|
||||
fileReader.onload = function (fileReaderEvent) {
|
||||
$timeout(function () {
|
||||
$scope.imageURL = fileReaderEvent.target.result;
|
||||
}, 0);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Called after the user has successfully uploaded a new picture
|
||||
$scope.uploader.onSuccessItem = function (fileItem, response, status, headers) {
|
||||
// Show success message
|
||||
$scope.success = true;
|
||||
|
||||
// Populate user object
|
||||
$scope.user = Authentication.user = response;
|
||||
|
||||
// Clear upload buttons
|
||||
$scope.cancelUpload();
|
||||
};
|
||||
|
||||
// Called after the user has failed to uploaded a new picture
|
||||
$scope.uploader.onErrorItem = function (fileItem, response, status, headers) {
|
||||
// Clear upload buttons
|
||||
$scope.cancelUpload();
|
||||
|
||||
// Show error message
|
||||
$scope.error = response.message;
|
||||
};
|
||||
|
||||
// Change user profile picture
|
||||
$scope.uploadProfilePicture = function () {
|
||||
// Clear messages
|
||||
$scope.success = $scope.error = null;
|
||||
|
||||
// Start upload
|
||||
$scope.uploader.uploadAll();
|
||||
};
|
||||
|
||||
// Cancel the upload process
|
||||
$scope.cancelUpload = function () {
|
||||
$scope.uploader.clearQueue();
|
||||
$scope.imageURL = $scope.user.profileImageURL;
|
||||
};
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('users').controller('EditProfileController', ['$scope', '$http', '$location', 'Users', 'Authentication',
|
||||
function($scope, $http, $location, Users, Authentication) {
|
||||
$scope.user = Authentication.user;
|
||||
|
||||
// Update a user profile
|
||||
$scope.updateUserProfile = function(isValid) {
|
||||
if (isValid){
|
||||
$scope.success = $scope.error = null;
|
||||
var user = new Users($scope.user);
|
||||
|
||||
user.$update(function(response) {
|
||||
$scope.success = true;
|
||||
Authentication.user = response;
|
||||
}, function(response) {
|
||||
$scope.error = response.data.message;
|
||||
});
|
||||
} else {
|
||||
$scope.submitted = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('users').controller('SocialAccountsController', ['$scope', '$http', '$location', 'Users', 'Authentication',
|
||||
function($scope, $http, $location, Users, Authentication) {
|
||||
$scope.user = Authentication.user;
|
||||
|
||||
// Check if there are additional accounts
|
||||
$scope.hasConnectedAdditionalSocialAccounts = function(provider) {
|
||||
for (var i in $scope.user.additionalProvidersData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check if provider is already in use with current user
|
||||
$scope.isConnectedSocialAccount = function(provider) {
|
||||
return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]);
|
||||
};
|
||||
|
||||
// Remove a user social account
|
||||
$scope.removeUserSocialAccount = function(provider) {
|
||||
$scope.success = $scope.error = null;
|
||||
|
||||
$http.delete('/api/users/accounts', {
|
||||
params: {
|
||||
provider: provider
|
||||
}
|
||||
}).success(function(response) {
|
||||
// If successful show success message and clear form
|
||||
$scope.success = true;
|
||||
$scope.user = Authentication.user = response;
|
||||
}).error(function(response) {
|
||||
$scope.error = response.message;
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('users').controller('SettingsController', ['$scope', '$http', '$location', 'Users', 'Authentication',
|
||||
function($scope, $http, $location, Users, Authentication) {
|
||||
$scope.user = Authentication.user;
|
||||
|
||||
// If user is not signed in then redirect back home
|
||||
if (!$scope.user) $location.path('/');
|
||||
}
|
||||
]);
|
||||
41
modules/users/client/css/users.css
Normal file
@@ -0,0 +1,41 @@
|
||||
@media (min-width: 992px) {
|
||||
.nav-users {
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
.social-account-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn-remove-account {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.btn-file {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-file input[type=file] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
font-size: 100px;
|
||||
text-align: right;
|
||||
filter: alpha(opacity=0);
|
||||
opacity: 0;
|
||||
background: white;
|
||||
cursor: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.user-profile-picture {
|
||||
min-height: 150px;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
modules/users/client/img/profile/default.png
Normal file
|
After Width: | Height: | Size: 64 KiB |