Skip to content

Commit

Permalink
Merge pull request #15 from rpmartz/web-client/part-04-efficient-work…
Browse files Browse the repository at this point in the history
…flow

Add signup and login functionality.
  • Loading branch information
rpmartz committed Jul 4, 2016
2 parents 88b3ae5 + 8915186 commit 37b7b24
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 13 deletions.
7 changes: 6 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
"dependencies": {
"bootstrap": "^3.3.6",
"angular": "^1.5.5",
"angular-route": "^1.5.5"
"angular-route": "^1.5.5",
"angular-messages": "^1.5.7",
"a0-angular-storage": "^0.0.15"
},
"devDependencies": {
"angular-mocks": "^1.5.7"
}
}
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-devtools")
compile('org.postgresql:postgresql:9.4.1208')
compile('org.flywaydb:flyway-core')
compile('io.springfox:springfox-swagger2:2.4.0')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ryanpmartz.booktrackr.authentication.JwtUtil;
import com.ryanpmartz.booktrackr.controller.dto.SignupDto;
import com.ryanpmartz.booktrackr.controller.dto.UserDto;
import com.ryanpmartz.booktrackr.domain.User;
Expand Down Expand Up @@ -29,13 +30,15 @@ public class UserController {

private static final String EMAIL_EXISTS_MESSAGE = "This email is in use";

private final UserService userService;
private final JwtUtil jwtUtil;
private final PasswordEncoder passwordEncoder;
private final UserService userService;

@Autowired
public UserController(final UserService userService, final PasswordEncoder encoder) {
public UserController(final UserService userService, final PasswordEncoder encoder, final JwtUtil jwtUtil) {
this.userService = userService;
this.passwordEncoder = encoder;
this.jwtUtil = jwtUtil;
}

@RequestMapping(value = "/users", method = RequestMethod.POST)
Expand Down Expand Up @@ -75,8 +78,11 @@ public ResponseEntity<?> createUser(@Valid @RequestBody SignupDto signupDto) thr
signupDto.setConfirmPassword(null);

User savedUser = userService.createUser(user);
String jwt = jwtUtil.generateToken(user);

return ResponseEntity.status(HttpStatus.CREATED)
.contentType(MediaType.APPLICATION_JSON_UTF8).body(new UserDto(savedUser));
.contentType(MediaType.APPLICATION_JSON_UTF8)
.header("Authorization", "Bearer " + jwt)
.body(new UserDto(savedUser));
}
}
40 changes: 37 additions & 3 deletions src/main/resources/static/app/js/app.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,51 @@
(function () {
'use strict';

angular.module('booktrackrApp', ['ngRoute']);
angular.module('booktrackrApp', ['ngRoute', 'ngMessages', 'angular-storage']);

angular.module('booktrackrApp').config(['$routeProvider', function ($routeProvider) {
angular.module('booktrackrApp').config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {
$routeProvider
.when('/', {
templateUrl: 'partials/home.html',
controller: 'HomeController',
controllerAs: 'vm'
})
.when('/books', {
templateUrl: 'partials/books.html'
})
.when('/login', {
templateUrl: 'partials/login.html'
})
.when('/signup', {
templateUrl: 'partials/signup.html'
});


$httpProvider.interceptors.push('authInterceptor');
}]);

angular.module('booktrackrApp').factory('authInterceptor', function ($rootScope, TokenService) {

var authInterceptor = {
request: function (config) {
var currentUserToken = TokenService.getCurrentUserToken();
var token = currentUserToken ? currentUserToken : null;

if (token) {
config.headers.authorization = token;
}
return config;
},
response: function (response) {
if (response.status === 401) {
$rootScope.$broadcast('unauthorized');
}
else if (response.status == 403) {
$rootScope.$broadcast('forbidden');
}
return response;
}
};
return authInterceptor;
});

})();
69 changes: 66 additions & 3 deletions src/main/resources/static/app/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,74 @@

angular
.module('booktrackrApp')
.controller('HomeController', HomeController);
.controller('HomeController', HomeController)
.controller('SignupController', SignupController)
.controller('LoginController', LoginController)
.controller('BooksController', BooksController);

/* @ngInject */
function HomeController() {
HomeController.$inject = ['Book', '$log'];
function HomeController(Book, $log) {
var vm = this;

Book.all().then(function (res) {
$log.info(res.data);
vm.books = res.data;
}, function (err) {
$log.info(err)
})
}

SignupController.$inject = ['AuthService', '$log'];
function SignupController(AuthService, $log) {
var vm = this;

vm.newUser = {};

vm.signup = function () {
AuthService.signup(vm.newUser)
.then(function (res) {
$log.info('Signup succeeded', res);
},
function (err) {
$log.error('Signup failed ', err);
})

}

}

LoginController.$inject = ['AuthService', '$log', '$location', 'TokenService'];
function LoginController(AuthService, $log, $location, TokenService) {
var vm = this;
vm.credentials = {};

vm.authenticate = function () {
AuthService.login(vm.credentials)
.then(
function (res) {
var jwt = res.headers('Authorization');
TokenService.setCurrentUserToken(jwt);
$location.path('/books');
},
function (err) {
$log.debug('Auth Failed');
// todo show failed auth message
})
}


}

BooksController.$inject = ['Book', '$log'];
function BooksController(Book, $log) {
var vm = this;

Book.all().then(function (res) {
$log.debug('Books response: ', res);

}, function (err) {
$log.error('all books call failed', err);
});
}

})();
60 changes: 60 additions & 0 deletions src/main/resources/static/app/js/services/services.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
(function () {
'use strict';

angular.module('booktrackrApp').factory('Book', Book);
Book.$inject = ['$http'];

function Book($http) {
var service = {
all: allBooks
};

function allBooks() {
return $http.get('/books');
}

return service;
}

angular.module('booktrackrApp').factory('AuthService', AuthService);
AuthService.$inject = ['$http'];

function AuthService($http) {
var service = {
login: login,
signup: signup
};

function login(credentials) {
return $http.post('/authenticate', credentials);
}

function signup(signupForm) {
return $http.post('/users', signupForm);
}

return service;
}

angular.module('booktrackrApp').factory('TokenService', TokenService);
TokenService.$inject = ['store'];

function TokenService(store) {

var service = {
setCurrentUserToken: setCurrentUserToken,
getCurrentUserToken: getCurrentUserToken
};

function setCurrentUserToken(token) {
store.set('currentUserToken', token);
}

function getCurrentUserToken() {
return store.get('currentUserToken');
}

return service
}

})();
8 changes: 8 additions & 0 deletions src/main/resources/static/partials/books.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class="row">
<div class="col-xs-6 col-xs-offset-3" ng-controller="BooksController as books">
<h2>Books</h2>
<ul ng-repeat="book in books.books">
<li>{{ book.title }}</li>
</ul>
</div>
</div>
2 changes: 2 additions & 0 deletions src/main/resources/static/partials/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<div class="col-xs-5 col-xs-offset-4">
<h3>Home Partial</h3>
<p>Rendered Courtesy of AngularJS!</p>
<a href="#/signup" class="btn btn-info">Create Account</a>
<a href="#/login" class="btn btn-info">Login</a>
</div>
</div>

