Skip to content

Commit

Permalink
feat(gen) added passport boilerplate question
Browse files Browse the repository at this point in the history
generates

  * Passport configuration and a User model

  * API routes for account creation/sign in

  * Login and signup views with form validation,
    including handling server validation errors

  * Settings view for changing your password

  * Angular Auth service for creating accounts/logining

closes #25, #36
  • Loading branch information
DaftMonk committed Jan 11, 2014
1 parent e6eff5d commit 8784106
Show file tree
Hide file tree
Showing 52 changed files with 1,369 additions and 120 deletions.
78 changes: 70 additions & 8 deletions app/index.js
Expand Up @@ -250,8 +250,17 @@ Generator.prototype.askForMongo = function askForMongo() {
name: 'mongo',
message: 'Would you like to include MongoDB with Mongoose?',
default: false
}, {
type: 'confirm',
name: 'mongoPassportUser',
message: 'Would you like to include a Passport authentication boilerplate?',
default: false,
when: function (props) {
return props.mongo;
}
}], function (props) {
this.mongo = props.mongo;
this.mongoPassportUser = props.mongoPassportUser;

cb();
}.bind(this));
Expand Down Expand Up @@ -335,21 +344,24 @@ function appendFilesToJade(jadeOrOptions, fileType, optimizedPath, sourceFileLis
return updatedContent;
}

