mirror of
https://github.com/taobataoma/meanTorrent.git
synced 2026-07-10 16:23:23 +02:00
Merge branch '0.4.0'
Releasing MEAN.JS 0.4.0 version on-top of previously MEAN.JS 0.3.3 on master branch
This commit is contained in:
26
.csslintrc
26
.csslintrc
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"adjoining-classes": false,
|
||||
"box-model": false,
|
||||
"box-sizing": false,
|
||||
"floats": false,
|
||||
"font-sizes": false,
|
||||
"important": false,
|
||||
"known-properties": false,
|
||||
"overqualified-elements": false,
|
||||
"qualified-headings": false,
|
||||
"regex-selectors": false,
|
||||
"unique-headings": false,
|
||||
"universal-selector": false,
|
||||
"unqualified-attributes": false
|
||||
"adjoining-classes": false,
|
||||
"box-model": false,
|
||||
"box-sizing": false,
|
||||
"floats": false,
|
||||
"font-sizes": false,
|
||||
"important": false,
|
||||
"known-properties": false,
|
||||
"overqualified-elements": false,
|
||||
"qualified-headings": false,
|
||||
"regex-selectors": false,
|
||||
"unique-headings": false,
|
||||
"universal-selector": false,
|
||||
"unqualified-attributes": false
|
||||
}
|
||||
|
||||
@@ -1,25 +1,43 @@
|
||||
# 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 = space
|
||||
indent_size = 2
|
||||
quote_type = single
|
||||
curly_bracket_next_line = false
|
||||
spaces_around_operators = true
|
||||
space_after_control_statements = true
|
||||
space_after_anonymous_functions = false
|
||||
space_after_anonymous_functions = true
|
||||
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
|
||||
|
||||
# The indentation in package.json will always need to be 2 spaces
|
||||
# https://github.com/npm/npm/issues/4718
|
||||
[package.json, bower.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
47
.gitignore
vendored
47
.gitignore
vendored
@@ -1,27 +1,45 @@
|
||||
# iOS / Apple
|
||||
# OS
|
||||
# ===========
|
||||
.DS_Store
|
||||
ehthumbs.db
|
||||
Icon?
|
||||
Thumbs.db
|
||||
config/env/local.js
|
||||
|
||||
# Node and related ecosystem
|
||||
# ==========================
|
||||
.nodemonignore
|
||||
.sass-cache/
|
||||
npm-debug.log
|
||||
node_modules/
|
||||
public/lib/
|
||||
app/tests/coverage/
|
||||
.bower-*/
|
||||
.idea/
|
||||
coverage/
|
||||
|
||||
# MEAN.js app and assets
|
||||
# ======================
|
||||
config/sslcerts/*.pem
|
||||
access.log
|
||||
public/dist/
|
||||
uploads
|
||||
modules/users/client/img/profile/uploads
|
||||
config/env/local.js
|
||||
*.pem
|
||||
|
||||
# Ignoring MEAN.JS's gh-pages branch for documenation
|
||||
_site/
|
||||
|
||||
# General
|
||||
# =======
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
logs/
|
||||
build/
|
||||
|
||||
# Sublime editor
|
||||
# ==============
|
||||
@@ -48,16 +66,9 @@ local.properties
|
||||
data/
|
||||
mongod
|
||||
|
||||
# General
|
||||
# =======
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
logs/
|
||||
build/
|
||||
# Visual Studio
|
||||
# =========
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
|
||||
76
.jshintrc
76
.jshintrc
@@ -1,42 +1,36 @@
|
||||
{
|
||||
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||
"browser": true, // Standard browser globals e.g. `window`, `document`.
|
||||
"esnext": true, // Allow ES.next specific features such as `const` and `let`.
|
||||
"bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
|
||||
"camelcase": false, // Permit only camelcase for `var` and `object indexes`.
|
||||
"curly": false, // Require {} for every new block or scope.
|
||||
"eqeqeq": true, // Require triple equals i.e. `===`.
|
||||
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||
"latedef": true, // Prohibit variable use before definition.
|
||||
"newcap": true, // Require capitalization of all constructor functions e.g. `new F()`.
|
||||
"noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
|
||||
"quotmark": "single", // Define quotes to string values.
|
||||
"regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
|
||||
"undef": true, // Require all non-global variables be declared before they are used.
|
||||
"unused": false, // Warn unused variables.
|
||||
"strict": true, // Require `use strict` pragma in every file.
|
||||
"trailing": true, // Prohibit trailing whitespaces.
|
||||
"smarttabs": false, // Suppresses warnings about mixed tabs and spaces
|
||||
"globals": { // Globals variables.
|
||||
"jasmine": true,
|
||||
"angular": true,
|
||||
"ApplicationConfiguration": true
|
||||
},
|
||||
"predef": [ // Extra globals.
|
||||
"define",
|
||||
"require",
|
||||
"exports",
|
||||
"module",
|
||||
"describe",
|
||||
"before",
|
||||
"beforeEach",
|
||||
"after",
|
||||
"afterEach",
|
||||
"it",
|
||||
"inject",
|
||||
"expect"
|
||||
],
|
||||
"indent": 4, // Specify indentation spacing
|
||||
"devel": true, // Allow development statements e.g. `console.log();`.
|
||||
"noempty": true // Prohibit use of empty blocks.
|
||||
}
|
||||
"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.).
|
||||
"camelcase": false, // Permit only camelcase for `var` and `object indexes`.
|
||||
"curly": false, // Require {} for every new block or scope.
|
||||
"eqeqeq": true, // Require triple equals i.e. `===`.
|
||||
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||
"latedef": true, // Prohibit variable use before definition.
|
||||
"newcap": true, // Require capitalization of all constructor functions e.g. `new F()`.
|
||||
"noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
|
||||
"quotmark": "single", // Define quotes to string values.
|
||||
"regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
|
||||
"undef": true, // Require all non-global variables be declared before they are used.
|
||||
"unused": false, // Warn unused variables.
|
||||
"strict": true, // Require `use strict` pragma in every file.
|
||||
"trailing": true, // Prohibit trailing whitespaces.
|
||||
"smarttabs": false, // Suppresses warnings about mixed tabs and spaces
|
||||
"globals": { // Globals variables.
|
||||
"angular": true,
|
||||
"io": true,
|
||||
"ApplicationConfiguration": true
|
||||
},
|
||||
"predef": [ // Extra globals.
|
||||
"inject",
|
||||
"by",
|
||||
"browser",
|
||||
"element"
|
||||
],
|
||||
"indent": 4, // Specify indentation spacing
|
||||
"devel": true, // Allow development statements e.g. `console.log();`.
|
||||
"noempty": true // Prohibit use of empty blocks.
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
/app/tests
|
||||
/app/tests
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.11"
|
||||
- "0.12"
|
||||
env:
|
||||
- NODE_ENV=travis
|
||||
services:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM dockerfile/nodejs
|
||||
FROM node:0.10
|
||||
|
||||
MAINTAINER Matthias Luebken, matthias@catalyst-zero.com
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN bower install --config.interactive=false --allow-root
|
||||
# Make everything available for start
|
||||
ADD . /home/mean
|
||||
|
||||
# currently only works for development
|
||||
# Set development environment as default
|
||||
ENV NODE_ENV development
|
||||
|
||||
# Port 3000 for server
|
||||
|
||||
76
README.md
76
README.md
@@ -1,8 +1,14 @@
|
||||
[](http://meanjs.org/)
|
||||
|
||||
[](https://gitter.im/meanjs/mean?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Master Branch:
|
||||
[](https://travis-ci.org/meanjs/mean)
|
||||
[](https://david-dm.org/meanjs/mean)
|
||||
[](https://gitter.im/meanjs/mean?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Dev Branch:
|
||||
[](https://travis-ci.org/meanjs/mean)
|
||||
[](https://david-dm.org/meanjs/mean/0.4.1)
|
||||
|
||||
MEAN.JS is a full-stack JavaScript open-source solution, which provides a solid starting point for [MongoDB](http://www.mongodb.org/), [Node.js](http://www.nodejs.org/), [Express](http://expressjs.com/), and [AngularJS](http://angularjs.org/) based applications. The idea is to solve the common issues with connecting those frameworks, build a robust framework to support daily development needs, and help developers use better practices while working with popular JavaScript components.
|
||||
|
||||
@@ -33,23 +39,27 @@ $ npm install -g grunt-cli
|
||||
## Downloading MEAN.JS
|
||||
There are several ways you can get the MEAN.JS boilerplate:
|
||||
|
||||
### Yo Generator
|
||||
The recommended way would be to use the [Official Yo Generator](http://meanjs.org/generator.html), which generates the latest stable copy of the MEAN.JS boilerplate and supplies multiple sub-generators to ease your daily development cycles.
|
||||
|
||||
### Cloning The GitHub Repository
|
||||
You can also use Git to directly clone the MEAN.JS repository:
|
||||
The recommended way to get MEAN.js is to use git to directly clone the MEAN.JS repository:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/meanjs/mean.git meanjs
|
||||
```
|
||||
|
||||
This will clone the latest version of the MEAN.JS repository to a **meanjs** folder.
|
||||
|
||||
### Downloading The Repository Zip File
|
||||
Another way to use the MEAN.JS boilerplate is to download a zip copy from the [master branch on GitHub](https://github.com/meanjs/mean/archive/master.zip). You can also do this using `wget` command:
|
||||
|
||||
```bash
|
||||
$ wget https://github.com/meanjs/mean/archive/master.zip -O meanjs.zip; unzip meanjs.zip; rm meanjs.zip
|
||||
```
|
||||
|
||||
Don't forget to rename **mean-master** after your project name.
|
||||
|
||||
### Yo Generator
|
||||
-Another way would be to use the [Official Yo Generator](http://meanjs.org/generator.html), which generates a copy of the MEAN.JS 0.3.x boilerplate and supplies multiple sub-generators to ease your daily development cycles.
|
||||
|
||||
## Quick Install
|
||||
Once you've downloaded the boilerplate and installed all the prerequisites, you're just a few steps away from starting to develop your MEAN application.
|
||||
|
||||
@@ -64,24 +74,50 @@ $ 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
|
||||
```
|
||||
|
||||
Your application should run on port 3000, so in your browser just go to [http://localhost:3000](http://localhost:3000)
|
||||
Your application should run on port 3000 with the *development* environment configuration, so in your browser just go to [http://localhost:3000](http://localhost:3000)
|
||||
|
||||
That's it! Your application should be running. To proceed with your development, check the other sections in this documentation.
|
||||
If you encounter any problems, try the Troubleshooting section.
|
||||
|
||||
* explore `config/env/development.js` for development environment configuration options
|
||||
|
||||
### Running in Production mode
|
||||
To run your application with *production* environment configuration, execute grunt as follows:
|
||||
|
||||
```bash
|
||||
$ grunt prod
|
||||
```
|
||||
|
||||
* explore `config/env/production.js` for production environment configuration options
|
||||
|
||||
### Running with TLS (SSL)
|
||||
Application will start by default with secure configuration (SSL mode) turned on and listen on port 8443.
|
||||
To run your application in a secure manner you'll need to use OpenSSL and generate a set of self-signed certificates. Unix-based users can use the following command:
|
||||
|
||||
```bash
|
||||
$ sh ./scripts/generate-ssl-certs.sh
|
||||
```
|
||||
|
||||
Windows users can follow instructions found [here](http://www.websense.com/support/article/kbarticle/How-to-use-OpenSSL-and-Microsoft-Certification-Authority).
|
||||
After you've generated the key and certificate, place them in the *config/sslcerts* folder.
|
||||
|
||||
Finally, execute grunt's prod task `grunt prod`
|
||||
* enable/disable SSL mode in production environment change the `secure` option in `config/env/production.js`
|
||||
|
||||
|
||||
## Testing Your Application
|
||||
You can run the full test suite included with MEAN.JS with the test task:
|
||||
|
||||
```
|
||||
```bash
|
||||
$ grunt test
|
||||
```
|
||||
|
||||
@@ -89,24 +125,24 @@ This will run both the server-side tests (located in the app/tests/ directory) a
|
||||
|
||||
To execute only the server tests, run the test:server task:
|
||||
|
||||
```
|
||||
```bash
|
||||
$ grunt test:server
|
||||
```
|
||||
|
||||
And to run only the client tests, run the test:client task:
|
||||
|
||||
```
|
||||
```bash
|
||||
$ grunt test:client
|
||||
```
|
||||
|
||||
## Development and deployment With Docker
|
||||
|
||||
* Install [Docker](http://www.docker.com/)
|
||||
* Install [Fig](https://github.com/orchardup/fig)
|
||||
* Install [Docker](https://docs.docker.com/installation/#installation)
|
||||
* Install [Compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
* Local development and testing with fig:
|
||||
* Local development and testing with compose:
|
||||
```bash
|
||||
$ fig up
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
* Local development and testing with just Docker:
|
||||
@@ -122,14 +158,6 @@ $
|
||||
$ docker run -p 3000:3000 -p 35729:35729 -v /Users/mdl/workspace/mean-stack/mean/public:/home/mean/public -v /Users/mdl/workspace/mean-stack/mean/app:/home/mean/app --link db:db_1 mean
|
||||
```
|
||||
|
||||
## Running in a secure environment
|
||||
To run your application in a secure manner you'll need to use OpenSSL and generate a set of self-signed certificates. Unix-based users can use the following command:
|
||||
```bash
|
||||
$ sh ./scripts/generate-ssl-certs.sh
|
||||
```
|
||||
Windows users can follow instructions found [here](http://www.websense.com/support/article/kbarticle/How-to-use-OpenSSL-and-Microsoft-Certification-Authority).
|
||||
After you've generated the key and certificate, place them in the *config/sslcerts* folder.
|
||||
|
||||
## Getting Started With MEAN.JS
|
||||
You have your application running, but there is a lot of stuff to understand. We recommend you go over the [Official Documentation](http://meanjs.org/docs.html).
|
||||
In the docs we'll try to explain both general concepts of MEAN components and give you some guidelines to help you improve your development process. We tried covering as many aspects as possible, and will keep it updated by your request. You can also help us develop and improve the documentation by checking out the *gh-pages* branch of this repository.
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
errorHandler = require('./errors.server.controller'),
|
||||
Article = mongoose.model('Article'),
|
||||
_ = require('lodash');
|
||||
|
||||
/**
|
||||
* Create a article
|
||||
*/
|
||||
exports.create = function(req, res) {
|
||||
var article = new Article(req.body);
|
||||
article.user = req.user;
|
||||
|
||||
article.save(function(err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
res.json(article);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the current article
|
||||
*/
|
||||
exports.read = function(req, res) {
|
||||
res.json(req.article);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a article
|
||||
*/
|
||||
exports.update = function(req, res) {
|
||||
var article = req.article;
|
||||
|
||||
article = _.extend(article, req.body);
|
||||
|
||||
article.save(function(err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
res.json(article);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete an article
|
||||
*/
|
||||
exports.delete = function(req, res) {
|
||||
var article = req.article;
|
||||
|
||||
article.remove(function(err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
res.json(article);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* List of Articles
|
||||
*/
|
||||
exports.list = function(req, res) {
|
||||
Article.find().sort('-created').populate('user', 'displayName').exec(function(err, articles) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
res.json(articles);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Article middleware
|
||||
*/
|
||||
exports.articleByID = function(req, res, next, id) {
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).send({
|
||||
message: 'Article is invalid'
|
||||
});
|
||||
}
|
||||
|
||||
Article.findById(id).populate('user', 'displayName').exec(function(err, article) {
|
||||
if (err) return next(err);
|
||||
if (!article) {
|
||||
return res.status(404).send({
|
||||
message: 'Article not found'
|
||||
});
|
||||
}
|
||||
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();
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
exports.index = function(req, res) {
|
||||
res.render('index', {
|
||||
user: req.user || null,
|
||||
request: req
|
||||
});
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Get unique error field name
|
||||
*/
|
||||
var getUniqueErrorMessage = function(err) {
|
||||
var output;
|
||||
|
||||
try {
|
||||
var fieldName = err.err.substring(err.err.lastIndexOf('.$') + 2, err.err.lastIndexOf('_1'));
|
||||
output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists';
|
||||
|
||||
} catch (ex) {
|
||||
output = 'Unique field already exists';
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the error message from error object
|
||||
*/
|
||||
exports.getErrorMessage = function(err) {
|
||||
var message = '';
|
||||
|
||||
if (err.code) {
|
||||
switch (err.code) {
|
||||
case 11000:
|
||||
case 11001:
|
||||
message = getUniqueErrorMessage(err);
|
||||
break;
|
||||
default:
|
||||
message = 'Something went wrong';
|
||||
}
|
||||
} else {
|
||||
for (var errName in err.errors) {
|
||||
if (err.errors[errName].message) message = err.errors[errName].message;
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Extend user's controller
|
||||
*/
|
||||
module.exports = _.extend(
|
||||
require('./users/users.authentication.server.controller'),
|
||||
require('./users/users.authorization.server.controller'),
|
||||
require('./users/users.password.server.controller'),
|
||||
require('./users/users.profile.server.controller')
|
||||
);
|
||||
@@ -1,206 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
errorHandler = require('../errors.server.controller'),
|
||||
mongoose = require('mongoose'),
|
||||
passport = require('passport'),
|
||||
User = mongoose.model('User');
|
||||
|
||||
/**
|
||||
* Signup
|
||||
*/
|
||||
exports.signup = function(req, res) {
|
||||
// For security measurement we remove the roles from the req.body object
|
||||
delete req.body.roles;
|
||||
|
||||
// Init Variables
|
||||
var user = new User(req.body);
|
||||
var message = null;
|
||||
|
||||
// Add missing user fields
|
||||
user.provider = 'local';
|
||||
user.displayName = user.firstName + ' ' + user.lastName;
|
||||
|
||||
// Then save the user
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
// Remove sensitive data before login
|
||||
user.password = undefined;
|
||||
user.salt = undefined;
|
||||
|
||||
req.login(user, function(err) {
|
||||
if (err) {
|
||||
res.status(400).send(err);
|
||||
} else {
|
||||
res.json(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Signin after passport authentication
|
||||
*/
|
||||
exports.signin = function(req, res, next) {
|
||||
passport.authenticate('local', function(err, user, info) {
|
||||
if (err || !user) {
|
||||
res.status(400).send(info);
|
||||
} else {
|
||||
// Remove sensitive data before login
|
||||
user.password = undefined;
|
||||
user.salt = undefined;
|
||||
|
||||
req.login(user, function(err) {
|
||||
if (err) {
|
||||
res.status(400).send(err);
|
||||
} else {
|
||||
res.json(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
})(req, res, next);
|
||||
};
|
||||
|
||||
/**
|
||||
* Signout
|
||||
*/
|
||||
exports.signout = function(req, res) {
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
};
|
||||
|
||||
/**
|
||||
* OAuth callback
|
||||
*/
|
||||
exports.oauthCallback = function(strategy) {
|
||||
return function(req, res, next) {
|
||||
passport.authenticate(strategy, function(err, user, redirectURL) {
|
||||
if (err || !user) {
|
||||
return res.redirect('/#!/signin');
|
||||
}
|
||||
req.login(user, function(err) {
|
||||
if (err) {
|
||||
return res.redirect('/#!/signin');
|
||||
}
|
||||
|
||||
return res.redirect(redirectURL || '/');
|
||||
});
|
||||
})(req, res, next);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to save or update a OAuth user profile
|
||||
*/
|
||||
exports.saveOAuthUserProfile = function(req, providerUserProfile, done) {
|
||||
if (!req.user) {
|
||||
// Define a search query fields
|
||||
var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField;
|
||||
var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField;
|
||||
|
||||
// Define main provider search query
|
||||
var mainProviderSearchQuery = {};
|
||||
mainProviderSearchQuery.provider = providerUserProfile.provider;
|
||||
mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField];
|
||||
|
||||
// Define additional provider search query
|
||||
var additionalProviderSearchQuery = {};
|
||||
additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField];
|
||||
|
||||
// Define a search query to find existing user with current provider profile
|
||||
var searchQuery = {
|
||||
$or: [mainProviderSearchQuery, additionalProviderSearchQuery]
|
||||
};
|
||||
|
||||
User.findOne(searchQuery, function(err, user) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
} else {
|
||||
if (!user) {
|
||||
var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : '');
|
||||
|
||||
User.findUniqueUsername(possibleUsername, null, function(availableUsername) {
|
||||
user = new User({
|
||||
firstName: providerUserProfile.firstName,
|
||||
lastName: providerUserProfile.lastName,
|
||||
username: availableUsername,
|
||||
displayName: providerUserProfile.displayName,
|
||||
email: providerUserProfile.email,
|
||||
provider: providerUserProfile.provider,
|
||||
providerData: providerUserProfile.providerData
|
||||
});
|
||||
|
||||
// And save the user
|
||||
user.save(function(err) {
|
||||
return done(err, user);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return done(err, user);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// User is already logged in, join the provider data to the existing user
|
||||
var user = req.user;
|
||||
|
||||
// Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured
|
||||
if (user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) {
|
||||
// Add the provider data to the additional provider data field
|
||||
if (!user.additionalProvidersData) user.additionalProvidersData = {};
|
||||
user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData;
|
||||
|
||||
// Then tell mongoose that we've updated the additionalProvidersData field
|
||||
user.markModified('additionalProvidersData');
|
||||
|
||||
// And save the user
|
||||
user.save(function(err) {
|
||||
return done(err, user, '/#!/settings/accounts');
|
||||
});
|
||||
} else {
|
||||
return done(new Error('User is already connected using this provider'), user);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove OAuth provider
|
||||
*/
|
||||
exports.removeOAuthProvider = function(req, res, next) {
|
||||
var user = req.user;
|
||||
var provider = req.param('provider');
|
||||
|
||||
if (user && provider) {
|
||||
// Delete the additional provider
|
||||
if (user.additionalProvidersData[provider]) {
|
||||
delete user.additionalProvidersData[provider];
|
||||
|
||||
// Then tell mongoose that we've updated the additionalProvidersData field
|
||||
user.markModified('additionalProvidersData');
|
||||
}
|
||||
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
req.login(user, function(err) {
|
||||
if (err) {
|
||||
res.status(400).send(err);
|
||||
} else {
|
||||
res.json(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User');
|
||||
|
||||
/**
|
||||
* User middleware
|
||||
*/
|
||||
exports.userByID = function(req, res, next, id) {
|
||||
User.findById(id).exec(function(err, user) {
|
||||
if (err) return next(err);
|
||||
if (!user) return next(new Error('Failed to load User ' + id));
|
||||
req.profile = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Require login routing middleware
|
||||
*/
|
||||
exports.requiresLogin = function(req, res, next) {
|
||||
if (!req.isAuthenticated()) {
|
||||
return res.status(401).send({
|
||||
message: 'User is not logged in'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* User authorizations routing middleware
|
||||
*/
|
||||
exports.hasAuthorization = function(roles) {
|
||||
var _this = this;
|
||||
|
||||
return function(req, res, next) {
|
||||
_this.requiresLogin(req, res, function() {
|
||||
if (_.intersection(req.user.roles, roles).length) {
|
||||
return next();
|
||||
} else {
|
||||
return res.status(403).send({
|
||||
message: 'User is not authorized'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -1,249 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
errorHandler = require('../errors.server.controller'),
|
||||
mongoose = require('mongoose'),
|
||||
passport = require('passport'),
|
||||
User = mongoose.model('User'),
|
||||
config = require('../../../config/config'),
|
||||
nodemailer = require('nodemailer'),
|
||||
async = require('async'),
|
||||
crypto = require('crypto');
|
||||
|
||||
var smtpTransport = nodemailer.createTransport(config.mailer.options);
|
||||
|
||||
/**
|
||||
* Forgot for reset password (forgot POST)
|
||||
*/
|
||||
exports.forgot = function(req, res, next) {
|
||||
async.waterfall([
|
||||
// Generate random token
|
||||
function(done) {
|
||||
crypto.randomBytes(20, function(err, buffer) {
|
||||
var token = buffer.toString('hex');
|
||||
done(err, token);
|
||||
});
|
||||
},
|
||||
// Lookup user by username
|
||||
function(token, done) {
|
||||
if (req.body.username) {
|
||||
User.findOne({
|
||||
username: req.body.username
|
||||
}, '-salt -password', function(err, user) {
|
||||
if (!user) {
|
||||
return res.status(400).send({
|
||||
message: 'No account with that username has been found'
|
||||
});
|
||||
} else if (user.provider !== 'local') {
|
||||
return res.status(400).send({
|
||||
message: 'It seems like you signed up using your ' + user.provider + ' account'
|
||||
});
|
||||
} else {
|
||||
user.resetPasswordToken = token;
|
||||
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
|
||||
|
||||
user.save(function(err) {
|
||||
done(err, token, user);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res.status(400).send({
|
||||
message: 'Username field must not be blank'
|
||||
});
|
||||
}
|
||||
},
|
||||
function(token, user, done) {
|
||||
res.render('templates/reset-password-email', {
|
||||
name: user.displayName,
|
||||
appName: config.app.title,
|
||||
url: 'http://' + req.headers.host + '/auth/reset/' + token
|
||||
}, function(err, emailHTML) {
|
||||
done(err, emailHTML, user);
|
||||
});
|
||||
},
|
||||
// If valid email, send reset email using service
|
||||
function(emailHTML, user, done) {
|
||||
var mailOptions = {
|
||||
to: user.email,
|
||||
from: config.mailer.from,
|
||||
subject: 'Password Reset',
|
||||
html: emailHTML
|
||||
};
|
||||
smtpTransport.sendMail(mailOptions, function(err) {
|
||||
if (!err) {
|
||||
res.send({
|
||||
message: 'An email has been sent to ' + user.email + ' with further instructions.'
|
||||
});
|
||||
} else {
|
||||
return res.status(400).send({
|
||||
message: 'Failure sending email'
|
||||
});
|
||||
}
|
||||
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
if (err) return next(err);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset password GET from email token
|
||||
*/
|
||||
exports.validateResetToken = function(req, res) {
|
||||
User.findOne({
|
||||
resetPasswordToken: req.params.token,
|
||||
resetPasswordExpires: {
|
||||
$gt: Date.now()
|
||||
}
|
||||
}, function(err, user) {
|
||||
if (!user) {
|
||||
return res.redirect('/#!/password/reset/invalid');
|
||||
}
|
||||
|
||||
res.redirect('/#!/password/reset/' + req.params.token);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset password POST from email token
|
||||
*/
|
||||
exports.reset = function(req, res, next) {
|
||||
// Init Variables
|
||||
var passwordDetails = req.body;
|
||||
|
||||
async.waterfall([
|
||||
|
||||
function(done) {
|
||||
User.findOne({
|
||||
resetPasswordToken: req.params.token,
|
||||
resetPasswordExpires: {
|
||||
$gt: Date.now()
|
||||
}
|
||||
}, function(err, user) {
|
||||
if (!err && user) {
|
||||
if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
|
||||
user.password = passwordDetails.newPassword;
|
||||
user.resetPasswordToken = undefined;
|
||||
user.resetPasswordExpires = undefined;
|
||||
|
||||
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 {
|
||||
// Return authenticated user
|
||||
res.json(user);
|
||||
|
||||
done(err, user);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res.status(400).send({
|
||||
message: 'Passwords do not match'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return res.status(400).send({
|
||||
message: 'Password reset token is invalid or has expired.'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
function(user, done) {
|
||||
res.render('templates/reset-password-confirm-email', {
|
||||
name: user.displayName,
|
||||
appName: config.app.title
|
||||
}, function(err, emailHTML) {
|
||||
done(err, emailHTML, user);
|
||||
});
|
||||
},
|
||||
// If valid email, send reset email using service
|
||||
function(emailHTML, user, done) {
|
||||
var mailOptions = {
|
||||
to: user.email,
|
||||
from: config.mailer.from,
|
||||
subject: 'Your password has been changed',
|
||||
html: emailHTML
|
||||
};
|
||||
|
||||
smtpTransport.sendMail(mailOptions, function(err) {
|
||||
done(err, 'done');
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
if (err) return next(err);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Change Password
|
||||
*/
|
||||
exports.changePassword = function(req, res) {
|
||||
// Init Variables
|
||||
var passwordDetails = req.body;
|
||||
|
||||
if (req.user) {
|
||||
if (passwordDetails.newPassword) {
|
||||
User.findById(req.user.id, function(err, user) {
|
||||
if (!err && user) {
|
||||
if (user.authenticate(passwordDetails.currentPassword)) {
|
||||
if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
|
||||
user.password = passwordDetails.newPassword;
|
||||
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
req.login(user, function(err) {
|
||||
if (err) {
|
||||
res.status(400).send(err);
|
||||
} else {
|
||||
res.send({
|
||||
message: 'Password changed successfully'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).send({
|
||||
message: 'Passwords do not match'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
message: 'Current password is incorrect'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
message: 'User is not found'
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).send({
|
||||
message: 'Please provide a new password'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
message: 'User is not signed in'
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
errorHandler = require('../errors.server.controller.js'),
|
||||
mongoose = require('mongoose'),
|
||||
passport = require('passport'),
|
||||
User = mongoose.model('User');
|
||||
|
||||
/**
|
||||
* Update user details
|
||||
*/
|
||||
exports.update = function(req, res) {
|
||||
// Init Variables
|
||||
var user = req.user;
|
||||
var message = null;
|
||||
|
||||
// For security measurement we remove the roles from the req.body object
|
||||
delete req.body.roles;
|
||||
|
||||
if (user) {
|
||||
// Merge existing user
|
||||
user = _.extend(user, req.body);
|
||||
user.updated = Date.now();
|
||||
user.displayName = user.firstName + ' ' + user.lastName;
|
||||
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
req.login(user, function(err) {
|
||||
if (err) {
|
||||
res.status(400).send(err);
|
||||
} else {
|
||||
res.json(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).send({
|
||||
message: 'User is not signed in'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send User
|
||||
*/
|
||||
exports.me = function(req, res) {
|
||||
res.json(req.user || null);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema;
|
||||
|
||||
/**
|
||||
* Article Schema
|
||||
*/
|
||||
var ArticleSchema = new Schema({
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
trim: true,
|
||||
required: 'Title cannot be blank'
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
trim: true
|
||||
},
|
||||
user: {
|
||||
type: Schema.ObjectId,
|
||||
ref: 'User'
|
||||
}
|
||||
});
|
||||
|
||||
mongoose.model('Article', ArticleSchema);
|
||||
@@ -1,146 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema,
|
||||
crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* A Validation function for local strategy properties
|
||||
*/
|
||||
var validateLocalStrategyProperty = function(property) {
|
||||
return ((this.provider !== 'local' && !this.updated) || property.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* A Validation function for local strategy password
|
||||
*/
|
||||
var validateLocalStrategyPassword = function(password) {
|
||||
return (this.provider !== 'local' || (password && password.length > 6));
|
||||
};
|
||||
|
||||
/**
|
||||
* User Schema
|
||||
*/
|
||||
var UserSchema = new Schema({
|
||||
firstName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: '',
|
||||
validate: [validateLocalStrategyProperty, 'Please fill in your first name']
|
||||
},
|
||||
lastName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: '',
|
||||
validate: [validateLocalStrategyProperty, 'Please fill in your last name']
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
trim: true
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: '',
|
||||
validate: [validateLocalStrategyProperty, 'Please fill in your email'],
|
||||
match: [/.+\@.+\..+/, 'Please fill a valid email address']
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
unique: 'Username already exists',
|
||||
required: 'Please fill in a username',
|
||||
trim: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
default: '',
|
||||
validate: [validateLocalStrategyPassword, 'Password should be longer']
|
||||
},
|
||||
salt: {
|
||||
type: String
|
||||
},
|
||||
provider: {
|
||||
type: String,
|
||||
required: 'Provider is required'
|
||||
},
|
||||
providerData: {},
|
||||
additionalProvidersData: {},
|
||||
roles: {
|
||||
type: [{
|
||||
type: String,
|
||||
enum: ['user', 'admin']
|
||||
}],
|
||||
default: ['user']
|
||||
},
|
||||
updated: {
|
||||
type: Date
|
||||
},
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
/* For reset password */
|
||||
resetPasswordToken: {
|
||||
type: String
|
||||
},
|
||||
resetPasswordExpires: {
|
||||
type: Date
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Hook a pre save method to hash the password
|
||||
*/
|
||||
UserSchema.pre('save', function(next) {
|
||||
if (this.password && this.password.length > 6) {
|
||||
this.salt = crypto.randomBytes(16).toString('base64');
|
||||
this.password = this.hashPassword(this.password);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
/**
|
||||
* Create instance method for hashing a password
|
||||
*/
|
||||
UserSchema.methods.hashPassword = function(password) {
|
||||
if (this.salt && password) {
|
||||
return crypto.pbkdf2Sync(password, new Buffer(this.salt, 'base64'), 10000, 64).toString('base64');
|
||||
} else {
|
||||
return password;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create instance method for authenticating user
|
||||
*/
|
||||
UserSchema.methods.authenticate = function(password) {
|
||||
return this.password === this.hashPassword(password);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find possible not used username
|
||||
*/
|
||||
UserSchema.statics.findUniqueUsername = function(username, suffix, callback) {
|
||||
var _this = this;
|
||||
var possibleUsername = username + (suffix || '');
|
||||
|
||||
_this.findOne({
|
||||
username: possibleUsername
|
||||
}, function(err, user) {
|
||||
if (!err) {
|
||||
if (!user) {
|
||||
callback(possibleUsername);
|
||||
} else {
|
||||
return _this.findUniqueUsername(username, (suffix || 0) + 1, callback);
|
||||
}
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
mongoose.model('User', UserSchema);
|
||||
@@ -1,22 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var users = require('../../app/controllers/users.server.controller'),
|
||||
articles = require('../../app/controllers/articles.server.controller');
|
||||
|
||||
module.exports = function(app) {
|
||||
// Article Routes
|
||||
app.route('/articles')
|
||||
.get(articles.list)
|
||||
.post(users.requiresLogin, articles.create);
|
||||
|
||||
app.route('/articles/:articleId')
|
||||
.get(articles.read)
|
||||
.put(users.requiresLogin, articles.hasAuthorization, articles.update)
|
||||
.delete(users.requiresLogin, articles.hasAuthorization, articles.delete);
|
||||
|
||||
// Finish by binding the article middleware
|
||||
app.param('articleId', articles.articleByID);
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(app) {
|
||||
// Root routing
|
||||
var core = require('../../app/controllers/core.server.controller');
|
||||
app.route('/').get(core.index);
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport');
|
||||
|
||||
module.exports = function(app) {
|
||||
// User Routes
|
||||
var users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
// Setting up the users profile api
|
||||
app.route('/users/me').get(users.me);
|
||||
app.route('/users').put(users.update);
|
||||
app.route('/users/accounts').delete(users.removeOAuthProvider);
|
||||
|
||||
// Setting up the users password api
|
||||
app.route('/users/password').post(users.changePassword);
|
||||
app.route('/auth/forgot').post(users.forgot);
|
||||
app.route('/auth/reset/:token').get(users.validateResetToken);
|
||||
app.route('/auth/reset/:token').post(users.reset);
|
||||
|
||||
// Setting up the users authentication api
|
||||
app.route('/auth/signup').post(users.signup);
|
||||
app.route('/auth/signin').post(users.signin);
|
||||
app.route('/auth/signout').get(users.signout);
|
||||
|
||||
// Setting the facebook oauth routes
|
||||
app.route('/auth/facebook').get(passport.authenticate('facebook', {
|
||||
scope: ['email']
|
||||
}));
|
||||
app.route('/auth/facebook/callback').get(users.oauthCallback('facebook'));
|
||||
|
||||
// Setting the twitter oauth routes
|
||||
app.route('/auth/twitter').get(passport.authenticate('twitter'));
|
||||
app.route('/auth/twitter/callback').get(users.oauthCallback('twitter'));
|
||||
|
||||
// Setting the google oauth routes
|
||||
app.route('/auth/google').get(passport.authenticate('google', {
|
||||
scope: [
|
||||
'https://www.googleapis.com/auth/userinfo.profile',
|
||||
'https://www.googleapis.com/auth/userinfo.email'
|
||||
]
|
||||
}));
|
||||
app.route('/auth/google/callback').get(users.oauthCallback('google'));
|
||||
|
||||
// Setting the linkedin oauth routes
|
||||
app.route('/auth/linkedin').get(passport.authenticate('linkedin'));
|
||||
app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin'));
|
||||
|
||||
// Setting the github oauth routes
|
||||
app.route('/auth/github').get(passport.authenticate('github'));
|
||||
app.route('/auth/github/callback').get(users.oauthCallback('github'));
|
||||
|
||||
// Finish by binding the user middleware
|
||||
app.param('userId', users.userByID);
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var should = require('should'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Article = mongoose.model('Article');
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
var user, article;
|
||||
|
||||
/**
|
||||
* Unit tests
|
||||
*/
|
||||
describe('Article Model Unit Tests:', function() {
|
||||
beforeEach(function(done) {
|
||||
user = new User({
|
||||
firstName: 'Full',
|
||||
lastName: 'Name',
|
||||
displayName: 'Full Name',
|
||||
email: 'test@test.com',
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
});
|
||||
|
||||
user.save(function() {
|
||||
article = new Article({
|
||||
title: 'Article Title',
|
||||
content: 'Article Content',
|
||||
user: user
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Method Save', function() {
|
||||
it('should be able to save without problems', function(done) {
|
||||
return article.save(function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to show an error when try to save without title', function(done) {
|
||||
article.title = '';
|
||||
|
||||
return article.save(function(err) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
Article.remove().exec(function() {
|
||||
User.remove().exec(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,280 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('should'),
|
||||
request = require('supertest'),
|
||||
app = require('../../server'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Article = mongoose.model('Article'),
|
||||
agent = request.agent(app);
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
var credentials, user, article;
|
||||
|
||||
/**
|
||||
* Article routes tests
|
||||
*/
|
||||
describe('Article CRUD tests', function() {
|
||||
beforeEach(function(done) {
|
||||
// Create user credentials
|
||||
credentials = {
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
};
|
||||
|
||||
// Create a new user
|
||||
user = new User({
|
||||
firstName: 'Full',
|
||||
lastName: 'Name',
|
||||
displayName: 'Full Name',
|
||||
email: 'test@test.com',
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
provider: 'local'
|
||||
});
|
||||
|
||||
// Save a user to the test db and create new article
|
||||
user.save(function() {
|
||||
article = {
|
||||
title: 'Article Title',
|
||||
content: 'Article Content'
|
||||
};
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to save an article if logged in', function(done) {
|
||||
agent.post('/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) done(signinErr);
|
||||
|
||||
// Get the userId
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
// Handle article save error
|
||||
if (articleSaveErr) done(articleSaveErr);
|
||||
|
||||
// Get a list of articles
|
||||
agent.get('/articles')
|
||||
.end(function(articlesGetErr, articlesGetRes) {
|
||||
// Handle article save error
|
||||
if (articlesGetErr) done(articlesGetErr);
|
||||
|
||||
// Get articles list
|
||||
var articles = articlesGetRes.body;
|
||||
|
||||
// Set assertions
|
||||
(articles[0].user._id).should.equal(userId);
|
||||
(articles[0].title).should.match('Article Title');
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to save an article if not logged in', function(done) {
|
||||
agent.post('/articles')
|
||||
.send(article)
|
||||
.expect(401)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
// Call the assertion callback
|
||||
done(articleSaveErr);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to save an article if no title is provided', function(done) {
|
||||
// Invalidate title field
|
||||
article.title = '';
|
||||
|
||||
agent.post('/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) done(signinErr);
|
||||
|
||||
// Get the userId
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/articles')
|
||||
.send(article)
|
||||
.expect(400)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
// Set message assertion
|
||||
(articleSaveRes.body.message).should.match('Title cannot be blank');
|
||||
|
||||
// Handle article save error
|
||||
done(articleSaveErr);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to update an article if signed in', function(done) {
|
||||
agent.post('/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) done(signinErr);
|
||||
|
||||
// Get the userId
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
// Handle article save error
|
||||
if (articleSaveErr) done(articleSaveErr);
|
||||
|
||||
// Update article title
|
||||
article.title = 'WHY YOU GOTTA BE SO MEAN?';
|
||||
|
||||
// Update an existing article
|
||||
agent.put('/articles/' + articleSaveRes.body._id)
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleUpdateErr, articleUpdateRes) {
|
||||
// Handle article update error
|
||||
if (articleUpdateErr) done(articleUpdateErr);
|
||||
|
||||
// Set assertions
|
||||
(articleUpdateRes.body._id).should.equal(articleSaveRes.body._id);
|
||||
(articleUpdateRes.body.title).should.match('WHY YOU GOTTA BE SO MEAN?');
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get a list of articles if not signed in', function(done) {
|
||||
// Create new article model instance
|
||||
var articleObj = new Article(article);
|
||||
|
||||
// Save the article
|
||||
articleObj.save(function() {
|
||||
// Request articles
|
||||
request(app).get('/articles')
|
||||
.end(function(req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.an.Array.with.lengthOf(1);
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to get a single article if not signed in', function(done) {
|
||||
// Create new article model instance
|
||||
var articleObj = new Article(article);
|
||||
|
||||
// Save the article
|
||||
articleObj.save(function() {
|
||||
request(app).get('/articles/' + articleObj._id)
|
||||
.end(function(req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.an.Object.with.property('title', article.title);
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return proper error for single article which doesnt exist, if not signed in', function(done) {
|
||||
request(app).get('/articles/test')
|
||||
.end(function(req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.an.Object.with.property('message', 'Article is invalid');
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to delete an article if signed in', function(done) {
|
||||
agent.post('/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) done(signinErr);
|
||||
|
||||
// Get the userId
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleSaveErr, articleSaveRes) {
|
||||
// Handle article save error
|
||||
if (articleSaveErr) done(articleSaveErr);
|
||||
|
||||
// Delete an existing article
|
||||
agent.delete('/articles/' + articleSaveRes.body._id)
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function(articleDeleteErr, articleDeleteRes) {
|
||||
// Handle article error error
|
||||
if (articleDeleteErr) done(articleDeleteErr);
|
||||
|
||||
// Set assertions
|
||||
(articleDeleteRes.body._id).should.equal(articleSaveRes.body._id);
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to delete an article if not signed in', function(done) {
|
||||
// Set article user
|
||||
article.user = user;
|
||||
|
||||
// Create new article model instance
|
||||
var articleObj = new Article(article);
|
||||
|
||||
// Save the article
|
||||
articleObj.save(function() {
|
||||
// Try deleting article
|
||||
request(app).delete('/articles/' + articleObj._id)
|
||||
.expect(401)
|
||||
.end(function(articleDeleteErr, articleDeleteRes) {
|
||||
// Set message assertion
|
||||
(articleDeleteRes.body.message).should.match('User is not logged in');
|
||||
|
||||
// Handle article error error
|
||||
done(articleDeleteErr);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
User.remove().exec(function() {
|
||||
Article.remove().exec(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var should = require('should'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User');
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
var user, user2;
|
||||
|
||||
/**
|
||||
* Unit tests
|
||||
*/
|
||||
describe('User Model Unit Tests:', function() {
|
||||
before(function(done) {
|
||||
user = new User({
|
||||
firstName: 'Full',
|
||||
lastName: 'Name',
|
||||
displayName: 'Full Name',
|
||||
email: 'test@test.com',
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
provider: 'local'
|
||||
});
|
||||
user2 = new User({
|
||||
firstName: 'Full',
|
||||
lastName: 'Name',
|
||||
displayName: 'Full Name',
|
||||
email: 'test@test.com',
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
provider: 'local'
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
describe('Method Save', function() {
|
||||
it('should begin with no users', function(done) {
|
||||
User.find({}, function(err, users) {
|
||||
users.should.have.length(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to save without problems', function(done) {
|
||||
user.save(done);
|
||||
});
|
||||
|
||||
it('should fail to save an existing user again', function(done) {
|
||||
user.save(function() {
|
||||
user2.save(function(err) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to show an error when try to save without first name', function(done) {
|
||||
user.firstName = '';
|
||||
return user.save(function(err) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
User.remove().exec(done);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
{% extends 'layout.server.view.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Page Not Found</h1>
|
||||
<pre>
|
||||
{{url}} is not a valid path.
|
||||
</pre>
|
||||
{% endblock %}
|
||||
@@ -1,81 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<title>{{title}}</title>
|
||||
|
||||
<!-- General META -->
|
||||
<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="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">
|
||||
|
||||
<!-- Semantic META -->
|
||||
<meta name="keywords" content="{{keywords}}">
|
||||
<meta name="description" content="{{description}}">
|
||||
|
||||
<!-- Facebook META -->
|
||||
<meta property="fb:app_id" content="{{facebookAppId}}">
|
||||
<meta property="og:site_name" content="{{title}}">
|
||||
<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: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">
|
||||
|
||||
<!-- 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 %}
|
||||
|
||||
<!-- HTML5 Shim -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body class="ng-cloak">
|
||||
<header data-ng-include="'/modules/core/views/header.client.view.html'" class="navbar navbar-fixed-top navbar-inverse"></header>
|
||||
<section class="content">
|
||||
<section class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!--Embedding The User Object-->
|
||||
<script type="text/javascript">
|
||||
var user = {{ user | json | safe }};
|
||||
</script>
|
||||
|
||||
<!--Application JavaScript Files-->
|
||||
{% 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>
|
||||
{% 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>
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<p>Dear {{name}},</p>
|
||||
<p></p>
|
||||
<p>This is a confirmation that the password for your account has just been changed</p>
|
||||
<br>
|
||||
<br>
|
||||
<p>The {{appName}} Support Team</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<p>Dear {{name}},</p>
|
||||
<br>
|
||||
<p>
|
||||
You have requested to have your password reset for your account at {{appName}}
|
||||
</p>
|
||||
<p>Please visit this url to reset your password:</p>
|
||||
<p>{{url}}</p>
|
||||
<strong>If you didn't make this request, you can ignore this email.</strong>
|
||||
<br>
|
||||
<br>
|
||||
<p>The {{appName}} Support Team</p>
|
||||
</body>
|
||||
</html>
|
||||
31
bower.json
31
bower.json
@@ -1,16 +1,19 @@
|
||||
{
|
||||
"name": "meanjs",
|
||||
"version": "0.3.2",
|
||||
"description": "Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.",
|
||||
"dependencies": {
|
||||
"bootstrap": "~3",
|
||||
"angular": "~1.2",
|
||||
"angular-resource": "~1.2",
|
||||
"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"
|
||||
}
|
||||
"name": "meanjs",
|
||||
"version": "0.4.0",
|
||||
"description": "Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.",
|
||||
"dependencies": {
|
||||
"bootstrap": "~3",
|
||||
"angular": "~1.3",
|
||||
"angular-resource": "~1.3",
|
||||
"angular-animate": "~1.3",
|
||||
"angular-mocks": "~1.3",
|
||||
"angular-bootstrap": "~0.13",
|
||||
"angular-ui-utils": "bower",
|
||||
"angular-ui-router": "~0.2",
|
||||
"angular-file-upload": "1.1.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "~1.3"
|
||||
}
|
||||
}
|
||||
|
||||
49
config/assets/default.js
Normal file
49
config/assets/default.js
Normal file
@@ -0,0 +1,49 @@
|
||||
'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: {
|
||||
gruntConfig: 'gruntfile.js',
|
||||
gulpConfig: 'gulpfile.js',
|
||||
allJS: ['server.js', 'config/**/*.js', 'modules/*/server/**/*.js'],
|
||||
models: 'modules/*/server/models/**/*.js',
|
||||
routes: ['modules/!(core)/server/routes/**/*.js', 'modules/core/server/routes/**/*.js'],
|
||||
sockets: 'modules/*/server/sockets/**/*.js',
|
||||
config: 'modules/*/server/config/*.js',
|
||||
policies: 'modules/*/server/policies/*.js',
|
||||
views: 'modules/*/server/views/*.html'
|
||||
}
|
||||
};
|
||||
5
config/assets/development.js
Normal file
5
config/assets/development.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
// Development assets
|
||||
};
|
||||
23
config/assets/production.js
Normal file
23
config/assets/production.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
client: {
|
||||
lib: {
|
||||
css: [
|
||||
'public/lib/bootstrap/dist/css/bootstrap.min.css',
|
||||
'public/lib/bootstrap/dist/css/bootstrap-theme.min.css',
|
||||
],
|
||||
js: [
|
||||
'public/lib/angular/angular.min.js',
|
||||
'public/lib/angular-resource/angular-resource.min.js',
|
||||
'public/lib/angular-animate/angular-animate.min.js',
|
||||
'public/lib/angular-ui-router/release/angular-ui-router.min.js',
|
||||
'public/lib/angular-ui-utils/ui-utils.min.js',
|
||||
'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js',
|
||||
'public/lib/angular-file-upload/angular-file-upload.min.js'
|
||||
]
|
||||
},
|
||||
css: 'public/dist/application.min.css',
|
||||
js: 'public/dist/application.min.js'
|
||||
}
|
||||
};
|
||||
9
config/assets/test.js
Normal file
9
config/assets/test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
tests: {
|
||||
client: ['modules/*/tests/client/**/*.js'],
|
||||
server: ['modules/*/tests/server/**/*.js'],
|
||||
e2e: ['modules/*/tests/e2e/**/*.js']
|
||||
}
|
||||
};
|
||||
232
config/config.js
232
config/config.js
@@ -4,87 +4,183 @@
|
||||
* 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('+ Error: No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead'));
|
||||
} else {
|
||||
console.error(chalk.red('+ Error: NODE_ENV is not defined! Using default development environment'));
|
||||
}
|
||||
process.env.NODE_ENV = 'development';
|
||||
}
|
||||
// Reset console color
|
||||
console.log(chalk.white(''));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modules CSS files
|
||||
* Validate Secure=true parameter can actually be turned on
|
||||
* because it requires certs and key files to be available
|
||||
*/
|
||||
module.exports.getCSSAssets = function() {
|
||||
var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/');
|
||||
return output;
|
||||
var validateSecureMode = function (config) {
|
||||
|
||||
if (config.secure !== true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var privateKey = fs.existsSync('./config/sslcerts/key.pem');
|
||||
var certificate = fs.existsSync('./config/sslcerts/cert.pem');
|
||||
|
||||
if (!privateKey || !certificate) {
|
||||
console.log(chalk.red('+ Error: Certificate file or key file is missing, falling back to non-SSL mode'));
|
||||
console.log(chalk.red(' To create them, simply run the following from your shell: sh ./scripts/generate-ssl-certs.sh'));
|
||||
console.log();
|
||||
config.secure = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize global configuration files
|
||||
*/
|
||||
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 = _.merge(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 = _.merge(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);
|
||||
|
||||
// Validate Secure SSL mode can be used
|
||||
validateSecureMode(config);
|
||||
|
||||
// Expose configuration utilities
|
||||
config.utils = {
|
||||
getGlobbedPaths: getGlobbedPaths
|
||||
};
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set configuration object
|
||||
*/
|
||||
module.exports = initGlobalConfig();
|
||||
|
||||
72
config/env/all.js
vendored
72
config/env/all.js
vendored
@@ -1,72 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
app: {
|
||||
title: 'MEAN.JS',
|
||||
description: 'Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js',
|
||||
keywords: 'mongodb, express, angularjs, node.js, mongoose, passport'
|
||||
},
|
||||
port: process.env.PORT || 3000,
|
||||
templateEngine: 'swig',
|
||||
// The secret should be set to a non-guessable string that
|
||||
// is used to compute a session hash
|
||||
sessionSecret: 'MEAN',
|
||||
// The name of the MongoDB collection to store sessions in
|
||||
sessionCollection: 'sessions',
|
||||
// The session cookie settings
|
||||
sessionCookie: {
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
// If secure is set to true then it will cause the cookie to be set
|
||||
// only when SSL-enabled (HTTPS) is used, and otherwise it won't
|
||||
// set a cookie. 'true' is recommended yet it requires the above
|
||||
// mentioned pre-requisite.
|
||||
secure: false,
|
||||
// Only set the maxAge to null if the cookie shouldn't be expired
|
||||
// at all. The cookie will expunge when the browser is closed.
|
||||
maxAge: null,
|
||||
// To set the cookie in a specific domain uncomment the following
|
||||
// setting:
|
||||
// domain: 'yourdomain.com'
|
||||
},
|
||||
// The session cookie name
|
||||
sessionName: 'connect.sid',
|
||||
log: {
|
||||
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
|
||||
format: 'combined',
|
||||
// Stream defaults to process.stdout
|
||||
// Uncomment to enable logging to a log on the file system
|
||||
options: {
|
||||
stream: 'access.log'
|
||||
}
|
||||
},
|
||||
assets: {
|
||||
lib: {
|
||||
css: [
|
||||
'public/lib/bootstrap/dist/css/bootstrap.css',
|
||||
'public/lib/bootstrap/dist/css/bootstrap-theme.css',
|
||||
],
|
||||
js: [
|
||||
'public/lib/angular/angular.js',
|
||||
'public/lib/angular-resource/angular-resource.js',
|
||||
'public/lib/angular-animate/angular-animate.js',
|
||||
'public/lib/angular-ui-router/release/angular-ui-router.js',
|
||||
'public/lib/angular-ui-utils/ui-utils.js',
|
||||
'public/lib/angular-bootstrap/ui-bootstrap-tpls.js'
|
||||
]
|
||||
},
|
||||
css: [
|
||||
'public/modules/**/css/*.css'
|
||||
],
|
||||
js: [
|
||||
'public/config.js',
|
||||
'public/application.js',
|
||||
'public/modules/*/*.js',
|
||||
'public/modules/*/*[!tests]*/*.js'
|
||||
],
|
||||
tests: [
|
||||
'public/lib/angular-mocks/angular-mocks.js',
|
||||
'public/modules/*/tests/*.js'
|
||||
]
|
||||
}
|
||||
};
|
||||
16
config/env/default.js
vendored
Normal file
16
config/env/default.js
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
'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',
|
||||
logo: 'modules/core/img/brand/logo.png',
|
||||
favicon: 'modules/core/img/brand/favicon.ico'
|
||||
};
|
||||
119
config/env/development.js
vendored
119
config/env/development.js
vendored
@@ -1,58 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
var defaultEnvConfig = require('./default');
|
||||
|
||||
module.exports = {
|
||||
db: {
|
||||
uri: 'mongodb://localhost/mean-dev',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
},
|
||||
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 - 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'
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
db: {
|
||||
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean-dev',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
},
|
||||
// Enable mongoose debug mode
|
||||
debug: process.env.MONGODB_DEBUG || false
|
||||
},
|
||||
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: defaultEnvConfig.app.title + ' - Development Environment'
|
||||
},
|
||||
facebook: {
|
||||
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'
|
||||
},
|
||||
paypal: {
|
||||
clientID: process.env.PAYPAL_ID || 'CLIENT_ID',
|
||||
clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET',
|
||||
callbackURL: '/api/auth/paypal/callback',
|
||||
sandbox: true
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
||||
},
|
||||
livereload: true
|
||||
};
|
||||
|
||||
26
config/env/local.example.js
vendored
26
config/env/local.example.js
vendored
@@ -7,17 +7,17 @@
|
||||
/* For example:
|
||||
|
||||
module.exports = {
|
||||
db: {
|
||||
uri: 'mongodb://localhost/local-dev',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/facebook/callback'
|
||||
}
|
||||
db: {
|
||||
uri: 'mongodb://localhost/local-dev',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/facebook/callback'
|
||||
}
|
||||
};
|
||||
*/
|
||||
*/
|
||||
|
||||
130
config/env/production.js
vendored
130
config/env/production.js
vendored
@@ -1,73 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
db: {
|
||||
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || '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: '/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'
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
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: {
|
||||
user: '',
|
||||
pass: ''
|
||||
},
|
||||
// Enable mongoose debug mode
|
||||
debug: process.env.MONGODB_DEBUG || false
|
||||
},
|
||||
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'
|
||||
}
|
||||
},
|
||||
facebook: {
|
||||
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'
|
||||
},
|
||||
paypal: {
|
||||
clientID: process.env.PAYPAL_ID || 'CLIENT_ID',
|
||||
clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET',
|
||||
callbackURL: '/api/auth/paypal/callback',
|
||||
sandbox: false
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
74
config/env/secure.js
vendored
74
config/env/secure.js
vendored
@@ -1,74 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
port: 8443,
|
||||
db: {
|
||||
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
},
|
||||
log: {
|
||||
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
|
||||
format: 'combined',
|
||||
// Stream defaults to process.stdout
|
||||
// Uncomment to enable logging to a log on the file system
|
||||
options: {
|
||||
stream: 'access.log'
|
||||
}
|
||||
},
|
||||
assets: {
|
||||
lib: {
|
||||
css: [
|
||||
'public/lib/bootstrap/dist/css/bootstrap.min.css',
|
||||
'public/lib/bootstrap/dist/css/bootstrap-theme.min.css',
|
||||
],
|
||||
js: [
|
||||
'public/lib/angular/angular.min.js',
|
||||
'public/lib/angular-resource/angular-resource.min.js',
|
||||
'public/lib/angular-animate/angular-animate.min.js',
|
||||
'public/lib/angular-ui-router/release/angular-ui-router.min.js',
|
||||
'public/lib/angular-ui-utils/ui-utils.min.js',
|
||||
'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js'
|
||||
]
|
||||
},
|
||||
css: 'public/dist/application.min.css',
|
||||
js: 'public/dist/application.min.js'
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/facebook/callback'
|
||||
},
|
||||
twitter: {
|
||||
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
|
||||
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/twitter/callback'
|
||||
},
|
||||
google: {
|
||||
clientID: process.env.GOOGLE_ID || 'APP_ID',
|
||||
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/google/callback'
|
||||
},
|
||||
linkedin: {
|
||||
clientID: process.env.LINKEDIN_ID || 'APP_ID',
|
||||
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/linkedin/callback'
|
||||
},
|
||||
github: {
|
||||
clientID: process.env.GITHUB_ID || 'APP_ID',
|
||||
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/github/callback'
|
||||
},
|
||||
mailer: {
|
||||
from: process.env.MAILER_FROM || 'MAILER_FROM',
|
||||
options: {
|
||||
service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER',
|
||||
auth: {
|
||||
user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID',
|
||||
pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
111
config/env/test.js
vendored
111
config/env/test.js
vendored
@@ -1,59 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
var defaultEnvConfig = require('./default');
|
||||
|
||||
module.exports = {
|
||||
db: {
|
||||
uri: 'mongodb://localhost/mean-test',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
},
|
||||
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'
|
||||
},
|
||||
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'
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
db: {
|
||||
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean-test',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
},
|
||||
// Enable mongoose debug mode
|
||||
debug: process.env.MONGODB_DEBUG || false
|
||||
},
|
||||
port: process.env.PORT || 3001,
|
||||
app: {
|
||||
title: defaultEnvConfig.app.title + ' - Test Environment'
|
||||
},
|
||||
facebook: {
|
||||
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'
|
||||
},
|
||||
paypal: {
|
||||
clientID: process.env.PAYPAL_ID || 'CLIENT_ID',
|
||||
clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET',
|
||||
callbackURL: '/api/auth/paypal/callback',
|
||||
sandbox: true
|
||||
},
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var fs = require('fs'),
|
||||
http = require('http'),
|
||||
https = require('https'),
|
||||
express = require('express'),
|
||||
morgan = require('morgan'),
|
||||
logger = require('./logger'),
|
||||
bodyParser = require('body-parser'),
|
||||
session = require('express-session'),
|
||||
compression = require('compression'),
|
||||
methodOverride = require('method-override'),
|
||||
cookieParser = require('cookie-parser'),
|
||||
helmet = require('helmet'),
|
||||
passport = require('passport'),
|
||||
mongoStore = require('connect-mongo')({
|
||||
session: session
|
||||
}),
|
||||
flash = require('connect-flash'),
|
||||
config = require('./config'),
|
||||
consolidate = require('consolidate'),
|
||||
path = require('path');
|
||||
|
||||
module.exports = function(db) {
|
||||
// Initialize express app
|
||||
var app = express();
|
||||
|
||||
// Globbing model files
|
||||
config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
|
||||
require(path.resolve(modelPath));
|
||||
});
|
||||
|
||||
// Setting application local variables
|
||||
app.locals.title = config.app.title;
|
||||
app.locals.description = config.app.description;
|
||||
app.locals.keywords = config.app.keywords;
|
||||
app.locals.facebookAppId = config.facebook.clientID;
|
||||
app.locals.jsFiles = config.getJavaScriptAssets();
|
||||
app.locals.cssFiles = config.getCSSAssets();
|
||||
|
||||
// Passing the request url to environment locals
|
||||
app.use(function(req, res, next) {
|
||||
res.locals.url = req.protocol + '://' + req.headers.host + req.url;
|
||||
next();
|
||||
});
|
||||
|
||||
// Should be placed before express.static
|
||||
app.use(compression({
|
||||
// only compress files for the following content types
|
||||
filter: function(req, res) {
|
||||
return (/json|text|javascript|css/).test(res.getHeader('Content-Type'));
|
||||
},
|
||||
// zlib option for compression level
|
||||
level: 3
|
||||
}));
|
||||
|
||||
// Showing stack errors
|
||||
app.set('showStackError', true);
|
||||
|
||||
// Set swig as the template engine
|
||||
app.engine('server.view.html', consolidate[config.templateEngine]);
|
||||
|
||||
// Set views path and view engine
|
||||
app.set('view engine', 'server.view.html');
|
||||
app.set('views', './app/views');
|
||||
|
||||
// Enable logger (morgan)
|
||||
app.use(morgan(logger.getLogFormat(), logger.getLogOptions()));
|
||||
|
||||
// Environment dependent middleware
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Disable views cache
|
||||
app.set('view cache', false);
|
||||
} else if (process.env.NODE_ENV === 'production') {
|
||||
app.locals.cache = 'memory';
|
||||
}
|
||||
|
||||
// Request body parsing middleware should be above methodOverride
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
app.use(bodyParser.json());
|
||||
app.use(methodOverride());
|
||||
|
||||
// Use helmet to secure Express headers
|
||||
app.use(helmet.xframe());
|
||||
app.use(helmet.xssFilter());
|
||||
app.use(helmet.nosniff());
|
||||
app.use(helmet.ienoopen());
|
||||
app.disable('x-powered-by');
|
||||
|
||||
// Setting the app router and static folder
|
||||
app.use(express.static(path.resolve('./public')));
|
||||
|
||||
// CookieParser should be above session
|
||||
app.use(cookieParser());
|
||||
|
||||
// Express MongoDB session storage
|
||||
app.use(session({
|
||||
saveUninitialized: true,
|
||||
resave: true,
|
||||
secret: config.sessionSecret,
|
||||
store: new mongoStore({
|
||||
db: db.connection.db,
|
||||
collection: config.sessionCollection
|
||||
}),
|
||||
cookie: config.sessionCookie,
|
||||
name: config.sessionName
|
||||
}));
|
||||
|
||||
// use passport session
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// connect flash for flash messages
|
||||
app.use(flash());
|
||||
|
||||
// Globbing routing files
|
||||
config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) {
|
||||
require(path.resolve(routePath))(app);
|
||||
});
|
||||
|
||||
// Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc.
|
||||
app.use(function(err, req, res, next) {
|
||||
// If the error object doesn't exists
|
||||
if (!err) return next();
|
||||
|
||||
// Log it
|
||||
console.error(err.stack);
|
||||
|
||||
// Error page
|
||||
res.status(500).render('500', {
|
||||
error: err.stack
|
||||
});
|
||||
});
|
||||
|
||||
// Assume 404 since no middleware responded
|
||||
app.use(function(req, res) {
|
||||
res.status(404).render('404', {
|
||||
url: req.originalUrl,
|
||||
error: 'Not Found'
|
||||
});
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'secure') {
|
||||
// Load SSL key and certificate
|
||||
var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8');
|
||||
var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8');
|
||||
|
||||
// Create HTTPS Server
|
||||
var httpsServer = https.createServer({
|
||||
key: privateKey,
|
||||
cert: certificate
|
||||
}, app);
|
||||
|
||||
// Return HTTPS server instance
|
||||
return httpsServer;
|
||||
}
|
||||
|
||||
// Return Express server instance
|
||||
return app;
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var glob = require('glob'),
|
||||
chalk = require('chalk');
|
||||
|
||||
/**
|
||||
* Module init function.
|
||||
*/
|
||||
module.exports = function() {
|
||||
/**
|
||||
* Before we begin, lets set the environment variable
|
||||
* We'll Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV
|
||||
*/
|
||||
glob('./config/env/' + process.env.NODE_ENV + '.js', {
|
||||
sync: true
|
||||
}, function(err, environmentFiles) {
|
||||
if (!environmentFiles.length) {
|
||||
if (process.env.NODE_ENV) {
|
||||
console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead'));
|
||||
} else {
|
||||
console.error(chalk.red('NODE_ENV is not defined! Using default development environment'));
|
||||
}
|
||||
|
||||
process.env.NODE_ENV = 'development';
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
52
config/lib/app.js
Normal file
52
config/lib/app.js
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var config = require('../config'),
|
||||
mongoose = require('./mongoose'),
|
||||
express = require('./express'),
|
||||
chalk = require('chalk');
|
||||
|
||||
// Initialize Models
|
||||
mongoose.loadModels();
|
||||
|
||||
module.exports.loadModels = function loadModels() {
|
||||
mongoose.loadModels();
|
||||
};
|
||||
|
||||
module.exports.init = function init(callback) {
|
||||
|
||||
mongoose.connect(function (db) {
|
||||
// Initialize express
|
||||
var app = express.init(db);
|
||||
if (callback) callback(app, db, config);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.start = function start(callback) {
|
||||
var _this = this;
|
||||
|
||||
_this.init(function(app, db, config) {
|
||||
|
||||
// Start the app by listening on <port>
|
||||
app.listen(config.port, function() {
|
||||
|
||||
// Logging initialization
|
||||
console.log('--');
|
||||
console.log(chalk.green(config.app.title));
|
||||
console.log(chalk.green('Environment:\t\t\t' + process.env.NODE_ENV));
|
||||
console.log(chalk.green('Port:\t\t\t\t' + config.port));
|
||||
console.log(chalk.green('Database:\t\t\t\t' + config.db.uri));
|
||||
if (process.env.NODE_ENV === 'secure') {
|
||||
console.log(chalk.green('HTTPs:\t\t\t\ton'));
|
||||
}
|
||||
console.log('--');
|
||||
|
||||
if (callback) callback(app, db, config);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
249
config/lib/express.js
Normal file
249
config/lib/express.js
Normal file
@@ -0,0 +1,249 @@
|
||||
'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'),
|
||||
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;
|
||||
app.locals.livereload = config.livereload;
|
||||
app.locals.logo = config.logo;
|
||||
app.locals.favicon = config.favicon;
|
||||
|
||||
// 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) {
|
||||
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');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
44
config/lib/mongoose.js
Normal file
44
config/lib/mongoose.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'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.uri, config.db.options, function (err) {
|
||||
// Log Error
|
||||
if (err) {
|
||||
console.error(chalk.red('Could not connect to MongoDB!'));
|
||||
console.log(err);
|
||||
} else {
|
||||
|
||||
// Enabling mongoose debug mode if required
|
||||
mongoose.set('debug', config.db.debug);
|
||||
|
||||
// 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
76
config/lib/socket.io.js
Normal file
@@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
// Load the module dependencies
|
||||
var config = require('../config'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
http = require('http'),
|
||||
https = require('https'),
|
||||
cookieParser = require('cookie-parser'),
|
||||
passport = require('passport'),
|
||||
socketio = require('socket.io'),
|
||||
session = require('express-session'),
|
||||
MongoStore = require('connect-mongo')(session);
|
||||
|
||||
// Define the Socket.io configuration method
|
||||
module.exports = function (app, db) {
|
||||
var server;
|
||||
if (config.secure === true) {
|
||||
// Load SSL key and certificate
|
||||
var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8');
|
||||
var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8');
|
||||
var options = {
|
||||
key: privateKey,
|
||||
cert: certificate
|
||||
};
|
||||
|
||||
// Create new HTTPS Server
|
||||
server = https.createServer(options, app);
|
||||
} else {
|
||||
// Create a new HTTP server
|
||||
server = http.createServer(app);
|
||||
}
|
||||
// Create a new Socket.io server
|
||||
var io = socketio.listen(server);
|
||||
|
||||
// Create a MongoDB storage object
|
||||
var mongoStore = new MongoStore({
|
||||
mongooseConnection: db.connection,
|
||||
collection: config.sessionCollection
|
||||
});
|
||||
|
||||
// Intercept Socket.io's handshake request
|
||||
io.use(function (socket, next) {
|
||||
// Use the 'cookie-parser' module to parse the request cookies
|
||||
cookieParser(config.sessionSecret)(socket.request, {}, function (err) {
|
||||
// Get the session id from the request cookies
|
||||
var sessionId = socket.request.signedCookies['connect.sid'];
|
||||
|
||||
// Use the mongoStorage instance to get the Express session information
|
||||
mongoStore.get(sessionId, function (err, session) {
|
||||
// Set the Socket.io session information
|
||||
socket.request.session = session;
|
||||
|
||||
// Use Passport to populate the user details
|
||||
passport.initialize()(socket.request, {}, function () {
|
||||
passport.session()(socket.request, {}, function () {
|
||||
if (socket.request.user) {
|
||||
next(null, true);
|
||||
} else {
|
||||
next(new Error('User is not authenticated'), false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Add an event listener to the 'connection' event
|
||||
io.on('connection', function (socket) {
|
||||
config.files.server.sockets.forEach(function (socketConfiguration) {
|
||||
require(path.resolve(socketConfiguration))(io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
return server;
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var morgan = require('morgan');
|
||||
var config = require('./config');
|
||||
var fs = require('fs');
|
||||
|
||||
/**
|
||||
* Module init function.
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
getLogFormat: function() {
|
||||
return config.log.format;
|
||||
},
|
||||
|
||||
getLogOptions: function() {
|
||||
var options = {};
|
||||
|
||||
try {
|
||||
if ('stream' in config.log.options) {
|
||||
options = {
|
||||
stream: fs.createWriteStream(process.cwd() + '/' + config.log.options.stream, {flags: 'a'})
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
User = require('mongoose').model('User'),
|
||||
path = require('path'),
|
||||
config = require('./config');
|
||||
|
||||
/**
|
||||
* Module init function.
|
||||
*/
|
||||
module.exports = function() {
|
||||
// Serialize sessions
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user.id);
|
||||
});
|
||||
|
||||
// Deserialize sessions
|
||||
passport.deserializeUser(function(id, done) {
|
||||
User.findOne({
|
||||
_id: id
|
||||
}, '-salt -password', function(err, user) {
|
||||
done(err, user);
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize strategies
|
||||
config.getGlobbedFiles('./config/strategies/**/*.js').forEach(function(strategy) {
|
||||
require(path.resolve(strategy))();
|
||||
});
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
FacebookStrategy = require('passport-facebook').Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use facebook strategy
|
||||
passport.use(new FacebookStrategy({
|
||||
clientID: config.facebook.clientID,
|
||||
clientSecret: config.facebook.clientSecret,
|
||||
callbackURL: config.facebook.callbackURL,
|
||||
passReqToCallback: true
|
||||
},
|
||||
function(req, accessToken, refreshToken, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.accessToken = accessToken;
|
||||
providerData.refreshToken = refreshToken;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var providerUserProfile = {
|
||||
firstName: profile.name.givenName,
|
||||
lastName: profile.name.familyName,
|
||||
displayName: profile.displayName,
|
||||
email: profile.emails[0].value,
|
||||
username: profile.username,
|
||||
provider: 'facebook',
|
||||
providerIdentifierField: 'id',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
GithubStrategy = require('passport-github').Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use github strategy
|
||||
passport.use(new GithubStrategy({
|
||||
clientID: config.github.clientID,
|
||||
clientSecret: config.github.clientSecret,
|
||||
callbackURL: config.github.callbackURL,
|
||||
passReqToCallback: true
|
||||
},
|
||||
function(req, accessToken, refreshToken, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.accessToken = accessToken;
|
||||
providerData.refreshToken = refreshToken;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var displayName = profile.displayName.trim();
|
||||
var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName
|
||||
var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName;
|
||||
var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : '';
|
||||
|
||||
var providerUserProfile = {
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
displayName: displayName,
|
||||
email: profile.emails[0].value,
|
||||
username: profile.username,
|
||||
provider: 'github',
|
||||
providerIdentifierField: 'id',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
GoogleStrategy = require('passport-google-oauth').OAuth2Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use google strategy
|
||||
passport.use(new GoogleStrategy({
|
||||
clientID: config.google.clientID,
|
||||
clientSecret: config.google.clientSecret,
|
||||
callbackURL: config.google.callbackURL,
|
||||
passReqToCallback: true
|
||||
},
|
||||
function(req, accessToken, refreshToken, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.accessToken = accessToken;
|
||||
providerData.refreshToken = refreshToken;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var providerUserProfile = {
|
||||
firstName: profile.name.givenName,
|
||||
lastName: profile.name.familyName,
|
||||
displayName: profile.displayName,
|
||||
email: profile.emails[0].value,
|
||||
username: profile.username,
|
||||
provider: 'google',
|
||||
providerIdentifierField: 'id',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
LinkedInStrategy = require('passport-linkedin').Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use linkedin strategy
|
||||
passport.use(new LinkedInStrategy({
|
||||
consumerKey: config.linkedin.clientID,
|
||||
consumerSecret: config.linkedin.clientSecret,
|
||||
callbackURL: config.linkedin.callbackURL,
|
||||
passReqToCallback: true,
|
||||
profileFields: ['id', 'first-name', 'last-name', 'email-address']
|
||||
},
|
||||
function(req, accessToken, refreshToken, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.accessToken = accessToken;
|
||||
providerData.refreshToken = refreshToken;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var providerUserProfile = {
|
||||
firstName: profile.name.givenName,
|
||||
lastName: profile.name.familyName,
|
||||
displayName: profile.displayName,
|
||||
email: profile.emails[0].value,
|
||||
username: profile.username,
|
||||
provider: 'linkedin',
|
||||
providerIdentifierField: 'id',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
LocalStrategy = require('passport-local').Strategy,
|
||||
User = require('mongoose').model('User');
|
||||
|
||||
module.exports = function() {
|
||||
// Use local strategy
|
||||
passport.use(new LocalStrategy({
|
||||
usernameField: 'username',
|
||||
passwordField: 'password'
|
||||
},
|
||||
function(username, password, done) {
|
||||
User.findOne({
|
||||
username: username
|
||||
}, function(err, user) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
if (!user) {
|
||||
return done(null, false, {
|
||||
message: 'Unknown user or invalid password'
|
||||
});
|
||||
}
|
||||
if (!user.authenticate(password)) {
|
||||
return done(null, false, {
|
||||
message: 'Unknown user or invalid password'
|
||||
});
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
});
|
||||
}
|
||||
));
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
TwitterStrategy = require('passport-twitter').Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use twitter strategy
|
||||
passport.use(new TwitterStrategy({
|
||||
consumerKey: config.twitter.clientID,
|
||||
consumerSecret: config.twitter.clientSecret,
|
||||
callbackURL: config.twitter.callbackURL,
|
||||
passReqToCallback: true
|
||||
},
|
||||
function(req, token, tokenSecret, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.token = token;
|
||||
providerData.tokenSecret = tokenSecret;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var displayName = profile.displayName.trim();
|
||||
var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName
|
||||
var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName;
|
||||
var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : '';
|
||||
|
||||
var providerUserProfile = {
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
displayName: displayName,
|
||||
username: profile.username,
|
||||
provider: 'twitter',
|
||||
providerIdentifierField: 'id_str',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
||||
430
gruntfile.js
430
gruntfile.js
@@ -1,194 +1,266 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
defaultAssets = require('./config/assets/default'),
|
||||
testAssets = require('./config/assets/test'),
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
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.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: defaultAssets.server.views,
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
serverJS: {
|
||||
files: _.union(defaultAssets.server.gruntConfig, defaultAssets.server.allJS),
|
||||
tasks: ['jshint'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
clientViews: {
|
||||
files: defaultAssets.client.views,
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
clientJS: {
|
||||
files: defaultAssets.client.js,
|
||||
tasks: ['jshint'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
clientCSS: {
|
||||
files: defaultAssets.client.css,
|
||||
tasks: ['csslint'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
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.gruntConfig, defaultAssets.server.views, defaultAssets.server.allJS, defaultAssets.server.config)
|
||||
}
|
||||
}
|
||||
},
|
||||
concurrent: {
|
||||
default: ['nodemon', 'watch'],
|
||||
debug: ['nodemon', 'watch', 'node-inspector'],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
all: {
|
||||
src: _.union(defaultAssets.server.gruntConfig, defaultAssets.server.allJS, defaultAssets.client.js, testAssets.tests.server, testAssets.tests.client, testAssets.tests.e2e),
|
||||
options: {
|
||||
jshintrc: true,
|
||||
node: true,
|
||||
mocha: true,
|
||||
jasmine: true
|
||||
}
|
||||
}
|
||||
},
|
||||
csslint: {
|
||||
options: {
|
||||
csslintrc: '.csslintrc'
|
||||
},
|
||||
all: {
|
||||
src: defaultAssets.client.css
|
||||
}
|
||||
},
|
||||
ngAnnotate: {
|
||||
production: {
|
||||
files: {
|
||||
'public/dist/application.js': defaultAssets.client.js
|
||||
}
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
production: {
|
||||
options: {
|
||||
mangle: false
|
||||
},
|
||||
files: {
|
||||
'public/dist/application.min.js': 'public/dist/application.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
cssmin: {
|
||||
combine: {
|
||||
files: {
|
||||
'public/dist/application.min.css': defaultAssets.client.css
|
||||
}
|
||||
}
|
||||
},
|
||||
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': {
|
||||
custom: {
|
||||
options: {
|
||||
'web-port': 1337,
|
||||
'web-host': 'localhost',
|
||||
'debug-port': 5858,
|
||||
'save-live-edit': true,
|
||||
'no-preload': true,
|
||||
'stack-trace-limit': 50,
|
||||
'hidden': []
|
||||
}
|
||||
}
|
||||
},
|
||||
mochaTest: {
|
||||
src: testAssets.tests.server,
|
||||
options: {
|
||||
reporter: 'spec'
|
||||
}
|
||||
},
|
||||
karma: {
|
||||
unit: {
|
||||
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',
|
||||
dest: 'config/env/local.js',
|
||||
filter: function () {
|
||||
return !fs.existsSync('config/env/local.js');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Project Configuration
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
watch: {
|
||||
serverViews: {
|
||||
files: watchFiles.serverViews,
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
serverJS: {
|
||||
files: watchFiles.serverJS,
|
||||
tasks: ['jshint'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
clientViews: {
|
||||
files: watchFiles.clientViews,
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
clientJS: {
|
||||
files: watchFiles.clientJS,
|
||||
tasks: ['jshint'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
clientCSS: {
|
||||
files: watchFiles.clientCSS,
|
||||
tasks: ['csslint'],
|
||||
options: {
|
||||
livereload: true
|
||||
}
|
||||
},
|
||||
mochaTests: {
|
||||
files: watchFiles.mochaTests,
|
||||
tasks: ['test:server'],
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
all: {
|
||||
src: watchFiles.clientJS.concat(watchFiles.serverJS),
|
||||
options: {
|
||||
jshintrc: true
|
||||
}
|
||||
}
|
||||
},
|
||||
csslint: {
|
||||
options: {
|
||||
csslintrc: '.csslintrc'
|
||||
},
|
||||
all: {
|
||||
src: watchFiles.clientCSS
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
production: {
|
||||
options: {
|
||||
mangle: false
|
||||
},
|
||||
files: {
|
||||
'public/dist/application.min.js': 'public/dist/application.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
cssmin: {
|
||||
combine: {
|
||||
files: {
|
||||
'public/dist/application.min.css': '<%= applicationCSSFiles %>'
|
||||
}
|
||||
}
|
||||
},
|
||||
nodemon: {
|
||||
dev: {
|
||||
script: 'server.js',
|
||||
options: {
|
||||
nodeArgs: ['--debug'],
|
||||
ext: 'js,html',
|
||||
watch: watchFiles.serverViews.concat(watchFiles.serverJS)
|
||||
}
|
||||
}
|
||||
},
|
||||
'node-inspector': {
|
||||
custom: {
|
||||
options: {
|
||||
'web-port': 1337,
|
||||
'web-host': 'localhost',
|
||||
'debug-port': 5858,
|
||||
'save-live-edit': true,
|
||||
'no-preload': true,
|
||||
'stack-trace-limit': 50,
|
||||
'hidden': []
|
||||
}
|
||||
}
|
||||
},
|
||||
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,
|
||||
options: {
|
||||
reporter: 'spec',
|
||||
require: 'server.js'
|
||||
}
|
||||
},
|
||||
karma: {
|
||||
unit: {
|
||||
configFile: 'karma.conf.js'
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
localConfig: {
|
||||
src: 'config/env/local.example.js',
|
||||
dest: 'config/env/local.js',
|
||||
filter: function() {
|
||||
return !fs.existsSync('config/env/local.js');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Load NPM tasks
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
// Load NPM tasks
|
||||
require('load-grunt-tasks')(grunt);
|
||||
// Making grunt default to force in order not to break the project.
|
||||
grunt.option('force', true);
|
||||
|
||||
// Making grunt default to force in order not to break the project.
|
||||
grunt.option('force', true);
|
||||
// Make sure upload directory exists
|
||||
grunt.task.registerTask('mkdir:upload', 'Task that makes sure upload directory exists.', function () {
|
||||
// Get the callback
|
||||
var done = this.async();
|
||||
|
||||
// A Task for loading the configuration object
|
||||
grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() {
|
||||
var init = require('./config/init')();
|
||||
var config = require('./config/config');
|
||||
grunt.file.mkdir(path.normalize(__dirname + '/modules/users/client/img/profile/uploads'));
|
||||
|
||||
grunt.config.set('applicationJavaScriptFiles', config.assets.js);
|
||||
grunt.config.set('applicationCSSFiles', config.assets.css);
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
// Default task(s).
|
||||
grunt.registerTask('default', ['lint', 'copy:localConfig', 'concurrent:default']);
|
||||
// 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();
|
||||
|
||||
// Debug task.
|
||||
grunt.registerTask('debug', ['lint', 'copy:localConfig', 'concurrent:debug']);
|
||||
// Use mongoose configuration
|
||||
var mongoose = require('./config/lib/mongoose.js');
|
||||
|
||||
// Secure task(s).
|
||||
grunt.registerTask('secure', ['env:secure', 'lint', 'copy:localConfig', 'concurrent:default']);
|
||||
// Connect to database
|
||||
mongoose.connect(function (db) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Lint task(s).
|
||||
grunt.registerTask('lint', ['jshint', 'csslint']);
|
||||
grunt.task.registerTask('server', 'Starting the server', function () {
|
||||
// Get the callback
|
||||
var done = this.async();
|
||||
|
||||
// Build task(s).
|
||||
grunt.registerTask('build', ['lint', 'loadConfig', 'ngAnnotate', 'uglify', 'cssmin']);
|
||||
var path = require('path');
|
||||
var app = require(path.resolve('./config/lib/app'));
|
||||
var server = app.start(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// 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']);
|
||||
// Lint CSS and JavaScript files.
|
||||
grunt.registerTask('lint', ['sass', 'less', 'jshint', 'csslint']);
|
||||
|
||||
// Lint project files and minify them into two production files.
|
||||
grunt.registerTask('build', ['env:dev', 'lint', 'ngAnnotate', 'uglify', 'cssmin']);
|
||||
|
||||
// Run the project tests
|
||||
grunt.registerTask('test', ['env:test', 'lint', 'mkdir:upload', 'copy:localConfig', 'server', 'mochaTest', 'karma:unit']);
|
||||
grunt.registerTask('test:server', ['env:test', 'lint', 'server', 'mochaTest']);
|
||||
grunt.registerTask('test:client', ['env:test', 'lint', 'server', 'karma:unit']);
|
||||
// Run the project in development mode
|
||||
grunt.registerTask('default', ['env:dev', 'lint', 'mkdir:upload', 'copy:localConfig', 'concurrent:default']);
|
||||
|
||||
// Run the project in debug mode
|
||||
grunt.registerTask('debug', ['env:dev', 'lint', 'mkdir:upload', 'copy:localConfig', 'concurrent:debug']);
|
||||
|
||||
// Run the project in production mode
|
||||
grunt.registerTask('prod', ['build', 'env:prod', 'mkdir:upload', 'copy:localConfig', 'concurrent:default']);
|
||||
};
|
||||
|
||||
195
gulpfile.js
Normal file
195
gulpfile.js
Normal file
@@ -0,0 +1,195 @@
|
||||
'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(),
|
||||
path = require('path');
|
||||
|
||||
// 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.gulpConfig, ['jshint']);
|
||||
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.gulpConfig, 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 (file) {
|
||||
file.dirname = file.dirname.replace(path.sep + 'scss', path.sep + 'css');
|
||||
}))
|
||||
.pipe(gulp.dest('./modules/'));
|
||||
});
|
||||
|
||||
// Less task
|
||||
gulp.task('less', function () {
|
||||
return gulp.src(defaultAssets.client.less)
|
||||
.pipe(plugins.less())
|
||||
.pipe(plugins.rename(function (file) {
|
||||
file.dirname = file.dirname.replace(path.sep + 'less', path.sep + 'css');
|
||||
}))
|
||||
.pipe(gulp.dest('./modules/'));
|
||||
});
|
||||
|
||||
// Mocha tests task
|
||||
gulp.task('mocha', function (done) {
|
||||
// Open mongoose connections
|
||||
var mongoose = require('./config/lib/mongoose.js');
|
||||
var error;
|
||||
|
||||
// Connect mongoose
|
||||
mongoose.connect(function() {
|
||||
// Run the tests
|
||||
gulp.src(testAssets.tests.server)
|
||||
.pipe(plugins.mocha({
|
||||
reporter: 'spec'
|
||||
}))
|
||||
.on('error', function (err) {
|
||||
// If an error occurs, save it
|
||||
error = err;
|
||||
})
|
||||
.on('end', function() {
|
||||
// When the tests are done, disconnect mongoose and pass the error state back to gulp
|
||||
mongoose.disconnect(function() {
|
||||
done(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Karma test runner task
|
||||
gulp.task('karma', function (done) {
|
||||
return gulp.src([])
|
||||
.pipe(plugins.karma({
|
||||
configFile: 'karma.conf.js',
|
||||
action: 'run',
|
||||
singleRun: true
|
||||
}));
|
||||
});
|
||||
|
||||
// Selenium standalone WebDriver update task
|
||||
gulp.task('webdriver-update', plugins.protractor.webdriver_update);
|
||||
|
||||
// Protractor test runner task
|
||||
gulp.task('protractor', function () {
|
||||
gulp.src([])
|
||||
.pipe(plugins.protractor.protractor({
|
||||
configFile: 'protractor.conf.js'
|
||||
}))
|
||||
.on('error', function (e) {
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
|
||||
// Lint CSS and JavaScript files.
|
||||
gulp.task('lint', function(done) {
|
||||
runSequence('less', 'sass', ['csslint', 'jshint'], done);
|
||||
});
|
||||
|
||||
// Lint project files and minify them into two production files.
|
||||
gulp.task('build', function(done) {
|
||||
runSequence('env:dev' ,'lint', ['uglify', 'cssmin'], done);
|
||||
});
|
||||
|
||||
// Run the project tests
|
||||
gulp.task('test', function(done) {
|
||||
runSequence('env:test', ['karma', 'mocha'], done);
|
||||
});
|
||||
|
||||
// Run the project in development mode
|
||||
gulp.task('default', function(done) {
|
||||
runSequence('env:dev', 'lint', ['nodemon', 'watch'], done);
|
||||
});
|
||||
|
||||
// Run the project in debug mode
|
||||
gulp.task('debug', function(done) {
|
||||
runSequence('env:dev', 'lint', ['nodemon', 'watch'], done);
|
||||
});
|
||||
|
||||
// Run the project in production mode
|
||||
gulp.task('prod', function(done) {
|
||||
runSequence('build', 'lint', ['nodemon', 'watch'], done);
|
||||
});
|
||||
@@ -3,49 +3,63 @@
|
||||
/**
|
||||
* 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({
|
||||
// Frameworks to use
|
||||
frameworks: ['jasmine'],
|
||||
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),
|
||||
preprocessors: {
|
||||
'modules/*/client/views/**/*.html': ['ng-html2js']
|
||||
},
|
||||
|
||||
// Test results reporter to use
|
||||
// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
|
||||
reporters: ['progress'],
|
||||
ngHtml2JsPreprocessor: {
|
||||
moduleName: 'mean',
|
||||
|
||||
// Web server port
|
||||
port: 9876,
|
||||
cacheIdFromPath: function (filepath) {
|
||||
return filepath.replace('/client', '');
|
||||
},
|
||||
},
|
||||
|
||||
// Enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
// List of files / patterns to load in the browser
|
||||
files: _.union(defaultAssets.client.lib.js, defaultAssets.client.lib.tests, defaultAssets.client.js, testAssets.tests.client, defaultAssets.client.views),
|
||||
|
||||
// Level of logging
|
||||
// Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
// Test results reporter to use
|
||||
// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
|
||||
reporters: ['progress'],
|
||||
|
||||
// Enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
// Web server port
|
||||
port: 9876,
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['PhantomJS'],
|
||||
// Enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
// If browser does not capture in given timeout [ms], kill it
|
||||
captureTimeout: 60000,
|
||||
// Level of logging
|
||||
// Possible values: karmaConfig.LOG_DISABLE || karmaConfig.LOG_ERROR || karmaConfig.LOG_WARN || karmaConfig.LOG_INFO || karmaConfig.LOG_DEBUG
|
||||
logLevel: karmaConfig.LOG_INFO,
|
||||
|
||||
// Continuous Integration mode
|
||||
// If true, it capture browsers, run tests and exit
|
||||
singleRun: true
|
||||
});
|
||||
// Enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari (only Mac)
|
||||
// - PhantomJS
|
||||
// - IE (only Windows)
|
||||
browsers: ['PhantomJS'],
|
||||
|
||||
// If browser does not capture in given timeout [ms], kill it
|
||||
captureTimeout: 60000,
|
||||
|
||||
// Continuous Integration mode
|
||||
// If true, it capture browsers, run tests and exit
|
||||
singleRun: true
|
||||
});
|
||||
};
|
||||
|
||||
2
public/modules/articles/articles.client.module.js → modules/articles/client/articles.client.module.js
Executable file → Normal file
2
public/modules/articles/articles.client.module.js → modules/articles/client/articles.client.module.js
Executable file → Normal file
@@ -1,4 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
// Use Application configuration module to register a new module
|
||||
// Use Applicaion configuration module to register a new module
|
||||
ApplicationConfiguration.registerModule('articles');
|
||||
25
modules/articles/client/config/articles.client.config.js
Normal file
25
modules/articles/client/config/articles.client.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
// Configuring the Articles module
|
||||
angular.module('articles').run(['Menus',
|
||||
function (Menus) {
|
||||
// Add the articles dropdown item
|
||||
Menus.addMenuItem('topbar', {
|
||||
title: 'Articles',
|
||||
state: 'articles',
|
||||
type: 'dropdown'
|
||||
});
|
||||
|
||||
// Add the dropdown list item
|
||||
Menus.addSubMenuItem('topbar', 'articles', {
|
||||
title: 'List Articles',
|
||||
state: 'articles.list'
|
||||
});
|
||||
|
||||
// Add the dropdown create item
|
||||
Menus.addSubMenuItem('topbar', 'articles', {
|
||||
title: 'Create Articles',
|
||||
state: 'articles.create'
|
||||
});
|
||||
}
|
||||
]);
|
||||
33
modules/articles/client/config/articles.client.routes.js
Normal file
33
modules/articles/client/config/articles.client.routes.js
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
// Setting up route
|
||||
angular.module('articles').config(['$stateProvider',
|
||||
function ($stateProvider) {
|
||||
// Articles state routing
|
||||
$stateProvider
|
||||
.state('articles', {
|
||||
abstract: true,
|
||||
url: '/articles',
|
||||
template: '<ui-view/>',
|
||||
data: {
|
||||
roles: ['user', 'admin']
|
||||
}
|
||||
})
|
||||
.state('articles.list', {
|
||||
url: '',
|
||||
templateUrl: 'modules/articles/views/list-articles.client.view.html'
|
||||
})
|
||||
.state('articles.create', {
|
||||
url: '/create',
|
||||
templateUrl: 'modules/articles/views/create-article.client.view.html'
|
||||
})
|
||||
.state('articles.view', {
|
||||
url: '/:articleId',
|
||||
templateUrl: 'modules/articles/views/view-article.client.view.html'
|
||||
})
|
||||
.state('articles.edit', {
|
||||
url: '/:articleId/edit',
|
||||
templateUrl: 'modules/articles/views/edit-article.client.view.html'
|
||||
});
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
// Articles controller
|
||||
angular.module('articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Authentication', 'Articles',
|
||||
function ($scope, $stateParams, $location, Authentication, Articles) {
|
||||
$scope.authentication = Authentication;
|
||||
|
||||
// Create new Article
|
||||
$scope.create = function () {
|
||||
// Create new Article object
|
||||
var article = new Articles({
|
||||
title: this.title,
|
||||
content: this.content
|
||||
});
|
||||
|
||||
// Redirect after save
|
||||
article.$save(function (response) {
|
||||
$location.path('articles/' + response._id);
|
||||
|
||||
// Clear form fields
|
||||
$scope.title = '';
|
||||
$scope.content = '';
|
||||
}, function (errorResponse) {
|
||||
$scope.error = errorResponse.data.message;
|
||||
});
|
||||
};
|
||||
|
||||
// Remove existing Article
|
||||
$scope.remove = function (article) {
|
||||
if (article) {
|
||||
article.$remove();
|
||||
|
||||
for (var i in $scope.articles) {
|
||||
if ($scope.articles[i] === article) {
|
||||
$scope.articles.splice(i, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$scope.article.$remove(function () {
|
||||
$location.path('articles');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Update existing Article
|
||||
$scope.update = function () {
|
||||
var article = $scope.article;
|
||||
|
||||
article.$update(function () {
|
||||
$location.path('articles/' + article._id);
|
||||
}, function (errorResponse) {
|
||||
$scope.error = errorResponse.data.message;
|
||||
});
|
||||
};
|
||||
|
||||
// Find a list of Articles
|
||||
$scope.find = function () {
|
||||
$scope.articles = Articles.query();
|
||||
};
|
||||
|
||||
// Find existing Article
|
||||
$scope.findOne = function () {
|
||||
$scope.article = Articles.get({
|
||||
articleId: $stateParams.articleId
|
||||
});
|
||||
};
|
||||
}
|
||||
]);
|
||||
14
modules/articles/client/services/articles.client.service.js
Normal file
14
modules/articles/client/services/articles.client.service.js
Normal file
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
//Articles service used for communicating with the articles REST endpoints
|
||||
angular.module('articles').factory('Articles', ['$resource',
|
||||
function ($resource) {
|
||||
return $resource('api/articles/:articleId', {
|
||||
articleId: '@_id'
|
||||
}, {
|
||||
update: {
|
||||
method: 'PUT'
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,29 @@
|
||||
<section data-ng-controller="ArticlesController">
|
||||
<div class="page-header">
|
||||
<h1>New Article</h1>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<form name="articleForm" class="form-horizontal" data-ng-submit="create()" novalidate>
|
||||
<fieldset>
|
||||
<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">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="content">Content</label>
|
||||
<div class="controls">
|
||||
<textarea name="content" data-ng-model="content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-default">
|
||||
</div>
|
||||
<div data-ng-show="error" class="text-danger">
|
||||
<strong data-ng-bind="error"></strong>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
29
modules/articles/client/views/edit-article.client.view.html
Normal file
29
modules/articles/client/views/edit-article.client.view.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<section data-ng-controller="ArticlesController" data-ng-init="findOne()">
|
||||
<div class="page-header">
|
||||
<h1>Edit Article</h1>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<form name="articleForm" class="form-horizontal" data-ng-submit="update()" novalidate>
|
||||
<fieldset>
|
||||
<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>
|
||||
<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"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Update" class="btn btn-default">
|
||||
</div>
|
||||
<div data-ng-show="error" class="text-danger">
|
||||
<strong data-ng-bind="error"></strong>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
20
modules/articles/client/views/list-articles.client.view.html
Normal file
20
modules/articles/client/views/list-articles.client.view.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<section data-ng-controller="ArticlesController" data-ng-init="find()">
|
||||
<div class="page-header">
|
||||
<h1>Articles</h1>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<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>
|
||||
by
|
||||
<span data-ng-bind="article.user.displayName"></span>
|
||||
</small>
|
||||
<h4 class="list-group-item-heading" data-ng-bind="article.title"></h4>
|
||||
<p class="list-group-item-text" data-ng-bind="article.content"></p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="alert alert-warning text-center" data-ng-if="articles.$resolved && !articles.length">
|
||||
No articles yet, why don't you <a data-ui-sref="articles.create">create one</a>?
|
||||
</div>
|
||||
</section>
|
||||
22
modules/articles/client/views/view-article.client.view.html
Normal file
22
modules/articles/client/views/view-article.client.view.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<section data-ng-controller="ArticlesController" data-ng-init="findOne()">
|
||||
<div class="page-header">
|
||||
<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" data-ui-sref="articles.edit({articleId: article._id})">
|
||||
<i class="glyphicon glyphicon-edit"></i>
|
||||
</a>
|
||||
<a class="btn btn-primary" data-ng-click="remove();">
|
||||
<i class="glyphicon glyphicon-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
<small>
|
||||
<em class="text-muted">
|
||||
Posted on
|
||||
<span data-ng-bind="article.created | date:'mediumDate'"></span>
|
||||
by
|
||||
<span data-ng-bind="article.user.displayName"></span>
|
||||
</em>
|
||||
</small>
|
||||
<p class="lead" data-ng-bind="article.content"></p>
|
||||
</section>
|
||||
@@ -0,0 +1,110 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var path = require('path'),
|
||||
mongoose = require('mongoose'),
|
||||
Article = mongoose.model('Article'),
|
||||
errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller'));
|
||||
|
||||
/**
|
||||
* Create a article
|
||||
*/
|
||||
exports.create = function (req, res) {
|
||||
var article = new Article(req.body);
|
||||
article.user = req.user;
|
||||
|
||||
article.save(function (err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
res.json(article);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the current article
|
||||
*/
|
||||
exports.read = function (req, res) {
|
||||
res.json(req.article);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a article
|
||||
*/
|
||||
exports.update = function (req, res) {
|
||||
var article = req.article;
|
||||
|
||||
article.title = req.body.title;
|
||||
article.content = req.body.content;
|
||||
|
||||
article.save(function (err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
res.json(article);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete an article
|
||||
*/
|
||||
exports.delete = function (req, res) {
|
||||
var article = req.article;
|
||||
|
||||
article.remove(function (err) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
res.json(article);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* List of Articles
|
||||
*/
|
||||
exports.list = function (req, res) {
|
||||
Article.find().sort('-created').populate('user', 'displayName').exec(function (err, articles) {
|
||||
if (err) {
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
res.json(articles);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Article middleware
|
||||
*/
|
||||
exports.articleByID = function (req, res, next, id) {
|
||||
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).send({
|
||||
message: 'Article is invalid'
|
||||
});
|
||||
}
|
||||
|
||||
Article.findById(id).populate('user', 'displayName').exec(function (err, article) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
} else if (!article) {
|
||||
return res.status(404).send({
|
||||
message: 'No article with that identifier has been found'
|
||||
});
|
||||
}
|
||||
req.article = article;
|
||||
next();
|
||||
});
|
||||
};
|
||||
34
modules/articles/server/models/article.server.model.js
Normal file
34
modules/articles/server/models/article.server.model.js
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema;
|
||||
|
||||
/**
|
||||
* Article Schema
|
||||
*/
|
||||
var ArticleSchema = new Schema({
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
trim: true,
|
||||
required: 'Title cannot be blank'
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
trim: true
|
||||
},
|
||||
user: {
|
||||
type: Schema.ObjectId,
|
||||
ref: 'User'
|
||||
}
|
||||
});
|
||||
|
||||
mongoose.model('Article', ArticleSchema);
|
||||
72
modules/articles/server/policies/articles.server.policy.js
Normal file
72
modules/articles/server/policies/articles.server.policy.js
Normal file
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var acl = require('acl');
|
||||
|
||||
// Using the memory backend
|
||||
acl = new acl(new acl.memoryBackend());
|
||||
|
||||
/**
|
||||
* Invoke Articles Permissions
|
||||
*/
|
||||
exports.invokeRolesPolicies = function () {
|
||||
acl.allow([{
|
||||
roles: ['admin'],
|
||||
allows: [{
|
||||
resources: '/api/articles',
|
||||
permissions: '*'
|
||||
}, {
|
||||
resources: '/api/articles/:articleId',
|
||||
permissions: '*'
|
||||
}]
|
||||
}, {
|
||||
roles: ['user'],
|
||||
allows: [{
|
||||
resources: '/api/articles',
|
||||
permissions: ['get', 'post']
|
||||
}, {
|
||||
resources: '/api/articles/:articleId',
|
||||
permissions: ['get']
|
||||
}]
|
||||
}, {
|
||||
roles: ['guest'],
|
||||
allows: [{
|
||||
resources: '/api/articles',
|
||||
permissions: ['get']
|
||||
}, {
|
||||
resources: '/api/articles/:articleId',
|
||||
permissions: ['get']
|
||||
}]
|
||||
}]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check If Articles Policy Allows
|
||||
*/
|
||||
exports.isAllowed = function (req, res, next) {
|
||||
var roles = (req.user) ? req.user.roles : ['guest'];
|
||||
|
||||
// If an article is being processed and the current user created it then allow any manipulation
|
||||
if (req.article && req.user && req.article.user.id === req.user.id) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Check for user roles
|
||||
acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase(), function (err, isAllowed) {
|
||||
if (err) {
|
||||
// An authorization error occurred.
|
||||
return res.status(500).send('Unexpected authorization error');
|
||||
} else {
|
||||
if (isAllowed) {
|
||||
// Access granted! Invoke next middleware
|
||||
return next();
|
||||
} else {
|
||||
return res.status(403).json({
|
||||
message: 'User is not authorized'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
23
modules/articles/server/routes/articles.server.routes.js
Normal file
23
modules/articles/server/routes/articles.server.routes.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var articlesPolicy = require('../policies/articles.server.policy'),
|
||||
articles = require('../controllers/articles.server.controller');
|
||||
|
||||
module.exports = function (app) {
|
||||
// Articles collection routes
|
||||
app.route('/api/articles').all(articlesPolicy.isAllowed)
|
||||
.get(articles.list)
|
||||
.post(articles.create);
|
||||
|
||||
// Single article routes
|
||||
app.route('/api/articles/:articleId').all(articlesPolicy.isAllowed)
|
||||
.get(articles.read)
|
||||
.put(articles.update)
|
||||
.delete(articles.delete);
|
||||
|
||||
// Finish by binding the article middleware
|
||||
app.param('articleId', articles.articleByID);
|
||||
};
|
||||
@@ -0,0 +1,210 @@
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
// Articles Controller Spec
|
||||
describe('Articles Controller Tests', function () {
|
||||
// Initialize global variables
|
||||
var ArticlesController,
|
||||
scope,
|
||||
$httpBackend,
|
||||
$stateParams,
|
||||
$location,
|
||||
Authentication,
|
||||
Articles,
|
||||
mockArticle;
|
||||
|
||||
// The $resource service augments the response object with methods for updating and deleting the resource.
|
||||
// If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
|
||||
// the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
|
||||
// When the toEqualData matcher compares two objects, it takes only object properties into
|
||||
// account and ignores methods.
|
||||
beforeEach(function () {
|
||||
jasmine.addMatchers({
|
||||
toEqualData: function (util, customEqualityTesters) {
|
||||
return {
|
||||
compare: function (actual, expected) {
|
||||
return {
|
||||
pass: angular.equals(actual, expected)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Then we can start by loading the main application module
|
||||
beforeEach(module(ApplicationConfiguration.applicationModuleName));
|
||||
|
||||
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
|
||||
// This allows us to inject a service but then attach it to a variable
|
||||
// with the same name as the service.
|
||||
beforeEach(inject(function ($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_, _Authentication_, _Articles_) {
|
||||
// Set a new global scope
|
||||
scope = $rootScope.$new();
|
||||
|
||||
// Point global variables to injected services
|
||||
$stateParams = _$stateParams_;
|
||||
$httpBackend = _$httpBackend_;
|
||||
$location = _$location_;
|
||||
Authentication = _Authentication_;
|
||||
Articles = _Articles_;
|
||||
|
||||
// create mock article
|
||||
mockArticle = new Articles({
|
||||
_id: '525a8422f6d0f87f0e407a33',
|
||||
title: 'An Article about MEAN',
|
||||
content: 'MEAN rocks!'
|
||||
});
|
||||
|
||||
// Mock logged in user
|
||||
Authentication.user = {
|
||||
roles: ['user']
|
||||
};
|
||||
|
||||
// Initialize the Articles controller.
|
||||
ArticlesController = $controller('ArticlesController', {
|
||||
$scope: scope
|
||||
});
|
||||
}));
|
||||
|
||||
it('$scope.find() should create an array with at least one article object fetched from XHR', inject(function (Articles) {
|
||||
// Create a sample articles array that includes the new article
|
||||
var sampleArticles = [mockArticle];
|
||||
|
||||
// Set GET response
|
||||
$httpBackend.expectGET('api/articles').respond(sampleArticles);
|
||||
|
||||
// Run controller functionality
|
||||
scope.find();
|
||||
$httpBackend.flush();
|
||||
|
||||
// Test scope value
|
||||
expect(scope.articles).toEqualData(sampleArticles);
|
||||
}));
|
||||
|
||||
it('$scope.findOne() should create an array with one article object fetched from XHR using a articleId URL parameter', inject(function (Articles) {
|
||||
// Set the URL parameter
|
||||
$stateParams.articleId = mockArticle._id;
|
||||
|
||||
// Set GET response
|
||||
$httpBackend.expectGET(/api\/articles\/([0-9a-fA-F]{24})$/).respond(mockArticle);
|
||||
|
||||
// Run controller functionality
|
||||
scope.findOne();
|
||||
$httpBackend.flush();
|
||||
|
||||
// Test scope value
|
||||
expect(scope.article).toEqualData(mockArticle);
|
||||
}));
|
||||
|
||||
describe('$scope.craete()', function () {
|
||||
var sampleArticlePostData;
|
||||
|
||||
beforeEach(function () {
|
||||
// Create a sample article object
|
||||
sampleArticlePostData = new Articles({
|
||||
title: 'An Article about MEAN',
|
||||
content: 'MEAN rocks!'
|
||||
});
|
||||
|
||||
// Fixture mock form input values
|
||||
scope.title = 'An Article about MEAN';
|
||||
scope.content = 'MEAN rocks!';
|
||||
|
||||
spyOn($location, 'path');
|
||||
});
|
||||
|
||||
it('should send a POST request with the form input values and then locate to new object URL', inject(function (Articles) {
|
||||
// Set POST response
|
||||
$httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(mockArticle);
|
||||
|
||||
// Run controller functionality
|
||||
scope.create();
|
||||
$httpBackend.flush();
|
||||
|
||||
// Test form inputs are reset
|
||||
expect(scope.title).toEqual('');
|
||||
expect(scope.content).toEqual('');
|
||||
|
||||
// Test URL redirection after the article was created
|
||||
expect($location.path.calls.mostRecent().args[0]).toBe('articles/' + mockArticle._id);
|
||||
}));
|
||||
|
||||
it('should set scope.error if save error', function () {
|
||||
var errorMessage = 'this is an error message';
|
||||
$httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(400, {
|
||||
message: errorMessage
|
||||
});
|
||||
|
||||
scope.create();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(scope.error).toBe(errorMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('$scope.update()', function () {
|
||||
beforeEach(function () {
|
||||
// Mock article in scope
|
||||
scope.article = mockArticle;
|
||||
});
|
||||
|
||||
it('should update a valid article', inject(function (Articles) {
|
||||
// Set PUT response
|
||||
$httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond();
|
||||
|
||||
// Run controller functionality
|
||||
scope.update();
|
||||
$httpBackend.flush();
|
||||
|
||||
// Test URL location to new object
|
||||
expect($location.path()).toBe('/articles/' + mockArticle._id);
|
||||
}));
|
||||
|
||||
it('should set scope.error to error response message', inject(function (Articles) {
|
||||
var errorMessage = 'error';
|
||||
$httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(400, {
|
||||
message: errorMessage
|
||||
});
|
||||
|
||||
scope.update();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(scope.error).toBe(errorMessage);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('$scope.remove(article)', function () {
|
||||
beforeEach(function () {
|
||||
// Create new articles array and include the article
|
||||
scope.articles = [mockArticle, {}];
|
||||
|
||||
// Set expected DELETE response
|
||||
$httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204);
|
||||
|
||||
// Run controller functionality
|
||||
scope.remove(mockArticle);
|
||||
});
|
||||
|
||||
it('should send a DELETE request with a valid articleId and remove the article from the scope', inject(function (Articles) {
|
||||
expect(scope.articles.length).toBe(1);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('scope.remove()', function () {
|
||||
beforeEach(function () {
|
||||
spyOn($location, 'path');
|
||||
scope.article = mockArticle;
|
||||
|
||||
$httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204);
|
||||
|
||||
scope.remove();
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should redirect to articles', function () {
|
||||
expect($location.path).toHaveBeenCalledWith('articles');
|
||||
});
|
||||
});
|
||||
});
|
||||
}());
|
||||
10
modules/articles/tests/e2e/articles.e2e.tests.js
Normal file
10
modules/articles/tests/e2e/articles.e2e.tests.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
describe('Articles E2E Tests:', function () {
|
||||
describe('Test articles page', function () {
|
||||
it('Should report missing credentials', function () {
|
||||
browser.get('http://localhost:3000/articles');
|
||||
expect(element.all(by.repeater('article in articles')).count()).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
64
modules/articles/tests/server/article.server.model.tests.js
Normal file
64
modules/articles/tests/server/article.server.model.tests.js
Normal file
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var should = require('should'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Article = mongoose.model('Article');
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
var user, article;
|
||||
|
||||
/**
|
||||
* Unit tests
|
||||
*/
|
||||
describe('Article Model Unit Tests:', function () {
|
||||
beforeEach(function (done) {
|
||||
user = new User({
|
||||
firstName: 'Full',
|
||||
lastName: 'Name',
|
||||
displayName: 'Full Name',
|
||||
email: 'test@test.com',
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
});
|
||||
|
||||
user.save(function () {
|
||||
article = new Article({
|
||||
title: 'Article Title',
|
||||
content: 'Article Content',
|
||||
user: user
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Method Save', function () {
|
||||
it('should be able to save without problems', function (done) {
|
||||
return article.save(function (err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to show an error when try to save without title', function (done) {
|
||||
article.title = '';
|
||||
|
||||
return article.save(function (err) {
|
||||
should.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
Article.remove().exec(function () {
|
||||
User.remove().exec(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
320
modules/articles/tests/server/article.server.routes.tests.js
Normal file
320
modules/articles/tests/server/article.server.routes.tests.js
Normal file
@@ -0,0 +1,320 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('should'),
|
||||
request = require('supertest'),
|
||||
path = require('path'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Article = mongoose.model('Article'),
|
||||
express = require(path.resolve('./config/lib/express'));
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
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 = {
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
};
|
||||
|
||||
// Create a new user
|
||||
user = new User({
|
||||
firstName: 'Full',
|
||||
lastName: 'Name',
|
||||
displayName: 'Full Name',
|
||||
email: 'test@test.com',
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
provider: 'local'
|
||||
});
|
||||
|
||||
// Save a user to the test db and create new article
|
||||
user.save(function () {
|
||||
article = {
|
||||
title: 'Article Title',
|
||||
content: 'Article Content'
|
||||
};
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to save an article if logged in', function (done) {
|
||||
agent.post('/api/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function (signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) {
|
||||
return done(signinErr);
|
||||
}
|
||||
|
||||
// Get the userId
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function (articleSaveErr, articleSaveRes) {
|
||||
// Handle article save error
|
||||
if (articleSaveErr) {
|
||||
return done(articleSaveErr);
|
||||
}
|
||||
|
||||
// Get a list of articles
|
||||
agent.get('/api/articles')
|
||||
.end(function (articlesGetErr, articlesGetRes) {
|
||||
// Handle article save error
|
||||
if (articlesGetErr) {
|
||||
return done(articlesGetErr);
|
||||
}
|
||||
|
||||
// Get articles list
|
||||
var articles = articlesGetRes.body;
|
||||
|
||||
// Set assertions
|
||||
(articles[0].user._id).should.equal(userId);
|
||||
(articles[0].title).should.match('Article Title');
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to save an article if not logged in', function (done) {
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(403)
|
||||
.end(function (articleSaveErr, articleSaveRes) {
|
||||
// Call the assertion callback
|
||||
done(articleSaveErr);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to save an article if no title is provided', function (done) {
|
||||
// Invalidate title field
|
||||
article.title = '';
|
||||
|
||||
agent.post('/api/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function (signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) {
|
||||
return done(signinErr);
|
||||
}
|
||||
|
||||
// Get the userId
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(400)
|
||||
.end(function (articleSaveErr, articleSaveRes) {
|
||||
// Set message assertion
|
||||
(articleSaveRes.body.message).should.match('Title cannot be blank');
|
||||
|
||||
// Handle article save error
|
||||
done(articleSaveErr);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to update an article if signed in', function (done) {
|
||||
agent.post('/api/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function (signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) {
|
||||
return done(signinErr);
|
||||
}
|
||||
|
||||
// Get the userId
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function (articleSaveErr, articleSaveRes) {
|
||||
// Handle article save error
|
||||
if (articleSaveErr) {
|
||||
return done(articleSaveErr);
|
||||
}
|
||||
|
||||
// Update article title
|
||||
article.title = 'WHY YOU GOTTA BE SO MEAN?';
|
||||
|
||||
// Update an existing article
|
||||
agent.put('/api/articles/' + articleSaveRes.body._id)
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function (articleUpdateErr, articleUpdateRes) {
|
||||
// Handle article update error
|
||||
if (articleUpdateErr) {
|
||||
return done(articleUpdateErr);
|
||||
}
|
||||
|
||||
// Set assertions
|
||||
(articleUpdateRes.body._id).should.equal(articleSaveRes.body._id);
|
||||
(articleUpdateRes.body.title).should.match('WHY YOU GOTTA BE SO MEAN?');
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get a list of articles if not signed in', function (done) {
|
||||
// Create new article model instance
|
||||
var articleObj = new Article(article);
|
||||
|
||||
// Save the article
|
||||
articleObj.save(function () {
|
||||
// Request articles
|
||||
request(app).get('/api/articles')
|
||||
.end(function (req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.instanceof(Array).and.have.lengthOf(1);
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to get a single article if not signed in', function (done) {
|
||||
// Create new article model instance
|
||||
var articleObj = new Article(article);
|
||||
|
||||
// Save the article
|
||||
articleObj.save(function () {
|
||||
request(app).get('/api/articles/' + articleObj._id)
|
||||
.end(function (req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.instanceof(Object).and.have.property('title', article.title);
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return proper error for single article with an invalid Id, if not signed in', function (done) {
|
||||
// test is not a valid mongoose Id
|
||||
request(app).get('/api/articles/test')
|
||||
.end(function (req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.instanceof(Object).and.have.property('message', 'Article is invalid');
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return proper error for single article which doesnt exist, if not signed in', function (done) {
|
||||
// This is a valid mongoose Id but a non-existent article
|
||||
request(app).get('/api/articles/559e9cd815f80b4c256a8f41')
|
||||
.end(function (req, res) {
|
||||
// Set assertion
|
||||
res.body.should.be.instanceof(Object).and.have.property('message', 'No article with that identifier has been found');
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to delete an article if signed in', function (done) {
|
||||
agent.post('/api/auth/signin')
|
||||
.send(credentials)
|
||||
.expect(200)
|
||||
.end(function (signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) {
|
||||
return done(signinErr);
|
||||
}
|
||||
|
||||
// Get the userId
|
||||
var userId = user.id;
|
||||
|
||||
// Save a new article
|
||||
agent.post('/api/articles')
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function (articleSaveErr, articleSaveRes) {
|
||||
// Handle article save error
|
||||
if (articleSaveErr) {
|
||||
return done(articleSaveErr);
|
||||
}
|
||||
|
||||
// Delete an existing article
|
||||
agent.delete('/api/articles/' + articleSaveRes.body._id)
|
||||
.send(article)
|
||||
.expect(200)
|
||||
.end(function (articleDeleteErr, articleDeleteRes) {
|
||||
// Handle article error error
|
||||
if (articleDeleteErr) {
|
||||
return done(articleDeleteErr);
|
||||
}
|
||||
|
||||
// Set assertions
|
||||
(articleDeleteRes.body._id).should.equal(articleSaveRes.body._id);
|
||||
|
||||
// Call the assertion callback
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to delete an article if not signed in', function (done) {
|
||||
// Set article user
|
||||
article.user = user;
|
||||
|
||||
// Create new article model instance
|
||||
var articleObj = new Article(article);
|
||||
|
||||
// Save the article
|
||||
articleObj.save(function () {
|
||||
// Try deleting article
|
||||
request(app).delete('/api/articles/' + articleObj._id)
|
||||
.expect(403)
|
||||
.end(function (articleDeleteErr, articleDeleteRes) {
|
||||
// Set message assertion
|
||||
(articleDeleteRes.body.message).should.match('User is not authorized');
|
||||
|
||||
// Handle article error error
|
||||
done(articleDeleteErr);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
User.remove().exec(function () {
|
||||
Article.remove().exec(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
4
modules/chat/client/chat.client.module.js
Normal file
4
modules/chat/client/chat.client.module.js
Normal file
@@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
// Use Applicaion configuration module to register a new module
|
||||
ApplicationConfiguration.registerModule('chat');
|
||||
12
modules/chat/client/config/chat.client.config.js
Normal file
12
modules/chat/client/config/chat.client.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
// Configuring the Chat module
|
||||
angular.module('chat').run(['Menus',
|
||||
function (Menus) {
|
||||
// Set top bar menu items
|
||||
Menus.addMenuItem('topbar', {
|
||||
title: 'Chat',
|
||||
state: 'chat'
|
||||
});
|
||||
}
|
||||
]);
|
||||
15
modules/chat/client/config/chat.client.routes.js
Normal file
15
modules/chat/client/config/chat.client.routes.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'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',
|
||||
data: {
|
||||
roles: ['user', 'admin']
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
||||
43
modules/chat/client/controllers/chat.client.controller.js
Normal file
43
modules/chat/client/controllers/chat.client.controller.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
// Create the 'chat' controller
|
||||
angular.module('chat').controller('ChatController', ['$scope', '$location', 'Authentication', 'Socket',
|
||||
function ($scope, $location, Authentication, Socket) {
|
||||
// Create a messages array
|
||||
$scope.messages = [];
|
||||
|
||||
// If user is not signed in then redirect back home
|
||||
if (!Authentication.user) {
|
||||
$location.path('/');
|
||||
}
|
||||
|
||||
// Make sure the Socket is connected
|
||||
if (!Socket.socket) {
|
||||
Socket.connect();
|
||||
}
|
||||
|
||||
// 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');
|
||||
});
|
||||
}
|
||||
]);
|
||||
15
modules/chat/client/css/chat.css
Normal file
15
modules/chat/client/css/chat.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.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%;
|
||||
}
|
||||
29
modules/chat/client/views/chat.client.view.html
Normal file
29
modules/chat/client/views/chat.client.view.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!-- The chat view -->
|
||||
<section class="container" data-ng-controller="ChatController">
|
||||
<div class="page-header">
|
||||
<h1>Chat Example</h1>
|
||||
</div>
|
||||
<!-- The message form -->
|
||||
<form class="col-xs-12 col-md-offset-4 col-md-4" ng-submit="sendMessage();">
|
||||
<fieldset class="row">
|
||||
<div class="input-group">
|
||||
<input type="text" id="messageText" name="messageText" class="form-control" data-ng-model="messageText" placeholder="Enter new message">
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<ul class="list-unstyled">
|
||||
<!-- List all messages -->
|
||||
<li class="col-xs-12 col-md-offset-4 col-md-4 chat-message" data-ng-repeat="message in messages">
|
||||
<small class="pull-right text-muted" data-ng-bind="message.created | date:'mediumTime'"></small>
|
||||
<img data-ng-src="{{message.profileImageURL}}" alt="{{message.username}}" class="pull-left chat-profile-image" />
|
||||
<div class="pull-left chat-message-details">
|
||||
<strong data-ng-bind="message.username"></strong>
|
||||
<br>
|
||||
<span data-ng-bind="message.text"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
34
modules/chat/server/sockets/chat.server.socket.config.js
Normal file
34
modules/chat/server/sockets/chat.server.socket.config.js
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
// Create the chat configuration
|
||||
module.exports = function (io, socket) {
|
||||
// Emit the status event when a new socket client is connected
|
||||
io.emit('chatMessage', {
|
||||
type: 'status',
|
||||
text: 'Is now connected',
|
||||
created: Date.now(),
|
||||
profileImageURL: socket.request.user.profileImageURL,
|
||||
username: socket.request.user.username
|
||||
});
|
||||
|
||||
// Send a chat messages to all connected sockets when a message is received
|
||||
socket.on('chatMessage', function (message) {
|
||||
message.type = 'message';
|
||||
message.created = Date.now();
|
||||
message.profileImageURL = socket.request.user.profileImageURL;
|
||||
message.username = socket.request.user.username;
|
||||
|
||||
// Emit the 'chatMessage' event
|
||||
io.emit('chatMessage', message);
|
||||
});
|
||||
|
||||
// Emit the status event when a socket client is disconnected
|
||||
socket.on('disconnect', function () {
|
||||
io.emit('chatMessage', {
|
||||
type: 'status',
|
||||
text: 'disconnected',
|
||||
created: Date.now(),
|
||||
username: socket.request.user.username
|
||||
});
|
||||
});
|
||||
};
|
||||
94
modules/chat/tests/client/chat.client.controller.tests.js
Normal file
94
modules/chat/tests/client/chat.client.controller.tests.js
Normal file
@@ -0,0 +1,94 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Chat client controller tests
|
||||
*/
|
||||
(function () {
|
||||
describe('ChatController', function () {
|
||||
//Initialize global variables
|
||||
var scope,
|
||||
Socket,
|
||||
ChatController,
|
||||
$timeout,
|
||||
$location,
|
||||
Authentication;
|
||||
|
||||
// Load the main application module
|
||||
beforeEach(module(ApplicationConfiguration.applicationModuleName));
|
||||
|
||||
beforeEach(inject(function ($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) {
|
||||
scope = $rootScope.$new();
|
||||
Socket = _Socket_;
|
||||
$timeout = _$timeout_;
|
||||
$location = _$location_;
|
||||
Authentication = _Authentication_;
|
||||
}));
|
||||
|
||||
describe('when user logged out', function () {
|
||||
beforeEach(inject(function ($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) {
|
||||
Authentication.user = undefined;
|
||||
spyOn($location, 'path');
|
||||
ChatController = $controller('ChatController', {
|
||||
$scope: scope,
|
||||
});
|
||||
}));
|
||||
|
||||
it('should redirect logged out user to /', function () {
|
||||
expect($location.path).toHaveBeenCalledWith('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user logged in', function () {
|
||||
beforeEach(inject(function ($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) {
|
||||
Authentication.user = {
|
||||
name: 'user',
|
||||
roles: ['user']
|
||||
};
|
||||
|
||||
ChatController = $controller('ChatController', {
|
||||
$scope: scope,
|
||||
});
|
||||
}));
|
||||
|
||||
it('should make sure socket is connected', function () {
|
||||
expect(Socket.socket).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should define messages array', function () {
|
||||
expect(scope.messages).toBeDefined();
|
||||
expect(scope.messages.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('sendMessage', function () {
|
||||
var text = 'hello world!';
|
||||
beforeEach(function () {
|
||||
scope.messageText = text;
|
||||
scope.sendMessage();
|
||||
$timeout.flush();
|
||||
});
|
||||
|
||||
it('should add message to messages', function () {
|
||||
expect(scope.messages.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should add message with proper text attribute set', function () {
|
||||
expect(scope.messages[0].text).toBe(text);
|
||||
});
|
||||
|
||||
it('should clear messageText', function () {
|
||||
expect(scope.messageText).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$destroy()', function () {
|
||||
beforeEach(function () {
|
||||
scope.$destroy();
|
||||
});
|
||||
|
||||
it('should remove chatMessage listener', function () {
|
||||
expect(Socket.socket.cbs.chatMessage).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}());
|
||||
8
modules/chat/tests/e2e/chat.e2e.tests.js
Normal file
8
modules/chat/tests/e2e/chat.e2e.tests.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Chat e2e tests
|
||||
*/
|
||||
describe('Chat E2E Tests:', function () {
|
||||
// TODO: Add chat e2e tests
|
||||
});
|
||||
8
modules/chat/tests/server/chat.socket.tests.js
Normal file
8
modules/chat/tests/server/chat.socket.tests.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Chat socket tests
|
||||
*/
|
||||
describe('Chat Socket Tests:', function () {
|
||||
// TODO: Add chat socket tests
|
||||
});
|
||||
23
modules/core/client/app/config.js
Normal file
23
modules/core/client/app/config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
// Init the application configuration module for AngularJS application
|
||||
var ApplicationConfiguration = (function () {
|
||||
// Init module configuration options
|
||||
var applicationModuleName = 'mean';
|
||||
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils', 'angularFileUpload'];
|
||||
|
||||
// Add a new vertical module
|
||||
var registerModule = function (moduleName, dependencies) {
|
||||
// Create angular module
|
||||
angular.module(moduleName, dependencies || []);
|
||||
|
||||
// Add the module to the AngularJS configuration file
|
||||
angular.module(applicationModuleName).requires.push(moduleName);
|
||||
};
|
||||
|
||||
return {
|
||||
applicationModuleName: applicationModuleName,
|
||||
applicationModuleVendorDependencies: applicationModuleVendorDependencies,
|
||||
registerModule: registerModule
|
||||
};
|
||||
})();
|
||||
55
modules/core/client/app/init.js
Normal file
55
modules/core/client/app/init.js
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
//Start by defining the main module and adding the module dependencies
|
||||
angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies);
|
||||
|
||||
// Setting HTML5 Location Mode
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider',
|
||||
function ($locationProvider) {
|
||||
$locationProvider.html5Mode(true).hashPrefix('!');
|
||||
}
|
||||
]);
|
||||
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(function ($rootScope, $state, Authentication) {
|
||||
// Check authentication before changing state
|
||||
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
|
||||
if (toState.data && toState.data.roles && toState.data.roles.length > 0) {
|
||||
var allowed = false;
|
||||
toState.data.roles.forEach(function (role) {
|
||||
if (Authentication.user.roles !== undefined && Authentication.user.roles.indexOf(role) !== -1) {
|
||||
allowed = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!allowed) {
|
||||
event.preventDefault();
|
||||
$state.go('authentication.signin', {}, {
|
||||
notify: false
|
||||
}).then(function () {
|
||||
$rootScope.$broadcast('$stateChangeSuccess', 'authentication.signin', {}, toState, toParams);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Record previous state
|
||||
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
|
||||
$state.previous = {
|
||||
state: fromState,
|
||||
params: fromParams,
|
||||
href: $state.href(fromState, fromParams)
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
//Then define the init function for starting up the application
|
||||
angular.element(document).ready(function () {
|
||||
//Fixing facebook bug with redirect
|
||||
if (window.location.hash === '#_=_') {
|
||||
window.location.hash = '#!';
|
||||
}
|
||||
|
||||
//Then init the app
|
||||
angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]);
|
||||
});
|
||||
12
modules/core/client/config/core-admin.client.menus.js
Normal file
12
modules/core/client/config/core-admin.client.menus.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('core.admin').run(['Menus',
|
||||
function (Menus) {
|
||||
Menus.addMenuItem('topbar', {
|
||||
title: 'Admin',
|
||||
state: 'admin',
|
||||
type: 'dropdown',
|
||||
roles: ['admin']
|
||||
});
|
||||
}
|
||||
]);
|
||||
16
modules/core/client/config/core-admin.client.routes.js
Normal file
16
modules/core/client/config/core-admin.client.routes.js
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
// Setting up route
|
||||
angular.module('core.admin.routes').config(['$stateProvider',
|
||||
function ($stateProvider) {
|
||||
$stateProvider
|
||||
.state('admin', {
|
||||
abstract: true,
|
||||
url: '/admin',
|
||||
template: '<ui-view/>',
|
||||
data: {
|
||||
roles: ['admin']
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
||||
21
modules/core/client/config/core.client.routes.js
Normal file
21
modules/core/client/config/core.client.routes.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
// Setting up route
|
||||
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
|
||||
function ($stateProvider, $urlRouterProvider) {
|
||||
|
||||
// Redirect to 404 when route not found
|
||||
$urlRouterProvider.otherwise('not-found');
|
||||
|
||||
// Home state routing
|
||||
$stateProvider
|
||||
.state('home', {
|
||||
url: '/',
|
||||
templateUrl: 'modules/core/views/home.client.view.html'
|
||||
})
|
||||
.state('not-found', {
|
||||
url: '/not-found',
|
||||
templateUrl: 'modules/core/views/404.client.view.html'
|
||||
});
|
||||
}
|
||||
]);
|
||||
23
modules/core/client/controllers/header.client.controller.js
Normal file
23
modules/core/client/controllers/header.client.controller.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('core').controller('HeaderController', ['$scope', '$state', 'Authentication', 'Menus',
|
||||
function ($scope, $state, Authentication, Menus) {
|
||||
// Expose view variables
|
||||
$scope.$state = $state;
|
||||
$scope.authentication = Authentication;
|
||||
|
||||
// Get the topbar menu
|
||||
$scope.menu = Menus.getMenu('topbar');
|
||||
|
||||
// Toggle the menu items
|
||||
$scope.isCollapsed = false;
|
||||
$scope.toggleCollapsibleMenu = function () {
|
||||
$scope.isCollapsed = !$scope.isCollapsed;
|
||||
};
|
||||
|
||||
// Collapsing the menu after navigation
|
||||
$scope.$on('$stateChangeSuccess', function () {
|
||||
$scope.isCollapsed = false;
|
||||
});
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('core').controller('HomeController', ['$scope', 'Authentication',
|
||||
function ($scope, Authentication) {
|
||||
// This provides Authentication context.
|
||||
$scope.authentication = Authentication;
|
||||
}
|
||||
]);
|
||||
6
modules/core/client/core.client.module.js
Normal file
6
modules/core/client/core.client.module.js
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
// Use Applicaion configuration module to register a new module
|
||||
ApplicationConfiguration.registerModule('core');
|
||||
ApplicationConfiguration.registerModule('core.admin', ['core']);
|
||||
ApplicationConfiguration.registerModule('core.admin.routes', ['ui.router']);
|
||||
35
modules/core/client/css/core.css
Normal file
35
modules/core/client/css/core.css
Normal file
@@ -0,0 +1,35 @@
|
||||
.content {
|
||||
margin-top: 50px;
|
||||
}
|
||||
.undecorated-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
[ng\:cloak],
|
||||
[ng-cloak],
|
||||
[data-ng-cloak],
|
||||
[x-ng-cloak],
|
||||
.ng-cloak,
|
||||
.x-ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
.ng-invalid.ng-dirty {
|
||||
border-color: #FA787E;
|
||||
}
|
||||
.ng-valid.ng-dirty {
|
||||
border-color: #78FA89;
|
||||
}
|
||||
.header-profile-image {
|
||||
opacity: 0.8;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.open .header-profile-image,
|
||||
a:hover .header-profile-image {
|
||||
opacity: 1;
|
||||
}
|
||||
.user-header-dropdown-toggle {
|
||||
padding-top: 11px !important;
|
||||
padding-bottom: 11px !important;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user