Merge pull request #1741 from tschiman/master

BAEL-797 Angular App For Cloud Series
This commit is contained in:
slavisa-baeldung 2017-05-18 01:09:11 +02:00 committed by GitHub
commit dacc694f02
66 changed files with 67252 additions and 219 deletions

View File

@ -59,6 +59,56 @@
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/*LiveTest.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<configuration>
<tasks>
<exec executable="cmd" osfamily="windows" dir="${project.basedir}/src/main/angular/ui">
<arg value="/c"/>
<arg value="ng"/>
<arg value="build"/>
</exec>
<exec executable="/bin/sh" osfamily="mac" dir="${project.basedir}/src/main/angular/ui">
<arg value="-c"/>
<arg value="ng build"/>
</exec>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<spring-cloud-dependencies.version>Brixton.SR7</spring-cloud-dependencies.version>
</properties>

View File

@ -0,0 +1,58 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "ui"
},
"apps": [
{
"root": "src",
"outDir": "../../resources/static/home",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.css",
"../node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"component": {}
}
}

View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,28 @@
# Ui
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -0,0 +1,14 @@
import {UiPage} from "./app.po";
describe('ui App', () => {
let page: UiPage;
beforeEach(() => {
page = new UiPage();
});
it('should display message saying app works', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('app works!');
});
});

View File

@ -0,0 +1,11 @@
import {browser, element, by} from "protractor";
export class UiPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types":[
"jasmine",
"node"
]
}
}

View File

@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./src/test.ts': ['@angular/cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'coverage-istanbul']
: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

View File

@ -0,0 +1,47 @@
{
"name": "ui",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0",
"bootstrap": "^4.0.0-alpha.6",
"core-js": "^2.4.1",
"rxjs": "^5.1.0",
"zone.js": "^0.8.4"
},
"devDependencies": {
"@angular/cli": "1.0.0",
"@angular/compiler-cli": "^4.0.0",
"@types/jasmine": "2.5.38",
"@types/node": "~6.0.60",
"codelyzer": "~2.0.0",
"jasmine-core": "~2.5.2",
"jasmine-spec-reporter": "~3.2.0",
"karma": "~1.4.1",
"karma-chrome-launcher": "~2.0.0",
"karma-cli": "~1.0.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-coverage-istanbul-reporter": "^0.2.0",
"protractor": "~5.1.0",
"ts-node": "~2.0.0",
"tslint": "~4.5.0",
"typescript": "~2.2.0"
}
}

View File

@ -0,0 +1,30 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
beforeLaunch: function() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
},
onPrepare() {
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,3 @@
.custom-close {
float:right;
}

View File

@ -0,0 +1,37 @@
<nav class="navbar navbar-toggleable-md navbar-inverse fixed-top bg-inverse">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="#">Book Rater <span *ngIf="principal.isAdmin()">Admin</span></a>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
</ul>
<button *ngIf="principal.authenticated" type="button" class="btn btn-link" (click)="onLogout()">Logout</button>
</div>
</nav>
<div class="jumbotron">
<div class="container">
<h1>Book Rater App</h1>
<p *ngIf="!principal.authenticated" class="lead">Anyone can view the books.</p>
<p *ngIf="principal.authenticated && !principal.isAdmin()" class="lead">Users can view and create ratings</p>
<p *ngIf="principal.isAdmin()" class="lead">Admins can do anything!</p>
</div>
</div>
<section class="books">
<div class="container">
<div class="row">
<div class="col-md">
<div class="row">
<div class="col-md-12">
<app-book-list [principal]="principal" (onBookSelected)="selectBook($event)"></app-book-list>
</div>
</div>
</div>
<div *ngIf="selectedBook != null" class="col-md-3">
<app-book-detail [selectedBook]="selectedBook" [principal]="principal" (closeBook)="closeBookDetail()"></app-book-detail>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,50 @@
import {Component} from "@angular/core";
import {Principal} from "./principal";
import {Response} from "@angular/http";
import {Book} from "./book";
import {HttpService} from "./http.service";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
selectedBook: Book = null;
principal: Principal = new Principal(false, []);
loginFailed: boolean = false;
constructor(private httpService: HttpService){}
ngOnInit(): void {
this.httpService.me()
.subscribe((response: Response) => {
let principalJson = response.json();
this.principal = new Principal(principalJson.authenticated, principalJson.authorities);
}, (error) => {
console.log(error);
});
}
onLogout() {
this.httpService.logout()
.subscribe((response: Response) => {
if (response.status === 200) {
this.loginFailed = false;
this.principal = new Principal(false, []);
window.location.replace(response.url);
}
}, (error) => {
console.log(error);
});
}
closeBookDetail() {
this.selectedBook = null;
}
selectBook(book: Book) {
this.selectedBook = book;
}
}

View File

@ -0,0 +1,28 @@
import {BrowserModule} from "@angular/platform-browser";
import {NgModule} from "@angular/core";
import {FormsModule} from "@angular/forms";
import {HttpModule} from "@angular/http";
import {AppComponent} from "./app.component";
import {RatingComponent} from "./rating/rating.component";
import {ClickStopPropagationDirective} from "./click-stop-propagation.directive";
import {BookDetailComponent} from "./book/book-detail/book-detail.component";
import {BookListComponent} from "./book/book-list/book-list.component";
import {HttpService} from "./http.service";
@NgModule({
declarations: [
AppComponent,
RatingComponent,
ClickStopPropagationDirective,
BookDetailComponent,
BookListComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [HttpService],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,10 @@
export class Book {
id: number;
author: String;
title: String;
constructor(id: number, author: String, title: String){
this.id = id;
this.author = author;
this.title = title;
}
}

View File

@ -0,0 +1,11 @@
<div class="card">
<div class="card-block">
<button type="button" class="close" aria-label="Close" (click)="closeBookDetail()">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="card-title">Title: {{selectedBook.title}}</h4>
<h6 class="card-subtitle mb-2 text-muted">Author: {{selectedBook.author}}</h6>
<p class="card-text">A quick summary of the book</p>
<app-rating *ngIf="principal.authenticated" [bookId]="selectedBook.id" [principal]="principal"></app-rating>
</div>
</div>

View File

@ -0,0 +1,26 @@
import {Component, OnInit, Input, Output, EventEmitter} from "@angular/core";
import {Book} from "../../book";
import {Principal} from "../../principal";
@Component({
selector: 'app-book-detail',
templateUrl: './book-detail.component.html',
styleUrls: ['./book-detail.component.css']
})
export class BookDetailComponent implements OnInit {
@Input() selectedBook: Book = null;
@Input() principal: Principal = null;
@Output() closeBook: EventEmitter<any> = new EventEmitter<any>();
constructor() { }
ngOnInit() {
}
closeBookDetail() {
this.closeBook.emit(null);
}
}

View File

@ -0,0 +1,54 @@
<div class="col-md-12" *ngFor="let book of books; let i = index;" (click)="selectBook(book)">
<div class="card">
<div class="card-block">
<div *ngIf="booksToEdit.indexOf(i) === -1 ; then bookView else bookEdit"></div>
<ng-template #bookView>
<button appClickStopPropagation *ngIf="principal.isAdmin()" type="button" class="btn btn-danger custom-close" (click)="delete(i)">Delete</button>
<button appClickStopPropagation *ngIf="principal.isAdmin()" type="button" class="btn btn-warning custom-close" (click)="editBook(i)">Edit</button>
<h4 class="card-title">Title: {{book.title}}</h4>
<h6 class="card-subtitle mb-2 text-muted">Author: {{book.author}}</h6>
</ng-template>
<ng-template #bookEdit>
<button appClickStopPropagation type="button" class="btn btn-secondary custom-close" (click)="cancelEditBook(i)">Cancel</button>
<form appClickStopPropagation (ngSubmit)="saveBook(i, newBooks[i])" class="mt-2 mt-md-0" #f1="ngForm">
<div class="form-group">
<label for="title">Title:</label>
<input id="title" name="title" [(ngModel)]="newBooks[i].title" required class="form-control mr-sm-2" type="text">
</div>
<div class="form-group">
<label for="author">Author:</label>
<input id="author" name="author" [(ngModel)]="newBooks[i].author" required class="form-control mr-sm-2" type="text">
</div>
<button class="btn btn-outline-success my-2 my-sm-0" type="submit" [disabled]="!f1.valid">Save</button>
</form>
</ng-template>
</div>
</div>
</div>
<div *ngIf="principal.isAdmin()" class="col-md-12">
<div class="card">
<div class="card-block">
<div *ngIf="!isAddNewBook; then bookPlaceHolder else bookAdd"></div>
<ng-template #bookPlaceHolder>
<h4 (click)="activateAddNewBook()" class="card-title center-block">Add New Book</h4>
</ng-template>
<ng-template #bookAdd>
<button appClickStopPropagation type="button" class="btn btn-secondary custom-close" (click)="cancelAddBook()">Cancel</button>
<form appClickStopPropagation (ngSubmit)="addNewBook(newBook, titleNewBook)" class="mt-2 mt-md-0" #f2="ngForm">
<div class="form-group">
<label for="titleNewBook">Title:</label>
<input id="titleNewBook" name="title" [(ngModel)]="newBook.title" required class="form-control mr-sm-2" type="text" #titleNewBook>
</div>
<div class="form-group">
<label for="authorNewBook">Author:</label>
<input id="authorNewBook" name="author" [(ngModel)]="newBook.author" required class="form-control mr-sm-2" type="text">
</div>
<button class="btn btn-outline-success my-2 my-sm-0" type="submit" [disabled]="!f2.valid">Save</button>
</form>
</ng-template>
</div>
</div>
</div>

View File

@ -0,0 +1,122 @@
import {Component, OnInit, Input, Output, EventEmitter} from "@angular/core";
import {Principal} from "../../principal";
import {Book} from "../../book";
import {Response} from "@angular/http";
import {HttpService} from "../../http.service";
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.css']
})
export class BookListComponent implements OnInit {
@Input() principal: Principal = null;
@Output() onBookSelected: EventEmitter<Book> = new EventEmitter<Book>();
books: Book[] = [];
newBooks: Book[] = [];
newBook: Book = new Book(Math.floor(Math.random() * 1000), '', '');
booksToEdit: number[] = [];
isAddNewBook: boolean = false;
selectedBook: Book = null;
constructor(private httpService: HttpService) { }
ngOnInit() {
this.loadBooks();
}
loadBooks() {
this.httpService.getBooks()
.subscribe((response: Response) => {
let booksJson: any[] = response.json()
booksJson.forEach(book => {
this.books.push(new Book(book.id, book.author, book.title));
this.newBooks.push(new Book(book.id, book.author, book.title));
})
}, (error) => {
console.log(error);
});
}
cancelEditBook(bookIndex: number) {
if (this.booksToEdit.indexOf(bookIndex) !== -1) {
this.booksToEdit.splice(this.booksToEdit.indexOf(bookIndex), 1); //remove the index of the book to edit
//get the original book
let bookCopy: Book = new Book(this.books[bookIndex].id, this.books[bookIndex].author, this.books[bookIndex].title);
this.newBooks.splice(bookIndex,1,bookCopy); //replace the edited book with the old book
}
}
editBook(bookIndex: number) {
this.booksToEdit.push(bookIndex);
}
saveBook(bookIndex: number, newBook: Book) {
console.log(newBook);
//save the book to the database
this.httpService.updateBook(newBook)
.subscribe((response: Response) => {
let bookJson = response.json();
let book: Book = new Book(bookJson.id, bookJson.author, bookJson.title);
//update the current array of books
let bookArr: Book = this.books.find(b => b.id === book.id);
bookArr.title = book.title;
bookArr.author = book.author;
this.booksToEdit.splice(this.booksToEdit.indexOf(bookIndex), 1); //remove the index of the book to edit
}, (error) => {
console.log(error);
});
}
delete(bookIndex: number) {
let book: Book = this.books[bookIndex];
this.httpService.deleteBook(book)
.subscribe(() => {
if (this.selectedBook !== null && this.books[bookIndex].id === this.selectedBook.id) {
this.selectedBook = null;
this.onBookSelected.emit(this.selectedBook);
}
this.books.splice(bookIndex, 1); //remove the book at this index;
this.newBooks.splice(bookIndex, 1); //remove the editing book at this index
}, (error) => {
console.log(error);
});
}
activateAddNewBook() {
this.isAddNewBook = true;
this.newBook = new Book(null, '', '');
}
addNewBook(newBook: Book, element: any) {
//write new book to db
this.httpService.createBook(newBook)
.subscribe((response: Response) => {
let bookJson = response.json();
let book: Book = new Book(bookJson.id, bookJson.author, bookJson.title);
console.log(book);
this.books.push(book);
this.newBooks.push(book);
this.newBook = new Book(Math.floor(Math.random() * 1000), '', '');
element.focus();
}, (error) => {
console.log(error);
});
}
cancelAddBook() {
this.isAddNewBook = false;
}
selectBook(book: Book) {
this.selectedBook = book;
this.onBookSelected.emit(book);
}
}

View File

@ -0,0 +1,13 @@
import {Directive, HostListener} from "@angular/core";
@Directive({
selector: '[appClickStopPropagation]'
})
export class ClickStopPropagationDirective
{
@HostListener("click", ["$event"])
public onClick(event: any): void
{
event.stopPropagation();
}
}

View File

@ -0,0 +1,56 @@
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {Response, Http, Headers, RequestOptions} from "@angular/http";
import {Book} from "./book";
import {Rating} from "./rating";
@Injectable()
export class HttpService {
constructor(private http: Http) { }
me(): Observable<Response> {
return this.http.get("/me", this.makeOptions())
}
logout(): Observable<Response> {
return this.http.post("/logout", '', this.makeOptions())
}
getBooks(): Observable<Response> {
return this.http.get("/book-service/books", this.makeOptions())
}
updateBook(newBook: Book): Observable<Response> {
return this.http.put("/book-service/books/" + newBook.id, newBook, this.makeOptions())
}
deleteBook(book: Book): Observable<Response> {
return this.http.delete("/book-service/books/" + book.id, this.makeOptions())
}
createBook(newBook: Book): Observable<Response> {
return this.http.post("/book-service/books", newBook, this.makeOptions())
}
getRatings(bookId: number): Observable<Response> {
return this.http.get("/rating-service/ratings?bookId=" + bookId, this.makeOptions())
}
createRating(rating: Rating): Observable<Response> {
return this.http.post("/rating-service/ratings", rating, this.makeOptions())
}
deleteRating(ratingId: number) {
return this.http.delete("/rating-service/ratings/" + ratingId, this.makeOptions())
}
updateRating(rating: Rating) {
return this.http.put("/rating-service/ratings/" + rating.id, rating, this.makeOptions())
}
private makeOptions(): RequestOptions {
let headers = new Headers({'Content-Type': 'application/json'});
return new RequestOptions({headers: headers});
}
}

View File

@ -0,0 +1,21 @@
export class Principal {
public authenticated: boolean;
public authorities: Authority[] = [];
constructor(authenticated: boolean, authorities: any[]) {
this.authenticated = authenticated;
authorities.map(auth => this.authorities.push(new Authority(auth.authority)))
}
isAdmin() {
return this.authorities.some((auth: Authority) => auth.authority.indexOf('ADMIN') > -1)
}
}
export class Authority {
public authority: String;
constructor(authority: String) {
this.authority = authority;
}
}

View File

@ -0,0 +1,11 @@
export class Rating{
id: number;
bookId: number;
stars: number;
constructor(id: number, bookId: number, stars: number) {
this.id = id;
this.bookId = bookId;
this.stars = stars;
}
}

View File

@ -0,0 +1,11 @@
div.progress {
margin-top: 5px;
}
.rating:hover {
border: solid blue;
}
.selected {
border: solid blue;
}

View File

@ -0,0 +1,25 @@
Ratings:
<div *ngFor="let rating of ratings; let i = index;" class="row">
<div class="col-md-10">
<div class="progress" [ngClass]="{'selected': principal.isAdmin() && rating === newRating, 'rating': principal.isAdmin()}" (click)="selectRating(rating)">
<div class="progress-bar bg-success" role="progressbar" [style.width]="findWidth(rating)" [attr.aria-valuenow]="rating.stars" aria-valuemin="0" aria-valuemax="5"></div>
</div>
</div>
<div class="col-md-1">
<button *ngIf="principal?.isAdmin()" type="button" class="close" aria-label="Close" (click)="deleteRating(i)">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
<form (ngSubmit)="onSaveRating(f)" #f="ngForm">
<div class="form-check form-check-inline" *ngFor="let star of stars; let i = index;">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="star" [(ngModel)]="newRating.stars" [value]="star">{{star}}
</label>
</div>
<button *ngIf="newRating.id === null" type="submit" class="btn btn-secondary" [disabled]="!f.valid">Add Rating</button>
<button *ngIf="principal.isAdmin() && newRating.id !== null" type="button" class="btn btn-secondary" (click)="updateRating()">Save</button>
<button *ngIf="principal.isAdmin() && newRating.id !== null" type="button" class="btn btn-secondary" (click)="cancelSelection()">Cancel</button>
</form>

View File

@ -0,0 +1,90 @@
import {Component, OnInit, Input, OnChanges} from "@angular/core";
import {Rating} from "../rating";
import {Principal} from "../principal";
import {HttpService} from "../http.service";
import {Response} from "@angular/http";
@Component({
selector: 'app-rating',
templateUrl: './rating.component.html',
styleUrls: ['./rating.component.css']
})
export class RatingComponent implements OnInit, OnChanges {
@Input() bookId: number;
@Input() principal: Principal = null;
ratings: Rating[] = [];
stars: number[] = [1,2,3,4,5];
newRating: Rating = null;
constructor(private httpService: HttpService) { }
ngOnInit() {}
ngOnChanges() {
this.newRating = new Rating(null, this.bookId, 1);
this.ratings = [];
this.loadRatings();
}
findWidth(rating: Rating): String {
let percent: number = (rating.stars/5)*100;
return percent.toString() + '%';
}
private loadRatings() {
this.httpService.getRatings(this.bookId)
.subscribe((response: Response) => {
let responseJson: any[] = response.json();
responseJson.forEach(rating => this.ratings.push(new Rating(rating.id, rating.bookId, rating.stars)))
}, (error) => {
console.log(error);
});
}
onSaveRating() {
console.log(this.newRating);
let ratingCopy: Rating = Object.assign({}, this.newRating);
this.httpService.createRating(ratingCopy)
.subscribe((response: Response) => {
let ratingJson = response.json()
this.ratings.push(new Rating(ratingJson.id, ratingJson.bookId, ratingJson.stars))
}, (error) => {
console.log(error);
});
}
updateRating() {
this.httpService.updateRating(this.newRating)
.subscribe(() => {
this.newRating = new Rating(null, this.bookId, 1);
}, (error) => {
console.log(error);
});
}
selectRating(rating: Rating) {
if (this.principal.isAdmin()) {
this.newRating = rating;
}
}
cancelSelection() {
this.newRating = new Rating(null, this.bookId, 1);
}
deleteRating(index: number) {
let rating = this.ratings[index];
this.httpService.deleteRating(rating.id)
.subscribe(() => {
if (this.ratings[index] === this.newRating) {
this.newRating = new Rating(null, this.bookId, 1);
}
this.ratings.splice(index, 1);
}, (error) => {
console.log(error);
});
}
}

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,8 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Ui</title>
<base href="/home/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>

View File

@ -0,0 +1,10 @@
import {enableProdMode} from "@angular/core";
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {AppModule} from "./app/app.module";
import {environment} from "./environments/environment";
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,15 @@
import "core-js/es6/reflect";
import "core-js/es7/reflect";
import "zone.js/dist/zone";
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/**
* Date, currency, decimal and percent pipes.
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
*/
// import 'intl'; // Run `npm install --save intl`.