Generator.prototype.navBarScript = function navBarScript() {
var ext = 'js';
var folder = 'javascript';
var minsafe = '';
var copyScriptWithEnvOptions = function copyScriptWithEnvOptions(that, fileToCopy, destinationFolder) {
var ext = 'js',
minsafe = '',
sourceFolder = 'javascript';

if(this.env.options.coffee) {
if(that.env.options.coffee) {
ext = 'coffee';
folder = 'coffeescript';
sourceFolder = 'coffeescript';
}

if(this.env.options.minsafe) {
if(that.env.options.minsafe) {
minsafe = '-min';
}
that.copy('../../templates/' + sourceFolder + minsafe + '/' + fileToCopy + '.' + ext, destinationFolder + fileToCopy + '.' + ext);
};

this.copy('../../templates/' + folder + minsafe + '/navbar.' + ext, 'app/scripts/controllers/navbar.' + ext);
Generator.prototype.navBarScript = function navBarScript() {
copyScriptWithEnvOptions(this, 'controllers/navbar', 'app/scripts/');
};

Generator.prototype.appJs = function appJs() {
Expand All @@ -360,6 +372,18 @@ Generator.prototype.appJs = function appJs() {
sourceFileList: ['scripts/app.js', 'scripts/controllers/main.js', 'scripts/controllers/navbar.js'],
searchPath: ['.tmp', 'app']
};

// only reference authentication controllers when required
if (this.mongoPassportUser) {
appendOptions.sourceFileList.push('scripts/controllers/login.js');
appendOptions.sourceFileList.push('scripts/controllers/signup.js');
appendOptions.sourceFileList.push('scripts/controllers/settings.js');
appendOptions.sourceFileList.push('scripts/services/auth.js');
appendOptions.sourceFileList.push('scripts/services/session.js');
appendOptions.sourceFileList.push('scripts/services/user.js');
appendOptions.sourceFileList.push('scripts/directives/mongooseError.js');
}

if (this.jade) {
this.indexFile = appendFilesToJade(appendOptions);
} else {
Expand All @@ -380,6 +404,11 @@ Generator.prototype.addJadeViews = function addHtmlJade() {
if(this.jade) {
this.copy('../../templates/views/jade/partials/main.jade', 'app/views/partials/main.jade');
this.copy('../../templates/views/jade/partials/navbar.jade', 'app/views/partials/navbar.jade');
if(this.mongoPassportUser) {
this.copy('../../templates/views/jade/partials/login.jade', 'app/views/partials/login.jade');
this.copy('../../templates/views/jade/partials/signup.jade', 'app/views/partials/signup.jade');
this.copy('../../templates/views/jade/partials/settings.jade', 'app/views/partials/settings.jade');
}
this.copy('../../templates/views/jade/404.jade', 'app/views/404.jade');
}
};
Expand All @@ -388,6 +417,11 @@ Generator.prototype.addHtmlViews = function addHtmlViews() {
if(!this.jade) {
this.copy('../../templates/views/html/partials/main.html', 'app/views/partials/main.html');
this.copy('../../templates/views/html/partials/navbar.html', 'app/views/partials/navbar.html');
if(this.mongoPassportUser) {
this.copy('../../templates/views/html/partials/login.html', 'app/views/partials/login.html');
this.copy('../../templates/views/html/partials/signup.html', 'app/views/partials/signup.html');
this.copy('../../templates/views/html/partials/settings.html', 'app/views/partials/settings.html');
}
this.copy('../../templates/views/html/404.html', 'app/views/404.html');
}
};
Expand Down Expand Up @@ -443,11 +477,39 @@ Generator.prototype.serverFiles = function () {
};

Generator.prototype.mongoFiles = function () {

if (!this.mongo) {
return; // Skip if disabled.
}
this.env.options.mongo = this.mongo;

this.template('../../templates/express/mongo/mongo.js', 'lib/db/mongo.js');
this.template('../../templates/express/mongo/dummydata.js', 'lib/db/dummydata.js');
this.template('../../templates/express/mongo/thing.js', 'lib/models/thing.js');

if(!this.mongoPassportUser) {
return; // Skip if disabled.
}
this.env.options.mongoPassportUser = this.mongoPassportUser;

// frontend
copyScriptWithEnvOptions(this, 'controllers/login', 'app/scripts/');
copyScriptWithEnvOptions(this, 'controllers/signup', 'app/scripts/');
copyScriptWithEnvOptions(this, 'controllers/settings', 'app/scripts/');

copyScriptWithEnvOptions(this, 'services/auth', 'app/scripts/');
copyScriptWithEnvOptions(this, 'services/session', 'app/scripts/');
copyScriptWithEnvOptions(this, 'services/user', 'app/scripts/');

copyScriptWithEnvOptions(this, 'directives/mongooseError', 'app/scripts/');

// middleware
this.template('../../templates/express/middleware.js', 'lib/middleware.js');
// config
this.template('../../templates/express/config/passport.js', 'lib/config/passport.js');
// models
this.template('../../templates/express/mongo/user.js', 'lib/models/user.js');
// controllers
this.template('../../templates/express/session.js', 'lib/controllers/session.js');
this.template('../../templates/express/users.js', 'lib/controllers/users.js');
};
2 changes: 2 additions & 0 deletions main/index.js
Expand Up @@ -13,6 +13,8 @@ util.inherits(Generator, ScriptBase);

Generator.prototype.createAppFile = function createAppFile() {
this.angularModules = this.env.options.angularDeps;
this.mongo = this.env.options.mongo;
this.mongoPassportUser = this.env.options.mongoPassportUser;
this.ngRoute = this.env.options.ngRoute;
this.appTemplate('app', 'scripts/app');
};
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -4,6 +4,7 @@
"description": "Yeoman generator for AngularJS with Express server",
"keywords": [
"yeoman-generator",
"mean",
"scaffold",
"express",
"fullstack",
Expand Down
10 changes: 10 additions & 0 deletions templates/coffeescript-min/app.coffee
Expand Up @@ -6,6 +6,16 @@ angular.module('<%= scriptAppName %>', [<%= angularModules %>])<% if (ngRoute) {
.when '/',
templateUrl: 'partials/main'
controller: 'MainCtrl'
<% if(mongo && mongoPassportUser) {%>
.when '/login',
templateUrl: 'partials/login'
controller: 'LoginCtrl'
.when '/logout',
controller: 'LogoutCtrl'
.when '/signup',
templateUrl: 'partials/signup'
controller: 'SignupCtrl'
<% } %>
.otherwise
redirectTo: '/'
$locationProvider.html5Mode(true)
Expand Down
Empty file.
35 changes: 21 additions & 14 deletions templates/coffeescript-min/navbar.coffee
@@ -1,17 +1,24 @@
'use strict'

angular.module('<%= scriptAppName %>')
.controller 'NavbarCtrl', ['$scope', '$location', ($scope, $location) ->
$scope.menu = [
title: 'Home'
link: '/'
,
title: 'About'
link: '#'
,
title: 'Contact'
link: '#'
]
$scope.isActive = (route) ->
route is $location.path()
]
.controller 'NavbarCtrl', ['$scope', '$location', ($scope, $location) ->
$scope.menu = [
title: 'Home'
link: '/'
,
title: 'About'
link: '#'
,
title: 'Contact'
link: '#'
<% if(mongo && mongoPassportUser) { %>,
title: 'Sign Up'
link: '#/signup'
,
title: 'Login'
link: '#/login'
}<% } %>
]
$scope.isActive = (route) ->
route is $location.path()
]
30 changes: 28 additions & 2 deletions templates/coffeescript/app.coffee
@@ -1,11 +1,37 @@
'use strict'

angular.module('<%= scriptAppName %>', [<%= angularModules %>])<% if (ngRoute) { %>
.config ($routeProvider, $locationProvider) ->
.config ($routeProvider, $locationProvider<% if (mongoPassportUser) { %>, $httpProvider<% } %>) ->
$routeProvider
.when '/',
templateUrl: 'partials/main'
controller: 'MainCtrl'
<% if(mongoPassportUser) {%>
.when '/login',
templateUrl: 'partials/login'
controller: 'LoginCtrl'
.when '/signup',
templateUrl: 'partials/signup'
controller: 'SignupCtrl'
.when '/settings',
templateUrl: 'partials/settings'
controller: 'SettingsCtrl'
authenticate: true<% } %>
.otherwise
redirectTo: '/'
$locationProvider.html5Mode(true)<% } %>

$locationProvider.html5Mode true<% if (mongoPassportUser) { %>

$httpProvider.interceptors.push ['$q', '$location', ($q, $location) ->
responseError: (response) ->
if response.status is 401 or response.status is 403
$location.path '/login'
$q.reject response
else
$q.reject response
]
.run ($rootScope, $location, Auth) ->

# Redirect to login if route requires auth and you're not logged in
$rootScope.$on '$routeChangeStart', (event, next) ->
$location.path '/login' if next.authenticate and not Auth.isLoggedIn()<% } %><% } %>
21 changes: 21 additions & 0 deletions templates/coffeescript/controllers/login.coffee
@@ -0,0 +1,21 @@
'use strict'

angular.module('<%= scriptAppName %>')
.controller 'LoginCtrl', ($scope, Auth, $location) ->
$scope.user = {}
$scope.errors = {}

$scope.login = (form) ->
$scope.submitted = true

if form.$valid
Auth.login(
email: $scope.user.email
password: $scope.user.password
)
.then ->
# Logged in, redirect to home
$location.path '/'
.catch (err) ->
err = err.data;
$scope.errors.other = err.message;
18 changes: 18 additions & 0 deletions templates/coffeescript/controllers/navbar.coffee
@@ -0,0 +1,18 @@
'use strict'

angular.module('<%= scriptAppName %>')
.controller 'NavbarCtrl', ($scope, $location<% if (mongoPassportUser) { %>, Auth<% } %>) ->
$scope.menu = [
title: 'Home'
link: '/'
<% if(mongoPassportUser) { %>,
title: 'Settings'
link: '/settings'
<% } %>]
<% if(mongoPassportUser) { %>
$scope.logout = ->
Auth.logout().then ->
$location.path "/login"
<% } %>
$scope.isActive = (route) ->
route is $location.path()
17 changes: 17 additions & 0 deletions templates/coffeescript/controllers/settings.coffee
@@ -0,0 +1,17 @@
'use strict'

angular.module('<%= scriptAppName %>')
.controller 'SettingsCtrl', ($scope, User, Auth) ->
$scope.errors = {}

$scope.changePassword = (form) ->
$scope.submitted = true

if form.$valid
Auth.changePassword($scope.user.oldPassword, $scope.user.newPassword)
.then(->
$scope.message = 'Password successfully changed.'
).catch( ->
form.password.$setValidity 'mongoose', false
$scope.errors.other = 'Incorrect password'
)
27 changes: 27 additions & 0 deletions templates/coffeescript/controllers/signup.coffee
@@ -0,0 +1,27 @@
'use strict'

angular.module('<%= scriptAppName %>')
.controller 'SignupCtrl', ($scope, Auth, $location) ->
$scope.user = {}
$scope.errors = {}

$scope.register = (form) ->
$scope.submitted = true

if form.$valid
Auth.createUser(
name: $scope.user.name
email: $scope.user.email
password: $scope.user.password
).then( ->
# Account created, redirect to home
$location.path '/'
).catch( (err) ->
err = err.data
$scope.errors = {}

# Update validity of form fields that match the mongoose errors
angular.forEach err.errors, (error, field) ->
form[field].$setValidity 'mongoose', false
$scope.errors[field] = error.type
)
13 changes: 13 additions & 0 deletions templates/coffeescript/directives/mongooseError.coffee
@@ -0,0 +1,13 @@
'use strict'

###
Removes server error when user updates input
###
angular.module('<%= scriptAppName %>')
.directive 'mongooseError', ->
restrict: 'A'
require: 'ngModel'
link: (scope, element, attrs, ngModel) ->
element.on 'keydown', ->
ngModel.$setValidity 'mongoose', true

16 changes: 0 additions & 16 deletions templates/coffeescript/navbar.coffee

This file was deleted.

0 comments on commit 8784106

Please sign in to comment.