23 changes: 23 additions & 0 deletions src/main/resources/static/partials/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div class="row">
<div class="col-xs-6 col-xs-offset-3" ng-controller="LoginController as login">
<h2>Login</h2>
<form novalidate name="loginForm">
<div class="form-group">
<label class="control-label" for="username">Email Address</label>
<input type="email" ng-model="login.credentials.username" class="form-control" id="username"
placeholder="Email"
ng-minlength="2" required>
</div>
<div class="form-group">
<label class="control-label" for="password">Password</label>
<input type="password" ng-model="login.credentials.password" class="form-control" id="password"
placeholder="Password" ng-minlength="2" required>
</div>
<div class="form-group">
<input type="submit" class="btn btn-info" ng-disabled="loginForm.$invalid"
ng-click="login.authenticate()" value="Submit"/>
<a href="#/signup" class="btn btn-default">Signup</a>
</div>
</form>
</div>
</div>
96 changes: 96 additions & 0 deletions src/main/resources/static/partials/signup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<div class="row">
<div class="col-xs-6 col-xs-offset-3" ng-controller="SignupController as signup">
<h2>Booktrackr Signup</h2>
<form novalidate name="newUserForm">
<div class="form-group"
ng-class="{ 'has-error' : newUserForm.firstName.$invalid && newUserForm.firstName.$touched }">
<label class="control-label" for="firstName">First Name</label>
<input type="text" ng-model="signup.newUser.firstName" class="form-control" id="firstName"
name="firstName"
placeholder="First Name" ng-required="true" ng-minlength="2" ng-maxlength="255">
<div class="help-block" ng-messages="newUserForm.firstName.$error"
ng-if="newUserForm.firstName.$touched">
<p ng-message="minlength" class="text-danger">First name is too short.</p>
<p ng-message="maxlength" class="text-danger">First name is too long.</p>
<p ng-message="required" class="text-danger">Please enter your first name.</p>
</div>
<div class="help-block" ng-messages="validationErrors.firstName"
ng-if="newUserForm.$submitted && validationErrors.firstName">
<p ng-message="result" class="text-danger">{{ validationErrors.firstName.result }}</p>
</div>
</div>
<div class="form-group"
ng-class="{ 'has-error' : newUserForm.lastName.$invalid && newUserForm.lastName.$touched }">
<label class="control-label" for="lastName">Last Name</label>
<input type="text" ng-model="signup.newUser.lastName" class="form-control" id="lastName" name="lastName"
placeholder="Last Name" ng-required="true" ng-minlength="2" ng-maxlength="255">
<div class="help-block" ng-messages="newUserForm.lastName.$error" ng-if="newUserForm.lastName.$touched">
<p ng-message="minlength" class="text-danger">Last name is too short.</p>
<p ng-message="maxlength" class="text-danger">Last name is too long.</p>
<p ng-message="required" class="text-danger">Please enter your last name.</p>
</div>
<div class="help-block" ng-messages="validationErrors.lastName"
ng-if="newUserForm.$submitted && validationErrors.lastName">
<p ng-message="result" class="text-danger">{{ validationErrors.lastName.result }}</p>
</div>
</div>
<div class="form-group"
ng-class="{ 'has-error' : (newUserForm.email.$invalid && newUserForm.email.$touched) || validationErrors.email }">
<label class="control-label" for="email">Email</label>
<input type="email" ng-model="signup.newUser.email" class="form-control" id="email" name="email"
placeholder="Email"
ng-required="true" ng-minlength="6" ng-maxlength="255">
<div class="help-block" ng-messages="newUserForm.email.$error" ng-if="newUserForm.email.$touched">
<p ng-message="minlength" class="text-danger">Please enter your email address.</p>
<p ng-message="maxlength" class="text-danger">Email is too long.</p>
<p ng-message="required" class="text-danger">Please enter your email address.</p>
<p ng-message="email" class="text-danger">Please enter a valid email address.</p>
<p ng-message="used" class="text-danger">This email is already in use.</p>
</div>
<div class="help-block" ng-messages="validationErrors.email"
ng-if="newUserForm.$submitted && validationErrors.email">
<p ng-message="result" class="text-danger">{{ validationErrors.email.result }}</p>
</div>
</div>
<div class="form-group"
ng-class="{ 'has-error' : newUserForm.password.$invalid && newUserForm.password.$touched }">
<label class="control-label" for="password">Password</label>
<input type="password" ng-model="signup.newUser.password" class="form-control" id="password"
name="password" placeholder="Password" ng-required="true" ng-minlength="8">
<div class="help-block" ng-messages="newUserForm.password.$error" ng-if="newUserForm.password.$touched">
<p ng-message="minlength" class="text-danger">Password must be at least 8 characters.</p>
<p ng-message="maxlength" class="text-danger">Password is too long.</p>
<p ng-message="required" class="text-danger">Please enter a password.</p>
</div>
<div class="help-block" ng-messages="validationErrors.password"
ng-if="newUserForm.$submitted && validationErrors.password">
<p ng-message="result" class="text-danger">{{ validationErrors.password.result }}</p>
</div>

