diff --git a/README.md b/README.md index a860565..73cd0ef 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Bump versions of `package.json` and `bower.json` files using *Semantic Versionin - [filter](https://github.com/42Zavattas/generator-bangular#filter) - [font](https://github.com/42Zavattas/generator-bangular#font) - [route](https://github.com/42Zavattas/generator-bangular#route) - - [service](https://github.com/42Zavattas/generator-bangular#service) + - [service / factory](https://github.com/42Zavattas/generator-bangular#service) - [style](https://github.com/42Zavattas/generator-bangular#style) - Server - [api](https://github.com/42Zavattas/generator-bangular#api) @@ -117,8 +117,9 @@ The **name** parameter is required. This will create in `client/views/`: ## Service yo bangular:service + yo bangular:factory -The **name** parameter is required. The service and its spec file will be generated at `client/services/`. +The **name** parameter is required. The service / factory and its spec file will be generated at `client/services/`. ## Style diff --git a/app/index.js b/app/index.js index 009bbc7..f3d1bb5 100644 --- a/app/index.js +++ b/app/index.js @@ -99,13 +99,22 @@ var BangularGenerator = yeoman.generators.Base.extend({ } if (props.backend === 'mongo') { - self.prompt({ + self.prompt([{ type: 'confirm', name: 'sockets', message: 'Do you want to add socket support?', default: false - }, function (props) { + }, { + type: 'confirm', + name: 'auth', + message: 'Do you want to scaffold a passport authentication process?', + default: false + }], function (props) { self.filters.sockets = props.sockets; + self.filters.auth = props.auth; + if (props.auth) { + self.filters['ngCookies'] = true; + } done(); }); } else { diff --git a/app/templates/#.jshintrc b/app/templates/#.jshintrc index 86d4b8f..2166f80 100644 --- a/app/templates/#.jshintrc +++ b/app/templates/#.jshintrc @@ -17,7 +17,6 @@ "undef": true, "unused": true, "strict": true, - "maxparams": 3, "maxdepth": 3, "maxlen": 80, @@ -35,6 +34,8 @@ "describe": true, "inject": true, "expect": true, + "afterEach": true, + "alert": true, "beforeEach": true } } diff --git a/app/templates/client/app.js b/app/templates/client/app.js index 0264412..e7f2d13 100644 --- a/app/templates/client/app.js +++ b/app/templates/client/app.js @@ -8,13 +8,45 @@ angular.module('<%= appname %>', [ 'ngAnimate'<% } %><% if (filters.sockets) { %>, 'btford.socket-io'<% } %> ]) - .config(function ($routeProvider, $locationProvider) { + .config(function ($routeProvider, $locationProvider<% if (filters.auth) { %>, $httpProvider<% } %>) { $routeProvider .otherwise({ redirectTo: '/' }); - $locationProvider.html5Mode(true); + $locationProvider.html5Mode(true);<% if (filters.auth) { %> + $httpProvider.interceptors.push('authInterceptor');<% } %> - }); + })<% if (filters.auth) { %> + .factory('authInterceptor', + function ($rootScope, $q, $cookieStore, $location) { + return { + + request: function (config) { + config.headers = config.headers || {}; + if ($cookieStore.get('token')) { + config.headers.Authorization = 'Bearer ' + $cookieStore.get('token'); + } + return config; + }, + + responseError: function (response) { + if (response.status === 401) { + $location.path('/login'); + $cookieStore.remove('token'); + return $q.reject(response); + } + else { + return $q.reject(response); + } + } + + }; + }) + + .run(function ($rootScope, Auth) { + + $rootScope.Auth = Auth; + + })<% } %>; diff --git a/app/templates/client/index.html b/app/templates/client/index.html index 05ec8c2..8688525 100644 --- a/app/templates/client/index.html +++ b/app/templates/client/index.html @@ -22,12 +22,32 @@ - <%= appname %> + <%= appname %><% if (filters.auth) { %> + + + +
logged in: {{ Auth.isLogged() }}
<% } %> + +
<% if (filters.sockets) { %> -
- - <% if (filters.sockets) { %> <% } %> + + @@ -35,7 +55,7 @@ - + diff --git a/app/templates/client/karma.conf.js b/app/templates/client/karma.conf.js index 337d1ce..950ae09 100644 --- a/app/templates/client/karma.conf.js +++ b/app/templates/client/karma.conf.js @@ -37,7 +37,11 @@ module.exports = function (config) { 'directives/**/*.js', 'directives/**/*.html', 'filters/**/*.js' - ], + ],<% if (filters.sockets) { %> + + exclude: [ + 'services/socket/socket.service.js', + ],<% } %> reporters: ['progress'], diff --git a/app/templates/client/services/auth(auth)/auth.js b/app/templates/client/services/auth(auth)/auth.js new file mode 100644 index 0000000..fb7f986 --- /dev/null +++ b/app/templates/client/services/auth(auth)/auth.js @@ -0,0 +1,84 @@ +'use strict'; + +angular.module('<%= appname %>') + .service('Auth', function ($rootScope, $cookieStore, $q, $http) { + + var _user = {}; + + if($cookieStore.get('token')) { + $http.get('/api/users/me') + .then(function (res) { + _user = res.data; + }) + .catch(function (err) { + console.log(err); + }); + } + + /** + * Signup + * + * @param user + * @returns {promise} + */ + this.signup = function (user) { + var deferred = $q.defer(); + $http.post('/api/users', user) + .then(function (res) { + _user = res.data.user; + $cookieStore.put('token', res.data.token); + deferred.resolve(); + }) + .catch(function (err) { + deferred.reject(err.data); + }); + return deferred.promise; + }; + + /** + * Login + * + * @param user + * @returns {promise} + */ + this.login = function (user) { + var deferred = $q.defer(); + $http.post('/auth/local', user) + .then(function (res) { + _user = res.data.user; + $cookieStore.put('token', res.data.token); + deferred.resolve(); + }) + .catch(function (err) { + deferred.reject(err.data); + }); + return deferred.promise; + }; + + /** + * Logout + */ + this.logout = function () { + $cookieStore.remove('token'); + _user = {}; + }; + + /** + * Check if user is logged + * + * @returns {boolean} + */ + this.isLogged = function () { + return _user.hasOwnProperty('email'); + }; + + /** + * Returns the user + * + * @returns {object} + */ + this.getUser = function () { + return _user; + }; + + }); diff --git a/app/templates/client/services/auth(auth)/auth.spec.js b/app/templates/client/services/auth(auth)/auth.spec.js new file mode 100644 index 0000000..a326dbf --- /dev/null +++ b/app/templates/client/services/auth(auth)/auth.spec.js @@ -0,0 +1,34 @@ +'use strict'; + +describe('Service: Auth', function () { + + beforeEach(module('<%= appname %>')); + + var Auth, + $httpBackend, + $cookieStore; + + beforeEach(inject(function (_Auth_, _$httpBackend_, _$cookieStore_) { + Auth = _Auth_; + $httpBackend = _$httpBackend_; + $cookieStore = _$cookieStore_; + $cookieStore.remove('token'); + })); + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + it('should log user', function () { + expect(Auth.isLogged()).toBe(false); + Auth.login({ email: 'test@test.com', password: 'test' }); + $httpBackend.expectPOST('/auth/local') + .respond({ token: 'abcde', user: { email: 'test@test.com' } }); + $httpBackend.flush(); + expect($cookieStore.get('token')).toBe('abcde'); + expect(Auth.getUser().email).toBe('test@test.com'); + expect(Auth.isLogged()).toBe(true); + }); + +}); diff --git a/app/templates/client/services/socket(sockets)/socket.mock.js b/app/templates/client/services/socket(sockets)/socket.mock.js new file mode 100644 index 0000000..890dde9 --- /dev/null +++ b/app/templates/client/services/socket(sockets)/socket.mock.js @@ -0,0 +1,17 @@ +'use strict'; + +angular.module('<%= appname %>') + .factory('Socket', function (socketFactory) { + + return { + socket: { + connect: angular.noop, + on: angular.noop, + emit: angular.noop, + receive: angular.noop + }, + syncModel: angular.noop, + unsyncModel: angular.noop + }; + + }); diff --git a/app/templates/client/views/home/home.spec.js b/app/templates/client/views/home/home.spec.js index 1a6b743..64a2535 100644 --- a/app/templates/client/views/home/home.spec.js +++ b/app/templates/client/views/home/home.spec.js @@ -1,6 +1,6 @@ 'use strict'; -describe('HomeCtrl', function () { +describe('Controller: HomeCtrl', function () { beforeEach(module('<%= appname %>')); diff --git a/app/templates/client/views/login(auth)/login.controller.js b/app/templates/client/views/login(auth)/login.controller.js new file mode 100644 index 0000000..65b46b0 --- /dev/null +++ b/app/templates/client/views/login(auth)/login.controller.js @@ -0,0 +1,32 @@ +'use strict'; + +angular.module('<%= appname %>') + .controller('LoginCtrl', function (Auth, $location) { + + var vm = this; + + angular.extend(vm, { + + name: 'LoginCtrl', + + /** + * User credentials + */ + user: { email: 'test@test.com', password: 'test' }, + + /** + * Login method + */ + login: function () { + Auth.login(vm.user) + .then(function () { + $location.path('/'); + }) + .catch(function (err) { + vm.error = err; + }); + } + + }); + + }); diff --git a/app/templates/client/views/login(auth)/login.html b/app/templates/client/views/login(auth)/login.html new file mode 100644 index 0000000..3826557 --- /dev/null +++ b/app/templates/client/views/login(auth)/login.html @@ -0,0 +1,17 @@ +
+ {{ vm.name }} +
+ +
+ + + +
+ +
{{ vm.error | json }}
diff --git a/app/templates/client/views/login(auth)/login.js b/app/templates/client/views/login(auth)/login.js new file mode 100644 index 0000000..1680165 --- /dev/null +++ b/app/templates/client/views/login(auth)/login.js @@ -0,0 +1,11 @@ +'use strict'; + +angular.module('<%= appname %>') + .config(function ($routeProvider) { + $routeProvider + .when('/login', { + templateUrl: 'views/login/login.html', + controller: 'LoginCtrl', + controllerAs: 'vm' + }); + }); diff --git a/app/templates/client/views/login(auth)/login.spec.js b/app/templates/client/views/login(auth)/login.spec.js new file mode 100644 index 0000000..3abcd57 --- /dev/null +++ b/app/templates/client/views/login(auth)/login.spec.js @@ -0,0 +1,30 @@ +'use strict'; + +describe('Controller: LoginCtrl', function () { + + beforeEach(module('<%= appname %>')); + + var LoginCtrl, + $httpBackend, + $location; + + beforeEach(inject(function ($controller, _$httpBackend_, _$location_) { + LoginCtrl = $controller('LoginCtrl', {}); + $httpBackend = _$httpBackend_; + $location = _$location_; + })); + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + it('should redirect to / after successful login', function () { + LoginCtrl.login({ email: 'test@test.com', password: 'test' }); + $httpBackend.expectPOST('/auth/local') + .respond({ token: 'token' }); + $httpBackend.flush(); + expect($location.path()).toBe('/'); + }); + +}); diff --git a/app/templates/client/views/signup(auth)/signup.controller.js b/app/templates/client/views/signup(auth)/signup.controller.js new file mode 100644 index 0000000..1181299 --- /dev/null +++ b/app/templates/client/views/signup(auth)/signup.controller.js @@ -0,0 +1,32 @@ +'use strict'; + +angular.module('<%= appname %>') + .controller('SignupCtrl', function (Auth, $location) { + + var vm = this; + + angular.extend(vm, { + + name: 'SignupCtrl', + + /** + * User credentials + */ + user: { email: 'test@test.com', password: 'test' }, + + /** + * Signup + */ + signup: function () { + Auth.signup(vm.user) + .then(function () { + $location.path('/'); + }) + .catch(function (err) { + vm.error = err; + }); + } + + }); + + }); diff --git a/app/templates/client/views/signup(auth)/signup.html b/app/templates/client/views/signup(auth)/signup.html new file mode 100644 index 0000000..6fa6a18 --- /dev/null +++ b/app/templates/client/views/signup(auth)/signup.html @@ -0,0 +1,17 @@ +
+ {{ vm.name }} +
+ +
+ + + +
+ +
{{ vm.error | json }}
diff --git a/app/templates/client/views/signup(auth)/signup.js b/app/templates/client/views/signup(auth)/signup.js new file mode 100644 index 0000000..c36684b --- /dev/null +++ b/app/templates/client/views/signup(auth)/signup.js @@ -0,0 +1,11 @@ +'use strict'; + +angular.module('<%= appname %>') + .config(function ($routeProvider) { + $routeProvider + .when('/signup', { + templateUrl: 'views/signup/signup.html', + controller: 'SignupCtrl', + controllerAs: 'vm' + }); + }); diff --git a/app/templates/client/views/signup(auth)/signup.spec.js b/app/templates/client/views/signup(auth)/signup.spec.js new file mode 100644 index 0000000..8a1872c --- /dev/null +++ b/app/templates/client/views/signup(auth)/signup.spec.js @@ -0,0 +1,17 @@ +'use strict'; + +describe('Controller: SignupCtrl', function () { + + beforeEach(module('<%= appname %>')); + + var SignupCtrl; + + beforeEach(inject(function ($controller) { + SignupCtrl = $controller('SignupCtrl', {}); + })); + + it('should ...', function () { + expect(1).toBe(1); + }); + +}); diff --git a/app/templates/gulpfile.js b/app/templates/gulpfile.js index 1a5d5a1..7ddfcb1 100644 --- a/app/templates/gulpfile.js +++ b/app/templates/gulpfile.js @@ -11,7 +11,7 @@ var fs = require('fs'); var karma = require('karma').server; var $ = require('gulp-load-plugins')(); -process.env.NODE_ENV = process.env.NODE_ENV || 'development' +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; var config = require('./server/config/environment'); @@ -162,18 +162,18 @@ function testClient (done) { gulp.task('test', function (done) { process.env.NODE_ENV = 'test'; - var filter = process.argv[3] ? process.argv[3].substr(2) : false; - if (filter === 'client') { + var arg = process.argv[3] ? process.argv[3].substr(2) : false; + if (arg === 'client') { return testClient(function () { process.exit(); done(); }); - } else if (filter === 'server') { + } else if (arg === 'server') { return testServer(function () { process.exit(); done(); }); - } else if (filter === false) { + } else if (arg === false) { return testClient(function () { testServer(function () { process.exit(); @@ -181,7 +181,7 @@ gulp.task('test', function (done) { }); }); } else { - console.log('Wrong parameter [%s], availables : --client, --server', filter); + console.log('Wrong parameter [%s], availables : --client, --server', arg); } }); @@ -189,7 +189,11 @@ gulp.task('test', function (done) { * Launch server */ gulp.task('serve', ['watch'], function () { - return $.nodemon({ script: 'server/server.js', ext: 'js', ignore: ['client', 'dist', 'node_modules'] }) + return $.nodemon({ + script: 'server/server.js', + ext: 'js', + ignore: ['client', 'dist', 'node_modules'] + }) .on('start', function () { if (!openOpts.already) { openOpts.already = true; @@ -264,7 +268,7 @@ gulp.task('scripts', function () { gulp.task('replace', function () { return gulp.src('dist/client/index.html') - .pipe($.replace(/<\/script>\n*/, '')) + .pipe($.replace(/ <\/script>\n*/, '')) .pipe(gulp.dest('dist/client')); }); @@ -279,7 +283,7 @@ gulp.task('rev', function () { return path.basename(file.path, ext) + '.' + hash.substr(0, 8) + ext; } })) - .pipe(gulp.dest('dist/client/')) + .pipe(gulp.dest('dist/client/')); }); gulp.task('build', function (cb) { @@ -298,16 +302,18 @@ gulp.task('build', function (cb) { gulp.task('version', function () { return gulp.src(['./package.json', './bower.json']) - .pipe($.bump({ type: process.argv[3] ? process.argv[3].substr(2) : 'patch' })) + .pipe($.bump({ + type: process.argv[3] ? process.argv[3].substr(2) : 'patch' + })) .pipe(gulp.dest('./')); }); gulp.task('bump', ['version'], function () { fs.readFile('./package.json', function (err, data) { - if (err) { return; } + if (err) { return ; } return gulp.src(['./package.json', './bower.json']) .pipe($.git.add()) - .pipe($.git.commit('chore(core): bump to ' + JSON.parse(data.toString()).version)); + .pipe($.git.commit('chore(core): bump to ' + JSON.parse(data).version)); }); }); diff --git a/app/templates/package.json b/app/templates/package.json index fd0e191..7d0e703 100644 --- a/app/templates/package.json +++ b/app/templates/package.json @@ -37,14 +37,21 @@ }, "dependencies": { "body-parser": "^1.11.0", - "chalk": "^0.5.1", + "chalk": "^0.5.1",<% if (filters.auth) { %> + "composable-middleware": "^0.3.0",<% } %> "compression": "^1.4.0", - "cookie-parser": "^1.3.3", - "express": "^4.11.2", + "cookie-parser": "^1.3.3",<% if (filters.auth) { %> + "connect-mongo": "^0.7.0",<% } %> + "express": "^4.11.2",<% if (filters.auth) { %> + "express-jwt": "^1.0.0", + "express-session": "^1.10.2", + "jsonwebtoken": "^3.2.2",<% } %> "lodash": "^3.0.1", "method-override": "^2.3.1",<% if (filters.backend === 'mongo') { %> "mongoose": "^3.8.22",<% } %> - "morgan": "^1.5.1",<% if (filters.backend === 'restock') { %> + "morgan": "^1.5.1",<% if (filters.auth) { %> + "passport": "^0.2.1", + "passport-local": "^1.0.0",<% } %><% if (filters.backend === 'restock') { %> "request": "^2.51.1",<% } %><% if (filters.sockets) { %> "socket.io": "^1.3.2",<% } %> "should": "^4.6.2", diff --git a/app/templates/server/api/user(auth)/index.js b/app/templates/server/api/user(auth)/index.js new file mode 100644 index 0000000..6af901d --- /dev/null +++ b/app/templates/server/api/user(auth)/index.js @@ -0,0 +1,11 @@ +'use strict'; + +var express = require('express'); +var router = express.Router(); +var controller = require('./user.controller'); +var auth = require('../../auth/auth.service'); + +router.get('/me', auth.isAuthenticated(), controller.getMe); +router.post('/', controller.create); + +module.exports = router; diff --git a/app/templates/server/api/user(auth)/user.controller.js b/app/templates/server/api/user(auth)/user.controller.js new file mode 100644 index 0000000..750e35d --- /dev/null +++ b/app/templates/server/api/user(auth)/user.controller.js @@ -0,0 +1,38 @@ +'use strict'; + +var config = require('../../config/environment'); +var jwt = require('jsonwebtoken'); +var User = require('./user.model'); + +function handleError(res, err) { + return res.status(500).send(err); +} + +/** + * Creates a new user in the DB. + * + * @param req + * @param res + */ +exports.create = function (req, res) { + User.create(req.body, function (err, user) { + if (err) { return handleError(res, err); } + var token = jwt.sign( + {_id: user._id }, + config.secrets.session, + { expiresInMinutes: 60 * 5 } + ); + res.status(201).json({ token: token, user: user }); + }); +}; + +exports.getMe = function (req, res) { + var userId = req.user._id; + User.findOne({ + _id: userId + }, '-salt -passwordHash', function(err, user) { + if (err) { return handleError(res, err); } + if (!user) { return res.json(401); } + res.status(200).json(user); + }); +}; diff --git a/app/templates/server/api/user(auth)/user.model.js b/app/templates/server/api/user(auth)/user.model.js new file mode 100644 index 0000000..3321b63 --- /dev/null +++ b/app/templates/server/api/user(auth)/user.model.js @@ -0,0 +1,85 @@ +'use strict'; + +var crypto = require('crypto'); +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; + +var UserSchema = new Schema({ + email: String, + passwordHash: String, + salt: String +}); + +/** + * Virtuals + */ + +UserSchema + .virtual('password') + .set(function(password) { + this._password = password; + this.salt = this.makeSalt(); + this.passwordHash = this.encryptPassword(password); + }) + .get(function() { + return this._password; + }); + +/** + * Validations + */ + +UserSchema + .path('email') + .validate(function(value, respond) { + var self = this; + this.constructor.findOne({ email: value }, function(err, user) { + if (err) { throw err; } + if (user) { + if (self.id === user.id) { return respond(true); } + return respond(false); + } + respond(true); + }); + }, 'email already used'); + +/** + * Methods + */ + +UserSchema.methods = { + + /** + * Authenticate + * + * @param {String} password + * @return {Boolean} + */ + authenticate: function(password) { + return this.encryptPassword(password) === this.passwordHash; + }, + + /** + * Make salt + * + * @return {String} + */ + makeSalt: function() { + return crypto.randomBytes(16).toString('base64'); + }, + + /** + * Encrypt password + * + * @param {String} password + * @return {String} + */ + encryptPassword: function(password) { + if (!password || !this.salt) { return ''; } + var salt = new Buffer(this.salt, 'base64'); + return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); + } + +}; + +module.exports = mongoose.model('User', UserSchema); diff --git a/app/templates/server/auth(auth)/auth.service.js b/app/templates/server/auth(auth)/auth.service.js new file mode 100644 index 0000000..d317f20 --- /dev/null +++ b/app/templates/server/auth(auth)/auth.service.js @@ -0,0 +1,42 @@ +'use strict'; + +var config = require('../config/environment'); +var jwt = require('jsonwebtoken'); +var expressJwt = require('express-jwt'); +var compose = require('composable-middleware'); +var User = require('../api/user/user.model'); +var validateJwt = expressJwt({ secret: config.secrets.session }); + +module.exports = { + + /** + * Attach the user object to the request if authenticated + * Otherwise returns 403 + */ + isAuthenticated: function () { + return compose() + .use(function(req, res, next) { + validateJwt(req, res, next); + }) + .use(function(req, res, next) { + User.findById(req.user._id, function (err, user) { + if (err) { return next(err); } + if (!user) { return res.send(401); } + req.user = user; + next(); + }); + }); + }, + + /** + * Returns a jwt token, signed by the app secret + */ + signToken: function (id) { + return jwt.sign( + { _id: id }, + config.secrets.session, + { expiresInMinutes: 60 * 5 } + ); + } + +}; diff --git a/app/templates/server/auth(auth)/index.js b/app/templates/server/auth(auth)/index.js new file mode 100644 index 0000000..969eb6e --- /dev/null +++ b/app/templates/server/auth(auth)/index.js @@ -0,0 +1,12 @@ +'use strict'; + +var express = require('express'); +var router = express.Router(); +var config = require('../config/environment'); +var User = require('../api/user/user.model'); + +require('./local/passport').setup(User, config); + +router.use('/local', require('./local')); + +module.exports = router; diff --git a/app/templates/server/auth(auth)/local/index.js b/app/templates/server/auth(auth)/local/index.js new file mode 100644 index 0000000..72e8781 --- /dev/null +++ b/app/templates/server/auth(auth)/local/index.js @@ -0,0 +1,19 @@ +'use strict'; + +var express = require('express'); +var passport = require('passport'); +var auth = require('../auth.service'); + +var router = express.Router(); + +router.post('/', function (req, res, next) { + passport.authenticate('local', function (err, user, info) { + var error = err || info; + if (error) { return res.status(401).json(error); } + if (!user) { return res.status(401).json({ msg: 'login failed' }); } + var token = auth.signToken(user._id); + res.json({ token: token, user: user }); + })(req, res, next); +}); + +module.exports = router; diff --git a/app/templates/server/auth(auth)/local/passport.js b/app/templates/server/auth(auth)/local/passport.js new file mode 100644 index 0000000..9b47f18 --- /dev/null +++ b/app/templates/server/auth(auth)/local/passport.js @@ -0,0 +1,24 @@ +'use strict'; + +var passport = require('passport'); +var LocalStrategy = require('passport-local').Strategy; + +exports.setup = function (User) { + passport.use(new LocalStrategy({ + usernameField: 'email', + passwordField: 'password' + }, + function(email, password, done) { + User.findOne({ + email: email + }, function(err, user) { + if (err) { return done(err); } + if (!user) { return done(null, false, { msg: 'email not found' }); } + if (!user.authenticate(password)) { + return done(null, false, { msg: 'incorrect password' }); + } + return done(null, user); + }); + } + )); +}; diff --git a/app/templates/server/config/#express.js b/app/templates/server/config/#express.js deleted file mode 100644 index e693159..0000000 --- a/app/templates/server/config/#express.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -var express = require('express'); -var compression = require('compression'); -var morgan = require('morgan'); -var path = require('path'); -var bodyParser = require('body-parser'); - -var config = require('./environment'); - -module.exports = function (app) { - - var env = config.env; - - app.set('view engine', 'html'); - app.use(bodyParser.urlencoded({ extended: false })); - app.use(bodyParser.json()); - app.use(compression()); - app.use(morgan('dev')); - app.use(express.static(path.join(config.root, 'client'))); - app.set('appPath', 'client'); - - if ('development' === env || 'test' === env) { - app.use(require('errorhandler')()); - } - -}; diff --git a/app/templates/server/config/environment/index.js b/app/templates/server/config/environment/index.js index 5cff782..03bcc2d 100644 --- a/app/templates/server/config/environment/index.js +++ b/app/templates/server/config/environment/index.js @@ -9,13 +9,16 @@ var all = { root: path.normalize(__dirname + '/../../..'), port: process.env.PORT || 9000,<% if (filters.backend === 'mongo') { %> - // MongoDB connection options mongo: { options: { db: { safe: true } } + }<% } %><% if (filters.auth) { %>, + + secrets: { + session: 'zavatta' || process.env.SESSION_SECRET }<% } %> }; diff --git a/app/templates/server/config/environment/production.js b/app/templates/server/config/environment/production.js index e05429f..9be3344 100644 --- a/app/templates/server/config/environment/production.js +++ b/app/templates/server/config/environment/production.js @@ -3,8 +3,6 @@ module.exports = { ip : process.env.IP || undefined<% if (filters.backend === 'mongo') { %>, mongo: { - uri: process.env.MONGOLAB_URI || - process.env.MONGOHQ_URL || - 'mongodb://localhost/<%= _.slugify(appname) %>' + uri: 'mongodb://localhost/<%= _.slugify(appname) %>' }<% } %> }; diff --git a/app/templates/server/config/express.js b/app/templates/server/config/express.js new file mode 100644 index 0000000..137a26f --- /dev/null +++ b/app/templates/server/config/express.js @@ -0,0 +1,41 @@ +'use strict'; + +var express = require('express'); +var compression = require('compression'); +var morgan = require('morgan'); +var path = require('path'); +var bodyParser = require('body-parser');<% if (filters.auth) { %> + +// auth purpose +var session = require('express-session'); +var passport = require('passport'); +var mongoStore = require('connect-mongo')(session); +var mongoose = require('mongoose');<% } %> + +var config = require('./environment'); + +module.exports = function (app) { + + var env = config.env; + + app.set('view engine', 'html'); + app.use(bodyParser.urlencoded({ extended: false })); + app.use(bodyParser.json()); + app.use(compression()); + app.use(morgan('dev'));<% if (filters.auth) { %> + app.use(passport.initialize());<% } %> + app.use(express.static(path.join(config.root, 'client'))); + app.set('appPath', 'client');<% if (filters.auth) { %> + + app.use(session({ + secret: config.secrets.session, + resave: true, + saveUninitialized: true, + store: new mongoStore({ mongooseConnection: mongoose.connection }) + }));<% } %> + + if ('development' === env || 'test' === env) { + app.use(require('errorhandler')()); + } + +}; diff --git a/app/templates/server/#routes.js b/app/templates/server/routes.js similarity index 73% rename from app/templates/server/#routes.js rename to app/templates/server/routes.js index 6f129f3..d302038 100644 --- a/app/templates/server/#routes.js +++ b/app/templates/server/routes.js @@ -4,7 +4,11 @@ var config = require('./config/environment'); module.exports = function (app) { - // API + // API<% if (filters.auth) { %> + app.use('/api/users', require('./api/user')); + + // Auth + app.use('/auth', require('./auth'));<% } %> app.route('/:url(api|app|bower_components|assets)/*') .get(function (req, res) { diff --git a/factory/index.js b/factory/index.js new file mode 100644 index 0000000..95a253c --- /dev/null +++ b/factory/index.js @@ -0,0 +1,37 @@ +'use strict'; + +var yeoman = require('yeoman-generator'); + +var BangularGenerator = yeoman.generators.NamedBase.extend({ + + initializing: function () { + this.camelName = this._.capitalize(this._.camelize(this.name, true)); + // TODO use _.decapitalize instead of this trick when yeoman-generator will update its undercore.string dependency... + this.dashName = this._.dasherize(this.name.substr(0, 1).toLowerCase() + this.name.substr(1)); + }, + + writing: function () { + + this.template( + 'factory.js', + 'client/services/' + + this.dashName + + '/' + + this.dashName + + '.js' + ); + + this.template( + 'spec.js', + 'client/services/' + + this.dashName + + '/' + + this.dashName + + '.spec.js' + ); + + } + +}); + +module.exports = BangularGenerator; diff --git a/factory/templates/factory.js b/factory/templates/factory.js new file mode 100644 index 0000000..398aded --- /dev/null +++ b/factory/templates/factory.js @@ -0,0 +1,9 @@ +'use strict'; + +angular.module('<%= appname %>') + .factory('<%= camelName %>', function () { + + return { + }; + + }); diff --git a/factory/templates/spec.js b/factory/templates/spec.js new file mode 100644 index 0000000..0895d0f --- /dev/null +++ b/factory/templates/spec.js @@ -0,0 +1,17 @@ +'use strict'; + +describe('Factory: <%= camelName %>', function () { + + beforeEach(module('<%= appname %>')); + + var <%= camelName %>; + + beforeEach(inject(function (_<%= camelName %>_) { + <%= camelName %> = _<%= camelName %>_; + })); + + it('should ...', function () { + expect(1).toBe(1); + }); + +}); diff --git a/gulpfile.js b/gulpfile.js index 0bc309b..38af22e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,6 +16,7 @@ gulp.task('logos', function (done) { 'logos/angular.png', 'logos/node.png', 'logos/socket.png', + 'logos/passport.png', 'logos/express.png', 'logos/mongo.png', 'logos/sass.png', diff --git a/logos/logos-sprite.png b/logos/logos-sprite.png index 4c7a407..024ab01 100644 Binary files a/logos/logos-sprite.png and b/logos/logos-sprite.png differ diff --git a/logos/passport.png b/logos/passport.png new file mode 100644 index 0000000..56c30fd Binary files /dev/null and b/logos/passport.png differ diff --git a/package.json b/package.json index ed16595..2231c3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "generator-bangular", - "version": "0.6.0", + "version": "0.7.0", "description": "Generate and serve your project in a blink of an eye", "license": "BSD", "homepage": "https://github.com/42Zavattas/generator-bangular", @@ -21,13 +21,14 @@ ], "keywords": [ "fullstack", - "yeoman-generator", "gulp", + "yeoman-generator", "bangular-generator", "bangular", "angular", "server", - "generator", + "sockets", + "passport", "lightweight", "blazingfast", "express", diff --git a/route/templates/spec.js b/route/templates/spec.js index 2d4dd90..9783d3c 100644 --- a/route/templates/spec.js +++ b/route/templates/spec.js @@ -4,14 +4,10 @@ describe('Controller: <%= controllerName %>', function () { beforeEach(module('<%= appname %>')); - var <%= controllerName %>, - scope; + var <%= controllerName %>; - beforeEach(inject(function ($controller, $rootScope) { - scope = $rootScope.$new(); - <%= controllerName %> = $controller('<%= controllerName %>', { - $scope: scope - }); + beforeEach(inject(function ($controller) { + <%= controllerName %> = $controller('<%= controllerName %>', {}); })); it('should ...', function () { diff --git a/test/test-app.js b/test/test-app.js index d54e39b..a8d6784 100644 --- a/test/test-app.js +++ b/test/test-app.js @@ -210,4 +210,41 @@ describe('Launching app generator tests', function () { }); + describe('', function () { + + before(function (done) { + + helpers.run(path.join(__dirname, '../app')) + .inDir(path.join(os.tmpdir(), './tmp')) + .withOptions({ 'skipInstall': true }) + .withPrompt({ + name: 'Test', + backend: 'mongo', + modules: [], + sockets: true, + auth: true + }) + .on('end', done); + }); + + it('should test for auth files', function () { + assert.file([ + 'client/views/signup', + 'client/views/login', + 'client/services/auth', + 'server/auth', + ]); + + assert.fileContent('server/routes.js', 'app.use(\'/auth\', require(\'./auth\'));'); + assert.fileContent('server/config/express.js', 'express-session'); + assert.fileContent('server/config/express.js', 'app.use(session({'); + assert.fileContent('package.json', 'passport'); + assert.fileContent('package.json', 'passport-local'); + assert.fileContent('package.json', 'connect-mongo'); + + assert.fileContent('client/index.html', 'signup'); + assert.fileContent('bower.json', 'angular-cookies'); + }); + }); + }); diff --git a/test/test-factory.js b/test/test-factory.js new file mode 100644 index 0000000..59f0d43 --- /dev/null +++ b/test/test-factory.js @@ -0,0 +1,45 @@ +'use strict'; + +var path = require('path'); +var os = require('os'); +var helpers = require('yeoman-generator').test; +var assert = require('yeoman-generator').assert; + +describe('Launching factory tests', function () { + + var bangular, bangService, tmpDir; + var bangDir = process.cwd(); + + describe('', function () { + + before(function (done) { + + tmpDir = path.join(os.tmpdir(), '/tmp'); + + helpers.testDirectory(tmpDir, function (err) { + bangular = helpers.createGenerator('bangular:app', + [path.join(bangDir, '/app')], + false, { 'skipInstall': true, 'skipLog': true }); + + helpers.mockPrompt(bangular, { name: 'Test', backend: 'json', modules: [] }); + bangular.run(done); + }); + + }); + + it('should create a new factory', function (done) { + + bangService = helpers.createGenerator('bangular:factory', [bangDir + '/factory'], 'socket'); + bangService.run(function () { + assert.file('client/services/socket/socket.js'); + assert.file('client/services/socket/socket.spec.js'); + assert.fileContent('client/services/socket/socket.js', '.factory(\'Socket\', function () {'); + assert.fileContent('client/services/socket/socket.spec.js', 'inject(function (_Socket_) {'); + done(); + }); + + }); + + }); + +});