View File

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View File

@ -0,0 +1,27 @@
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/proxy.js";
import "zone.js/dist/sync-test";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";
import {getTestBed} from "@angular/core/testing";
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare var __karma__: any;
declare var require: any;
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"baseUrl": "",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,20 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"module": "commonjs",
"target": "es5",
"baseUrl": "",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}

View File

@ -0,0 +1,20 @@
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"baseUrl": "src",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2016",
"dom"
]
}
}

View File

@ -0,0 +1,116 @@
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"import-blacklist": [true, "rxjs"],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [true, "ignore-params"],
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"typeof-compare": true,
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector": [true, "attribute", "app", "camelCase"],
"component-selector": [true, "element", "app", "kebab-case"],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true,
"no-access-missing-member": true,
"templates-use-public": true,
"invoke-injectable": true
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
public class AuthenticationController {
@GetMapping("/me")
public Principal getMyUser(Principal principal) {
return principal;
}
}

View File

@ -0,0 +1,16 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.boot.web.servlet.ErrorPageRegistrar;
import org.springframework.boot.web.servlet.ErrorPageRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@Component
public class ErrorPageConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/home/index.html"));
}
}

View File

@ -21,18 +21,17 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/book-service/books").permitAll()
.antMatchers("/zipkin/**").permitAll()
http
.formLogin()
.defaultSuccessUrl("/home/index.html", true)
.and()
.authorizeRequests()
.antMatchers("/book-service/**", "/rating-service/**", "/login*", "/").permitAll()
.antMatchers("/eureka/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.logout()
.and()
.logout().permitAll()
.logoutSuccessUrl("/book-service/books").permitAll()
.and()
.csrf()
.disable();
.csrf().disable();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Ui</title>
<base href="/home/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root>Loading...</app-root>
<script type="text/javascript" src="inline.bundle.js"></script><script type="text/javascript" src="polyfills.bundle.js"></script><script type="text/javascript" src="styles.bundle.js"></script><script type="text/javascript" src="vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body>
</html>

View File

@ -0,0 +1,146 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [], result;
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId])
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/ while(resolves.length)
/******/ resolves.shift()();
/******/ if(executeModules) {
/******/ for(i=0; i < executeModules.length; i++) {
/******/ result = __webpack_require__(__webpack_require__.s = executeModules[i]);
/******/ }
/******/ }
/******/ return result;
/******/ };
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // objects to store loaded and loading chunks
/******/ var installedChunks = {
/******/ 4: 0
/******/ };
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = function requireEnsure(chunkId) {
/******/ if(installedChunks[chunkId] === 0)
/******/ return Promise.resolve();
/******/
/******/ // an Promise means "currently loading".
/******/ if(installedChunks[chunkId]) {
/******/ return installedChunks[chunkId][2];
/******/ }
/******/ // start chunk loading
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = 'text/javascript';
/******/ script.charset = 'utf-8';
/******/ script.async = true;
/******/ script.timeout = 120000;
/******/
/******/ if (__webpack_require__.nc) {
/******/ script.setAttribute("nonce", __webpack_require__.nc);
/******/ }
/******/ script.src = __webpack_require__.p + "" + chunkId + ".chunk.js";
/******/ var timeout = setTimeout(onScriptComplete, 120000);
/******/ script.onerror = script.onload = onScriptComplete;
/******/ function onScriptComplete() {
/******/ // avoid mem leaks in IE.
/******/ script.onerror = script.onload = null;
/******/ clearTimeout(timeout);
/******/ var chunk = installedChunks[chunkId];
/******/ if(chunk !== 0) {
/******/ if(chunk) chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
/******/ installedChunks[chunkId] = undefined;
/******/ }
/******/ };
/******/
/******/ var promise = new Promise(function(resolve, reject) {
/******/ installedChunks[chunkId] = [resolve, reject];
/******/ });
/******/ installedChunks[chunkId][2] = promise;
/******/
/******/ head.appendChild(script);
/******/ return promise;
/******/ };
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // on error function for async loading
/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; };
/******/ })
/************************************************************************/
/******/ ([]);
//# sourceMappingURL=inline.bundle.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,788 @@
webpackJsonp([1,4],{
/***/ 144:
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(13)();
// imports
// module
exports.push([module.i, ".custom-close {\r\n float:right;\r\n}\r\n", ""]);
// exports
/*** EXPORTS FROM exports-loader ***/
module.exports = module.exports.toString();
/***/ }),
/***/ 145:
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(13)();
// imports
// module
exports.push([module.i, "", ""]);
// exports
/*** EXPORTS FROM exports-loader ***/
module.exports = module.exports.toString();
/***/ }),
/***/ 146:
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(13)();
// imports
// module
exports.push([module.i, ".custom-close {\r\n float:right;\r\n}\r\n", ""]);
// exports
/*** EXPORTS FROM exports-loader ***/
module.exports = module.exports.toString();
/***/ }),
/***/ 147:
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(13)();
// imports
// module
exports.push([module.i, "div.progress {\r\n margin-top: 5px;\r\n}\r\n\r\n.rating:hover {\r\n border: solid blue;\r\n}\r\n\r\n.selected {\r\n border: solid blue;\r\n}\r\n", ""]);
// exports
/*** EXPORTS FROM exports-loader ***/
module.exports = module.exports.toString();
/***/ }),
/***/ 149:
/***/ (function(module, exports) {
module.exports = "<nav class=\"navbar navbar-toggleable-md navbar-inverse fixed-top bg-inverse\">\r\n <button class=\"navbar-toggler navbar-toggler-right\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbarCollapse\" aria-controls=\"navbarCollapse\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\r\n <span class=\"navbar-toggler-icon\"></span>\r\n </button>\r\n <a class=\"navbar-brand\" href=\"#\">Book Rater <span *ngIf=\"principal.isAdmin()\">Admin</span></a>\r\n <div class=\"collapse navbar-collapse\" id=\"navbarCollapse\">\r\n <ul class=\"navbar-nav mr-auto\">\r\n </ul>\r\n <button *ngIf=\"principal.authenticated\" type=\"button\" class=\"btn btn-link\" (click)=\"onLogout()\">Logout</button>\r\n </div>\r\n</nav>\r\n\r\n<div class=\"jumbotron\">\r\n <div class=\"container\">\r\n <h1>Book Rater App</h1>\r\n <p *ngIf=\"!principal.authenticated\" class=\"lead\">Anyone can view the books.</p>\r\n <p *ngIf=\"principal.authenticated && !principal.isAdmin()\" class=\"lead\">Users can view and create ratings</p>\r\n <p *ngIf=\"principal.isAdmin()\" class=\"lead\">Admins can do anything!</p>\r\n </div>\r\n</div>\r\n\r\n<section class=\"books\">\r\n <div class=\"container\">\r\n <div class=\"row\">\r\n <div class=\"col-md\">\r\n <div class=\"row\">\r\n <div class=\"col-md-12\">\r\n <app-book-list [principal]=\"principal\" (onBookSelected)=\"selectBook($event)\"></app-book-list>\r\n </div>\r\n </div>\r\n </div>\r\n <div *ngIf=\"selectedBook != null\" class=\"col-md-3\">\r\n <app-book-detail [selectedBook]=\"selectedBook\" [principal]=\"principal\" (closeBook)=\"closeBookDetail()\"></app-book-detail>\r\n </div>\r\n </div>\r\n </div>\r\n</section>\r\n"
/***/ }),
/***/ 150:
/***/ (function(module, exports) {
module.exports = "<div class=\"card\">\r\n <div class=\"card-block\">\r\n <button type=\"button\" class=\"close\" aria-label=\"Close\" (click)=\"closeBookDetail()\">\r\n <span aria-hidden=\"true\">&times;</span>\r\n </button>\r\n <h4 class=\"card-title\">Title: {{selectedBook.title}}</h4>\r\n <h6 class=\"card-subtitle mb-2 text-muted\">Author: {{selectedBook.author}}</h6>\r\n <p class=\"card-text\">A quick summary of the book</p>\r\n <app-rating *ngIf=\"principal.authenticated\" [bookId]=\"selectedBook.id\" [principal]=\"principal\"></app-rating>\r\n </div>\r\n</div>\r\n"
/***/ }),
/***/ 151:
/***/ (function(module, exports) {
module.exports = "<div class=\"col-md-12\" *ngFor=\"let book of books; let i = index;\" (click)=\"selectBook(book)\">\r\n <div class=\"card\">\r\n <div class=\"card-block\">\r\n <div *ngIf=\"booksToEdit.indexOf(i) === -1 ; then bookView else bookEdit\"></div>\r\n <ng-template #bookView>\r\n <button appClickStopPropagation *ngIf=\"principal.isAdmin()\" type=\"button\" class=\"btn btn-danger custom-close\" (click)=\"delete(i)\">Delete</button>\r\n <button appClickStopPropagation *ngIf=\"principal.isAdmin()\" type=\"button\" class=\"btn btn-warning custom-close\" (click)=\"editBook(i)\">Edit</button>\r\n <h4 class=\"card-title\">Title: {{book.title}}</h4>\r\n <h6 class=\"card-subtitle mb-2 text-muted\">Author: {{book.author}}</h6>\r\n </ng-template>\r\n <ng-template #bookEdit>\r\n <button appClickStopPropagation type=\"button\" class=\"btn btn-secondary custom-close\" (click)=\"cancelEditBook(i)\">Cancel</button>\r\n <form appClickStopPropagation (ngSubmit)=\"saveBook(i, newBooks[i])\" class=\"mt-2 mt-md-0\" #f1=\"ngForm\">\r\n <div class=\"form-group\">\r\n <label for=\"title\">Title:</label>\r\n <input id=\"title\" name=\"title\" [(ngModel)]=\"newBooks[i].title\" required class=\"form-control mr-sm-2\" type=\"text\">\r\n </div>\r\n <div class=\"form-group\">\r\n <label for=\"author\">Author:</label>\r\n <input id=\"author\" name=\"author\" [(ngModel)]=\"newBooks[i].author\" required class=\"form-control mr-sm-2\" type=\"text\">\r\n </div>\r\n <button class=\"btn btn-outline-success my-2 my-sm-0\" type=\"submit\" [disabled]=\"!f1.valid\">Save</button>\r\n </form>\r\n </ng-template>\r\n\r\n </div>\r\n </div>\r\n</div>\r\n<div *ngIf=\"principal.isAdmin()\" class=\"col-md-12\">\r\n <div class=\"card\">\r\n <div class=\"card-block\">\r\n <div *ngIf=\"!isAddNewBook; then bookPlaceHolder else bookAdd\"></div>\r\n <ng-template #bookPlaceHolder>\r\n <h4 (click)=\"activateAddNewBook()\" class=\"card-title center-block\">Add New Book</h4>\r\n </ng-template>\r\n <ng-template #bookAdd>\r\n <button appClickStopPropagation type=\"button\" class=\"btn btn-secondary custom-close\" (click)=\"cancelAddBook()\">Cancel</button>\r\n <form appClickStopPropagation (ngSubmit)=\"addNewBook(newBook, titleNewBook)\" class=\"mt-2 mt-md-0\" #f2=\"ngForm\">\r\n <div class=\"form-group\">\r\n <label for=\"titleNewBook\">Title:</label>\r\n <input id=\"titleNewBook\" name=\"title\" [(ngModel)]=\"newBook.title\" required class=\"form-control mr-sm-2\" type=\"text\" #titleNewBook>\r\n </div>\r\n <div class=\"form-group\">\r\n <label for=\"authorNewBook\">Author:</label>\r\n <input id=\"authorNewBook\" name=\"author\" [(ngModel)]=\"newBook.author\" required class=\"form-control mr-sm-2\" type=\"text\">\r\n </div>\r\n <button class=\"btn btn-outline-success my-2 my-sm-0\" type=\"submit\" [disabled]=\"!f2.valid\">Save</button>\r\n </form>\r\n </ng-template>\r\n\r\n </div>\r\n </div>\r\n\r\n</div>\r\n"
/***/ }),
/***/ 152:
/***/ (function(module, exports) {
module.exports = "Ratings:\r\n<div *ngFor=\"let rating of ratings; let i = index;\" class=\"row\">\r\n <div class=\"col-md-10\">\r\n <div class=\"progress\" [ngClass]=\"{'selected': principal.isAdmin() && rating === newRating, 'rating': principal.isAdmin()}\" (click)=\"selectRating(rating)\">\r\n <div class=\"progress-bar bg-success\" role=\"progressbar\" [style.width]=\"findWidth(rating)\" [attr.aria-valuenow]=\"rating.stars\" aria-valuemin=\"0\" aria-valuemax=\"5\"></div>\r\n </div>\r\n </div>\r\n <div class=\"col-md-1\">\r\n <button *ngIf=\"principal?.isAdmin()\" type=\"button\" class=\"close\" aria-label=\"Close\" (click)=\"deleteRating(i)\">\r\n <span aria-hidden=\"true\">&times;</span>\r\n </button>\r\n </div>\r\n</div>\r\n\r\n<form (ngSubmit)=\"onSaveRating(f)\" #f=\"ngForm\">\r\n <div class=\"form-check form-check-inline\" *ngFor=\"let star of stars; let i = index;\">\r\n <label class=\"form-check-label\">\r\n <input class=\"form-check-input\" type=\"radio\" name=\"star\" [(ngModel)]=\"newRating.stars\" [value]=\"star\">{{star}}\r\n </label>\r\n </div>\r\n <button *ngIf=\"newRating.id === null\" type=\"submit\" class=\"btn btn-secondary\" [disabled]=\"!f.valid\">Add Rating</button>\r\n <button *ngIf=\"principal.isAdmin() && newRating.id !== null\" type=\"button\" class=\"btn btn-secondary\" (click)=\"updateRating()\">Save</button>\r\n <button *ngIf=\"principal.isAdmin() && newRating.id !== null\" type=\"button\" class=\"btn btn-secondary\" (click)=\"cancelSelection()\">Cancel</button>\r\n</form>\r\n\r\n"
/***/ }),
/***/ 176:
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(75);
/***/ }),
/***/ 22:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__angular_http__ = __webpack_require__(50);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return HttpService; });
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var HttpService = (function () {
function HttpService(http) {
this.http = http;
}
HttpService.prototype.me = function () {
return this.http.get("/me", this.makeOptions());
};
HttpService.prototype.logout = function () {
return this.http.post("/logout", '', this.makeOptions());
};
HttpService.prototype.getBooks = function () {
return this.http.get("/book-service/books", this.makeOptions());
};
HttpService.prototype.updateBook = function (newBook) {
return this.http.put("/book-service/books/" + newBook.id, newBook, this.makeOptions());
};
HttpService.prototype.deleteBook = function (book) {
return this.http.delete("/book-service/books/" + book.id, this.makeOptions());
};
HttpService.prototype.createBook = function (newBook) {
return this.http.post("/book-service/books", newBook, this.makeOptions());
};
HttpService.prototype.getRatings = function (bookId) {
return this.http.get("/rating-service/ratings?bookId=" + bookId, this.makeOptions());
};
HttpService.prototype.createRating = function (rating) {
return this.http.post("/rating-service/ratings", rating, this.makeOptions());
};
HttpService.prototype.deleteRating = function (ratingId) {
return this.http.delete("/rating-service/ratings/" + ratingId, this.makeOptions());
};
HttpService.prototype.updateRating = function (rating) {
return this.http.put("/rating-service/ratings/" + rating.id, rating, this.makeOptions());
};
HttpService.prototype.makeOptions = function () {
var headers = new __WEBPACK_IMPORTED_MODULE_1__angular_http__["b" /* Headers */]({ 'Content-Type': 'application/json' });
return new __WEBPACK_IMPORTED_MODULE_1__angular_http__["c" /* RequestOptions */]({ headers: headers });
};
return HttpService;
}());
HttpService = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["c" /* Injectable */])(),
__metadata("design:paramtypes", [typeof (_a = typeof __WEBPACK_IMPORTED_MODULE_1__angular_http__["d" /* Http */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_1__angular_http__["d" /* Http */]) === "function" && _a || Object])
], HttpService);
var _a;
//# sourceMappingURL=http.service.js.map
/***/ }),
/***/ 23:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Principal; });
/* unused harmony export Authority */
var Principal = (function () {
function Principal(authenticated, authorities) {
var _this = this;
this.authorities = [];
this.authenticated = authenticated;
authorities.map(function (auth) { return _this.authorities.push(new Authority(auth.authority)); });
}
Principal.prototype.isAdmin = function () {
return this.authorities.some(function (auth) { return auth.authority.indexOf('ADMIN') > -1; });
};
return Principal;
}());
var Authority = (function () {
function Authority(authority) {
this.authority = authority;
}
return Authority;
}());
//# sourceMappingURL=principal.js.map
/***/ }),
/***/ 51:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Book; });
var Book = (function () {
function Book(id, author, title) {
this.id = id;
this.author = author;
this.title = title;
}
return Book;
}());
//# sourceMappingURL=book.js.map
/***/ }),
/***/ 74:
/***/ (function(module, exports) {
function webpackEmptyContext(req) {
throw new Error("Cannot find module '" + req + "'.");
}
webpackEmptyContext.keys = function() { return []; };
webpackEmptyContext.resolve = webpackEmptyContext;
module.exports = webpackEmptyContext;
webpackEmptyContext.id = 74;
/***/ }),
/***/ 75:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__angular_platform_browser_dynamic__ = __webpack_require__(81);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__app_app_module__ = __webpack_require__(83);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__environments_environment__ = __webpack_require__(89);
if (__WEBPACK_IMPORTED_MODULE_3__environments_environment__["a" /* environment */].production) {
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["a" /* enableProdMode */])();
}
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__angular_platform_browser_dynamic__["a" /* platformBrowserDynamic */])().bootstrapModule(__WEBPACK_IMPORTED_MODULE_2__app_app_module__["a" /* AppModule */]);
//# sourceMappingURL=main.js.map
/***/ }),
/***/ 82:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__principal__ = __webpack_require__(23);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__http_service__ = __webpack_require__(22);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return AppComponent; });
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var AppComponent = (function () {
function AppComponent(httpService) {
this.httpService = httpService;
this.selectedBook = null;
this.principal = new __WEBPACK_IMPORTED_MODULE_1__principal__["a" /* Principal */](false, []);
this.loginFailed = false;
}
AppComponent.prototype.ngOnInit = function () {
var _this = this;
this.httpService.me()
.subscribe(function (response) {
var principalJson = response.json();
_this.principal = new __WEBPACK_IMPORTED_MODULE_1__principal__["a" /* Principal */](principalJson.authenticated, principalJson.authorities);
}, function (error) {
console.log(error);
});
};
AppComponent.prototype.onLogout = function () {
var _this = this;
this.httpService.logout()
.subscribe(function (response) {
if (response.status === 200) {
_this.loginFailed = false;
_this.principal = new __WEBPACK_IMPORTED_MODULE_1__principal__["a" /* Principal */](false, []);
window.location.replace(response.url);
}
}, function (error) {
console.log(error);
});
};
AppComponent.prototype.closeBookDetail = function () {
this.selectedBook = null;
};
AppComponent.prototype.selectBook = function (book) {
this.selectedBook = book;
};
return AppComponent;
}());
AppComponent = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["_3" /* Component */])({
selector: 'app-root',
template: __webpack_require__(149),
styles: [__webpack_require__(144)]
}),
__metadata("design:paramtypes", [typeof (_a = typeof __WEBPACK_IMPORTED_MODULE_2__http_service__["a" /* HttpService */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_2__http_service__["a" /* HttpService */]) === "function" && _a || Object])
], AppComponent);
var _a;
//# sourceMappingURL=app.component.js.map
/***/ }),
/***/ 83:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_platform_browser__ = __webpack_require__(21);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__angular_core__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__angular_forms__ = __webpack_require__(80);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__angular_http__ = __webpack_require__(50);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__app_component__ = __webpack_require__(82);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__rating_rating_component__ = __webpack_require__(88);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__click_stop_propagation_directive__ = __webpack_require__(86);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__book_book_detail_book_detail_component__ = __webpack_require__(84);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8__book_book_list_book_list_component__ = __webpack_require__(85);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_9__http_service__ = __webpack_require__(22);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return AppModule; });
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var AppModule = (function () {
function AppModule() {
}
return AppModule;
}());
AppModule = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__angular_core__["b" /* NgModule */])({
declarations: [
__WEBPACK_IMPORTED_MODULE_4__app_component__["a" /* AppComponent */],
__WEBPACK_IMPORTED_MODULE_5__rating_rating_component__["a" /* RatingComponent */],
__WEBPACK_IMPORTED_MODULE_6__click_stop_propagation_directive__["a" /* ClickStopPropagationDirective */],
__WEBPACK_IMPORTED_MODULE_7__book_book_detail_book_detail_component__["a" /* BookDetailComponent */],
__WEBPACK_IMPORTED_MODULE_8__book_book_list_book_list_component__["a" /* BookListComponent */]
],
imports: [
__WEBPACK_IMPORTED_MODULE_0__angular_platform_browser__["a" /* BrowserModule */],
__WEBPACK_IMPORTED_MODULE_2__angular_forms__["a" /* FormsModule */],
__WEBPACK_IMPORTED_MODULE_3__angular_http__["a" /* HttpModule */]
],
providers: [__WEBPACK_IMPORTED_MODULE_9__http_service__["a" /* HttpService */]],
bootstrap: [__WEBPACK_IMPORTED_MODULE_4__app_component__["a" /* AppComponent */]]
})
], AppModule);
//# sourceMappingURL=app.module.js.map
/***/ }),
/***/ 84:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__book__ = __webpack_require__(51);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__principal__ = __webpack_require__(23);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return BookDetailComponent; });
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var BookDetailComponent = (function () {
function BookDetailComponent() {
this.selectedBook = null;
this.principal = null;
this.closeBook = new __WEBPACK_IMPORTED_MODULE_0__angular_core__["D" /* EventEmitter */]();
}
BookDetailComponent.prototype.ngOnInit = function () {
};
BookDetailComponent.prototype.closeBookDetail = function () {
this.closeBook.emit(null);
};
return BookDetailComponent;
}());
__decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["M" /* Input */])(),
__metadata("design:type", typeof (_a = typeof __WEBPACK_IMPORTED_MODULE_1__book__["a" /* Book */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_1__book__["a" /* Book */]) === "function" && _a || Object)
], BookDetailComponent.prototype, "selectedBook", void 0);
__decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["M" /* Input */])(),
__metadata("design:type", typeof (_b = typeof __WEBPACK_IMPORTED_MODULE_2__principal__["a" /* Principal */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_2__principal__["a" /* Principal */]) === "function" && _b || Object)
], BookDetailComponent.prototype, "principal", void 0);
__decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["_2" /* Output */])(),
__metadata("design:type", typeof (_c = typeof __WEBPACK_IMPORTED_MODULE_0__angular_core__["D" /* EventEmitter */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_0__angular_core__["D" /* EventEmitter */]) === "function" && _c || Object)
], BookDetailComponent.prototype, "closeBook", void 0);
BookDetailComponent = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["_3" /* Component */])({
selector: 'app-book-detail',
template: __webpack_require__(150),
styles: [__webpack_require__(145)]
}),
__metadata("design:paramtypes", [])
], BookDetailComponent);
var _a, _b, _c;
//# sourceMappingURL=book-detail.component.js.map
/***/ }),
/***/ 85:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__principal__ = __webpack_require__(23);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__book__ = __webpack_require__(51);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__http_service__ = __webpack_require__(22);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return BookListComponent; });
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var BookListComponent = (function () {
function BookListComponent(httpService) {
this.httpService = httpService;
this.principal = null;
this.onBookSelected = new __WEBPACK_IMPORTED_MODULE_0__angular_core__["D" /* EventEmitter */]();
this.books = [];
this.newBooks = [];
this.newBook = new __WEBPACK_IMPORTED_MODULE_2__book__["a" /* Book */](Math.floor(Math.random() * 1000), '', '');
this.booksToEdit = [];
this.isAddNewBook = false;
this.selectedBook = null;
}
BookListComponent.prototype.ngOnInit = function () {
this.loadBooks();
};
BookListComponent.prototype.loadBooks = function () {
var _this = this;
this.httpService.getBooks()
.subscribe(function (response) {
var booksJson = response.json();
booksJson.forEach(function (book) {
_this.books.push(new __WEBPACK_IMPORTED_MODULE_2__book__["a" /* Book */](book.id, book.author, book.title));
_this.newBooks.push(new __WEBPACK_IMPORTED_MODULE_2__book__["a" /* Book */](book.id, book.author, book.title));
});
}, function (error) {
console.log(error);
});
};
BookListComponent.prototype.cancelEditBook = function (bookIndex) {
if (this.booksToEdit.indexOf(bookIndex) !== -1) {
this.booksToEdit.splice(this.booksToEdit.indexOf(bookIndex), 1); //remove the index of the book to edit
//get the original book
var bookCopy = new __WEBPACK_IMPORTED_MODULE_2__book__["a" /* Book */](this.books[bookIndex].id, this.books[bookIndex].author, this.books[bookIndex].title);
this.newBooks.splice(bookIndex, 1, bookCopy); //replace the edited book with the old book
}
};
BookListComponent.prototype.editBook = function (bookIndex) {
this.booksToEdit.push(bookIndex);
};
BookListComponent.prototype.saveBook = function (bookIndex, newBook) {
var _this = this;
console.log(newBook);
//save the book to the database
this.httpService.updateBook(newBook)
.subscribe(function (response) {
var bookJson = response.json();
var book = new __WEBPACK_IMPORTED_MODULE_2__book__["a" /* Book */](bookJson.id, bookJson.author, bookJson.title);
//update the current array of books
var bookArr = _this.books.find(function (b) { return b.id === book.id; });
bookArr.title = book.title;
bookArr.author = book.author;
_this.booksToEdit.splice(_this.booksToEdit.indexOf(bookIndex), 1); //remove the index of the book to edit
}, function (error) {
console.log(error);
});
};
BookListComponent.prototype.delete = function (bookIndex) {
var _this = this;
var book = this.books[bookIndex];
this.httpService.deleteBook(book)
.subscribe(function () {
if (_this.selectedBook !== null && _this.books[bookIndex].id === _this.selectedBook.id) {
_this.selectedBook = null;
_this.onBookSelected.emit(_this.selectedBook);
}
_this.books.splice(bookIndex, 1); //remove the book at this index;
_this.newBooks.splice(bookIndex, 1); //remove the editing book at this index
}, function (error) {
console.log(error);
});
};
BookListComponent.prototype.activateAddNewBook = function () {
this.isAddNewBook = true;
this.newBook = new __WEBPACK_IMPORTED_MODULE_2__book__["a" /* Book */](null, '', '');
};
BookListComponent.prototype.addNewBook = function (newBook, element) {
var _this = this;
//write new book to db
this.httpService.createBook(newBook)
.subscribe(function (response) {
var bookJson = response.json();
var book = new __WEBPACK_IMPORTED_MODULE_2__book__["a" /* Book */](bookJson.id, bookJson.author, bookJson.title);
console.log(book);
_this.books.push(book);
_this.newBooks.push(book);
_this.newBook = new __WEBPACK_IMPORTED_MODULE_2__book__["a" /* Book */](Math.floor(Math.random() * 1000), '', '');
element.focus();
}, function (error) {
console.log(error);
});
};
BookListComponent.prototype.cancelAddBook = function () {
this.isAddNewBook = false;
};
BookListComponent.prototype.selectBook = function (book) {
this.selectedBook = book;
this.onBookSelected.emit(book);
};
return BookListComponent;
}());
__decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["M" /* Input */])(),
__metadata("design:type", typeof (_a = typeof __WEBPACK_IMPORTED_MODULE_1__principal__["a" /* Principal */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_1__principal__["a" /* Principal */]) === "function" && _a || Object)
], BookListComponent.prototype, "principal", void 0);
__decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["_2" /* Output */])(),
__metadata("design:type", typeof (_b = typeof __WEBPACK_IMPORTED_MODULE_0__angular_core__["D" /* EventEmitter */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_0__angular_core__["D" /* EventEmitter */]) === "function" && _b || Object)
], BookListComponent.prototype, "onBookSelected", void 0);
BookListComponent = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["_3" /* Component */])({
selector: 'app-book-list',
template: __webpack_require__(151),
styles: [__webpack_require__(146)]
}),
__metadata("design:paramtypes", [typeof (_c = typeof __WEBPACK_IMPORTED_MODULE_3__http_service__["a" /* HttpService */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_3__http_service__["a" /* HttpService */]) === "function" && _c || Object])
], BookListComponent);
var _a, _b, _c;
//# sourceMappingURL=book-list.component.js.map
/***/ }),
/***/ 86:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__(2);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ClickStopPropagationDirective; });
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var ClickStopPropagationDirective = (function () {
function ClickStopPropagationDirective() {
}
ClickStopPropagationDirective.prototype.onClick = function (event) {
event.stopPropagation();
};
return ClickStopPropagationDirective;
}());
__decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["_4" /* HostListener */])("click", ["$event"]),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], ClickStopPropagationDirective.prototype, "onClick", null);
ClickStopPropagationDirective = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["H" /* Directive */])({
selector: '[appClickStopPropagation]'
})
], ClickStopPropagationDirective);
//# sourceMappingURL=click-stop-propagation.directive.js.map
/***/ }),
/***/ 87:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Rating; });
var Rating = (function () {
function Rating(id, bookId, stars) {
this.id = id;
this.bookId = bookId;
this.stars = stars;
}
return Rating;
}());
//# sourceMappingURL=rating.js.map
/***/ }),
/***/ 88:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_core__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__rating__ = __webpack_require__(87);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__principal__ = __webpack_require__(23);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__http_service__ = __webpack_require__(22);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RatingComponent; });
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var RatingComponent = (function () {
function RatingComponent(httpService) {
this.httpService = httpService;
this.principal = null;
this.ratings = [];
this.stars = [1, 2, 3, 4, 5];
this.newRating = null;
}
RatingComponent.prototype.ngOnInit = function () { };
RatingComponent.prototype.ngOnChanges = function () {
this.newRating = new __WEBPACK_IMPORTED_MODULE_1__rating__["a" /* Rating */](null, this.bookId, 1);
this.ratings = [];
this.loadRatings();
};
RatingComponent.prototype.findWidth = function (rating) {
var percent = (rating.stars / 5) * 100;
return percent.toString() + '%';
};
RatingComponent.prototype.loadRatings = function () {
var _this = this;
this.httpService.getRatings(this.bookId)
.subscribe(function (response) {
var responseJson = response.json();
responseJson.forEach(function (rating) { return _this.ratings.push(new __WEBPACK_IMPORTED_MODULE_1__rating__["a" /* Rating */](rating.id, rating.bookId, rating.stars)); });
}, function (error) {
console.log(error);
});
};
RatingComponent.prototype.onSaveRating = function () {
var _this = this;
console.log(this.newRating);
var ratingCopy = Object.assign({}, this.newRating);
this.httpService.createRating(ratingCopy)
.subscribe(function (response) {
var ratingJson = response.json();
_this.ratings.push(new __WEBPACK_IMPORTED_MODULE_1__rating__["a" /* Rating */](ratingJson.id, ratingJson.bookId, ratingJson.stars));
}, function (error) {
console.log(error);
});
};
RatingComponent.prototype.updateRating = function () {
var _this = this;
this.httpService.updateRating(this.newRating)
.subscribe(function () {
_this.newRating = new __WEBPACK_IMPORTED_MODULE_1__rating__["a" /* Rating */](null, _this.bookId, 1);
}, function (error) {
console.log(error);
});
};
RatingComponent.prototype.selectRating = function (rating) {
if (this.principal.isAdmin()) {
this.newRating = rating;
}
};
RatingComponent.prototype.cancelSelection = function () {
this.newRating = new __WEBPACK_IMPORTED_MODULE_1__rating__["a" /* Rating */](null, this.bookId, 1);
};
RatingComponent.prototype.deleteRating = function (index) {
var _this = this;
var rating = this.ratings[index];
this.httpService.deleteRating(rating.id)
.subscribe(function () {
if (_this.ratings[index] === _this.newRating) {
_this.newRating = new __WEBPACK_IMPORTED_MODULE_1__rating__["a" /* Rating */](null, _this.bookId, 1);
}
_this.ratings.splice(index, 1);
}, function (error) {
console.log(error);
});
};
return RatingComponent;
}());
__decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["M" /* Input */])(),
__metadata("design:type", Number)
], RatingComponent.prototype, "bookId", void 0);
__decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["M" /* Input */])(),
__metadata("design:type", typeof (_a = typeof __WEBPACK_IMPORTED_MODULE_2__principal__["a" /* Principal */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_2__principal__["a" /* Principal */]) === "function" && _a || Object)
], RatingComponent.prototype, "principal", void 0);
RatingComponent = __decorate([
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["_3" /* Component */])({
selector: 'app-rating',
template: __webpack_require__(152),
styles: [__webpack_require__(147)]
}),
__metadata("design:paramtypes", [typeof (_b = typeof __WEBPACK_IMPORTED_MODULE_3__http_service__["a" /* HttpService */] !== "undefined" && __WEBPACK_IMPORTED_MODULE_3__http_service__["a" /* HttpService */]) === "function" && _b || Object])
], RatingComponent);
var _a, _b;
//# sourceMappingURL=rating.component.js.map
/***/ }),
/***/ 89:
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return environment; });
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
// The file contents for the current environment will overwrite these during build.
var environment = {
production: false
};
//# sourceMappingURL=environment.js.map
/***/ })
},[176]);
//# sourceMappingURL=main.bundle.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Book Rater Landing</title>
</head>
<body>
<h1>Book Rater</h1>
<p>So many great things about the books</p>
<a href="/login">Login</a>
</body>
</html>