</div>
<div class="form-group"
ng-class="{ 'has-error' : newUserForm.confirmPassword.$invalid && newUserForm.confirmPassword.$touched }">
<label class="control-label" for="confirmPassword">Confirm Password</label>
<input type="password" ng-model="signup.newUser.confirmPassword" class="form-control"
id="confirmPassword"
name="confirmPassword"
placeholder="Confirm Password" ng-required="true" ng-minlength="8">
</div>
<div class="help-block" ng-messages="newUserForm.confirmPassword.$error"
ng-if="newUserForm.confirmPassword.$touched">
<p ng-message="minlength" class="text-danger">Password must be at least 8 characters.</p>
<p ng-message="maxlength" class="text-danger">Password is too long.</p>
<p ng-message="required" class="text-danger">Please confirm your password.</p>
</div>
<div class="help-block" ng-messages="validationErrors.confirmPassword"
ng-if="newUserForm.$submitted && validationErrors.confirmPassword">
<p ng-message="result" class="text-danger">{{ validationErrors.confirmPassword.result }}</p>
</div>
<div class="form-group pull-right">
<a href="#/" class="btn btn-danger">Cancel</a>
<button class="btn btn-info" ng-click="signup.signup()" ng-disabled="newUserForm.$invalid">Submit
</button>
</div>
</form>
</div>
</div>
Loading

0 comments on commit 37b7b24

Please sign in to comment.