0.4.0 branch merged into master

This commit is contained in:
Liran Tal
2015-07-02 12:08:09 +03:00
156 changed files with 2718 additions and 1572 deletions

View File

@@ -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
View File

@@ -22,6 +22,9 @@ app/tests/coverage/
config/sslcerts/*.pem
access.log
public/dist/
uploads
modules/users/client/img/profile/uploads
*.pem
# Sublime editor
# ==============

View File

@@ -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.
}
}

View File

@@ -1 +1 @@
/app/tests
/app/tests

0
Procfile Executable file → Normal file
View File

View File

@@ -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
```

View File

@@ -1,11 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
exports.index = function(req, res) {
res.render('index', {
user: req.user || null,
request: req
});
};

View File

@@ -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'
});
}
});
};
};

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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
View 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'
}
};

View File

@@ -0,0 +1,5 @@
'use strict';
module.exports = {
// Development assets
};

View 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
View 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']
}
};

View File

@@ -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
View File

@@ -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
View 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'
};

View File

@@ -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: {

View File

@@ -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
View File

@@ -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
View File

@@ -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',

View File

@@ -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;
};

View File

@@ -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
View 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
View 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
View 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;
};

View File

@@ -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;
}
};

View File

@@ -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
View 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);
});

View File

@@ -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,

View 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');

View 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'
});
}
]);

View 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'
});
}
]);
]);

View File

@@ -65,4 +65,4 @@ angular.module('articles').controller('ArticlesController', ['$scope', '$statePa
});
};
}
]);
]);

View File

@@ -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',
}
});
}
]);
]);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
};
};

View 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'
});
}
}
});
};

View 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);
};

View File

@@ -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);
}));
});
}());
}());

View 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);
});
});
});

View File

@@ -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);

View File

@@ -0,0 +1,4 @@
'use strict';
// Use Applicaion configuration module to register a new module
ApplicationConfiguration.registerModule('chat');

View 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'
});
}
]);

View 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'
});
}
]);

View 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');
});
}
]);

View 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%;
}

View 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>

View 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
});
});
};

View File

@@ -0,0 +1,10 @@
'use strict';
/**
* Chat client controller tests
*/
(function() {
describe('ChatController', function() {
// TODO: Add chat client controller tests
});
}());

View File

@@ -0,0 +1,8 @@
'use strict';
/**
* Chat e2e tests
*/
describe('Chat E2E Tests:', function() {
// TODO: Add chat e2e tests
});

View File

@@ -0,0 +1,8 @@
'use strict';
/**
* Chat socket tests
*/
describe('Chat Socket Tests:', function() {
// TODO: Add chat socket tests
});

View File

@@ -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
};
})();
})();

View File

@@ -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]);
});
});

View File

@@ -13,4 +13,4 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
templateUrl: 'modules/core/views/home.client.view.html'
});
}
]);
]);

View File

@@ -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;
});
}
]);
]);

View File

@@ -1,9 +1,8 @@
'use strict';
angular.module('core').controller('HomeController', ['$scope', 'Authentication',
function($scope, Authentication) {
// This provides Authentication context.
$scope.authentication = Authentication;
}
]);
]);

View File

@@ -0,0 +1,4 @@
'use strict';
// Use Applicaion configuration module to register a new module
ApplicationConfiguration.registerModule('core');

View 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;
}

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View 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
});
}
]);

View 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);
}
};
}
]);

View 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>

View File

@@ -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>

View 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
});
};

View File

@@ -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:

View 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);
};

View File

@@ -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>

View File

@@ -21,4 +21,4 @@
expect(scope.authentication).toBeTruthy();
});
});
})();
})();

View File

@@ -21,4 +21,4 @@
expect(scope.authentication).toBeTruthy();
});
});
})();
})();

View File

@@ -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',
}
]);
}
]);
]);

View 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'
});
}
]);

View File

@@ -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
});
};
}
]);
]);

View File

@@ -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
});
};
}
]);
]);

View File

@@ -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
});
};
}
]);
]);

View File

@@ -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;
});
};
}
]);

View File

@@ -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;
};
}
]);

View File

@@ -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;
}
};
}
]);

View File

@@ -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;
});
};
}
]);

View File

@@ -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('/');
}
]);

View 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;
}

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Some files were not shown because too many files have changed in this diff Show More