View File

@ -1,201 +0,0 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.apache.http.entity.ContentType;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
public class IntegrationLiveTest {
private TestRestTemplate testRestTemplate = new TestRestTemplate();
private String testUrl = "http://localhost:8080";
@Test
public void testAccess() throws Exception {
ResponseEntity<String> response = testRestTemplate.getForEntity(testUrl + "/book-service/books", String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
//try the protected resource and confirm the redirect to login
response = testRestTemplate.getForEntity(testUrl + "/book-service/books/1", String.class);
Assert.assertEquals(HttpStatus.FOUND, response.getStatusCode());
Assert.assertEquals("http://localhost:8080/login", response.getHeaders().get("Location").get(0));
//login as user/password
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "user");
form.add("password", "password");
response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
String sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
addBook();
//request the protected resource
response = testRestTemplate.exchange(testUrl + "/book-service/books/1", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
addRatings();
//request the admin protected resource to determine it is still protected
response = testRestTemplate.exchange(testUrl + "/rating-service/ratings", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
//login as the admin
form.clear();
form.add("username", "admin");
form.add("password", "admin");
response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
httpEntity = new HttpEntity<>(headers);
//request the protected resource
response = testRestTemplate.exchange(testUrl + "/rating-service/ratings", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertNotNull(response.getBody());
//request the discovery resources as the admin
response = testRestTemplate.exchange(testUrl + "/discovery", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
}
private void addRatings() {
//login as user/password
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "user");
form.add("password", "password");
ResponseEntity<String> response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
String sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
headers.add("ContentType", ContentType.APPLICATION_JSON.getMimeType());
Rating rating = new Rating(1L, 4);
HttpEntity<Rating> httpEntity = new HttpEntity<>(rating, headers);
//request the protected resource
ResponseEntity<Rating> bookResponse = testRestTemplate.postForEntity(testUrl + "/rating-service/ratings", httpEntity, Rating.class);
Assert.assertEquals(HttpStatus.OK, bookResponse.getStatusCode());
Assert.assertEquals(rating.getBookId(), bookResponse.getBody().getBookId());
Assert.assertEquals(rating.getStars(), bookResponse.getBody().getStars());
}
private void addBook(){
//login as user/password
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("username", "admin");
form.add("password", "admin");
ResponseEntity<String> response = testRestTemplate.postForEntity(testUrl + "/login", form, String.class);
//extract the session from the cookie and propagate it to the next request
String sessionCookie = response.getHeaders().get("Set-Cookie").get(0).split(";")[0];
HttpHeaders headers = new HttpHeaders();
headers.add("Cookie", sessionCookie);
headers.add("ContentType", ContentType.APPLICATION_JSON.getMimeType());
Book book = new Book("Baeldung", "How to spring cloud");
HttpEntity<Book> httpEntity = new HttpEntity<>(book, headers);
//request the protected resource
ResponseEntity<Book> bookResponse = testRestTemplate.postForEntity(testUrl + "/book-service/books", httpEntity, Book.class);
Assert.assertEquals(HttpStatus.OK, bookResponse.getStatusCode());
Assert.assertEquals(book.getAuthor(), bookResponse.getBody().getAuthor());
Assert.assertEquals(book.getTitle(), bookResponse.getBody().getTitle());
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Book {
private Long id;
private String author;
private String title;
public Book() {
}
public Book(String author, String title) {
this.author = author;
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Rating {
private Long id;
private Long bookId;
private int stars;
public Rating() {
}
public Rating(Long bookId, int stars) {
this.bookId = bookId;
this.stars = stars;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getBookId() {
return bookId;
}
public void setBookId(Long bookId) {
this.bookId = bookId;
}
public int getStars() {
return stars;
}
public void setStars(int stars) {
this.stars = stars;
}
}
}

View File

@ -1,18 +1,17 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import static io.restassured.RestAssured.config;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.restassured.RestAssured;
import io.restassured.authentication.FormAuthConfig;
import io.restassured.config.RedirectConfig;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import static io.restassured.RestAssured.config;
public class LiveTest {
@ -34,7 +33,7 @@ public class LiveTest {
@Test
public void whenAccessProtectedResourceWithoutLogin_thenRedirectToLogin() {
final Response response = RestAssured.get(ROOT_URI + "/book-service/books/1");
final Response response = RestAssured.get(ROOT_URI + "/home/index.html");
Assert.assertEquals(HttpStatus.FOUND.value(), response.getStatusCode());
Assert.assertEquals("http://localhost:8080/login", response.getHeader("Location"));
}

View File

@ -0,0 +1,25 @@
package com.baeldung.spring.cloud.bootstrap.svcbook;
import com.baeldung.spring.cloud.bootstrap.svcbook.book.Book;
import com.baeldung.spring.cloud.bootstrap.svcbook.book.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class DataLoader implements ApplicationRunner {
private BookService bookService;
@Autowired
public DataLoader(BookService bookService) {
this.bookService = bookService;
}
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
this.bookService.createBook(new Book("Aldous Huxley", "Brave new world"));
this.bookService.createBook(new Book("George Orwell", "Animal Farm"));
}
}

View File

@ -19,6 +19,11 @@ public class Book {
public Book() {
}
public Book(String author, String title) {
this.author = author;
this.title = title;
}
public Long getId() {
return id;
}

View File

@ -0,0 +1,28 @@
package com.baeldung.spring.cloud.bootstrap.svcrating;
import com.baeldung.spring.cloud.bootstrap.svcrating.rating.Rating;
import com.baeldung.spring.cloud.bootstrap.svcrating.rating.RatingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class DataLoader implements ApplicationRunner {
private RatingService ratingService;
@Autowired
public DataLoader(RatingService ratingService) {
this.ratingService = ratingService;
}
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
this.ratingService.createRating(new Rating(1L, 1));
this.ratingService.createRating(new Rating(1L, 2));
this.ratingService.createRating(new Rating(2L, 3));
this.ratingService.createRating(new Rating(2L, 4));
this.ratingService.createRating(new Rating(2L, 5));
}
}

View File

@ -26,6 +26,11 @@ public class Rating {
this.stars = stars;
}
public Rating(Long bookId, int stars) {
this.bookId = bookId;
this.stars = stars;
}
public Long getId() {
return id;
}

View File

@ -1,15 +1,14 @@
package com.baeldung.spring.cloud.bootstrap.svcrating.rating;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.common.base.Preconditions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.base.Preconditions;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
@Transactional(readOnly = true)
@ -58,6 +57,7 @@ public class RatingService {
return ratingRepository.save(rating);
}
@Transactional(propagation = Propagation.REQUIRED)
public Rating updateRating(Rating rating, Long ratingId) {
Preconditions.checkNotNull(rating);
Preconditions.checkState(rating.getId() == ratingId);