BAEL-3091: The Prototype Pattern in Java (changed code based on valid comments from a reader)

This commit is contained in:
Vivek Balasubramaniam
2019-10-29 22:27:15 +05:30
parent db85c8f275
commit d3d5b060e7
20517 changed files with 1642290 additions and 0 deletions
@@ -0,0 +1,21 @@
## Spring Cloud Bootstrap
This module contains articles about bootstrapping Spring Cloud applications
### Relevant Articles:
- [Spring Cloud Bootstrapping](http://www.baeldung.com/spring-cloud-bootstrapping)
- [Spring Cloud Securing Services](http://www.baeldung.com/spring-cloud-securing-services)
- [Spring Cloud Tracing Services with Zipkin](http://www.baeldung.com/tracing-services-with-zipkin)
- [Spring Cloud Series The Gateway Pattern](http://www.baeldung.com/spring-cloud-gateway-pattern)
- [Spring Cloud Adding Angular 4](http://www.baeldung.com/spring-cloud-angular)
### Running the Project
- To run the project:
- copy the appliction-config folder to c:\Users\{username}\ on Windows or /home/{username}/ on *nix. Then open a git bash terminal in application-config and run:
- git init
- git add .
- git commit -m "First commit"
- start the config server
- start the discovery server
- start all the other servers in any order (gateway, svc-book, svc-rating, zipkin)
@@ -0,0 +1,20 @@
spring.application.name=book-service
server.port=8083
resource.returnString=hello cloud
resource.user.returnString=hello cloud user
resource.admin.returnString=hello cloud admin
eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
management.security.sessions=never
logging.level.org.springframework.web.=debug
logging.level.org.springframework.security=debug
spring.redis.host=localhost
spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*)
@@ -0,0 +1,11 @@
spring.application.name=discovery
server.port=8082
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
spring.redis.host=localhost
spring.redis.port=6379
@@ -0,0 +1,30 @@
spring.application.name=gateway
server.port=8080
eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
management.security.sessions=always
zuul.routes.book-service.path=/book-service/**
zuul.routes.book-service.sensitive-headers=Set-Cookie,Authorization
hystrix.command.book-service.execution.isolation.thread.timeoutInMilliseconds=600000
zuul.routes.rating-service.path=/rating-service/**
zuul.routes.rating-service.sensitive-headers=Set-Cookie,Authorization
hystrix.command.rating-service.execution.isolation.thread.timeoutInMilliseconds=600000
zuul.routes.discovery.path=/discovery/**
zuul.routes.discovery.sensitive-headers=Set-Cookie,Authorization
zuul.routes.discovery.url=http://localhost:8082
hystrix.command.discovery.execution.isolation.thread.timeoutInMilliseconds=600000
logging.level.org.springframework.web.=debug
logging.level.org.springframework.security=debug
logging.level.org.springframework.cloud.netflix.zuul=debug
spring.redis.host=localhost
spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*|.+favicon.*)
@@ -0,0 +1,20 @@
spring.application.name=rating-service
server.port=8084
resource.returnString=hello cloud
resource.user.returnString=hello cloud user
resource.admin.returnString=hello cloud admin
eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
management.security.sessions=never
logging.level.org.springframework.web.=debug
logging.level.org.springframework.security=debug
spring.redis.host=localhost
spring.redis.port=6379
spring.sleuth.sampler.percentage=1.0
spring.sleuth.web.skipPattern=(^cleanup.*)
@@ -0,0 +1,7 @@
spring.application.name=zipkin
server.port=9411
eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
logging.level.org.springframework.web=debug
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>config</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>config</name>
<parent>
<artifactId>parent-boot-1</artifactId>
<groupId>com.baeldung</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<spring-cloud-dependencies.version>Brixton.SR7</spring-cloud-dependencies.version>
</properties>
</project>
@@ -0,0 +1,15 @@
package com.baeldung.spring.cloud.bootstrap.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
@@ -0,0 +1,23 @@
package com.baeldung.spring.cloud.bootstrap.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("configUser").password("configPassword").roles("SYSTEM");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("SYSTEM").and().httpBasic().and().csrf().disable();
}
}
@@ -0,0 +1,12 @@
server.port=8081
spring.application.name=config
spring.cloud.config.server.git.uri=file:///${user.home}/application-config
eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/
security.user.name=configUser
security.user.password=configPassword
security.user.role=SYSTEM
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -0,0 +1,17 @@
package org.baeldung;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.spring.cloud.bootstrap.config.ConfigApplication;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConfigApplication.class)
public class SpringContextIntegrationTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}
@@ -0,0 +1,17 @@
package org.baeldung;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.spring.cloud.bootstrap.config.ConfigApplication;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConfigApplication.class)
public class SpringContextTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>discovery</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>discovery</name>
<parent>
<artifactId>parent-boot-1</artifactId>
<groupId>com.baeldung</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<spring-cloud-dependencies.version>Edgware.SR5</spring-cloud-dependencies.version>
</properties>
</project>
@@ -0,0 +1,13 @@
package com.baeldung.spring.cloud.bootstrap.discovery;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryApplication.class, args);
}
}
@@ -0,0 +1,44 @@
package com.baeldung.spring.cloud.bootstrap.discovery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("discUser").password("discPassword").roles("SYSTEM");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and().requestMatchers().antMatchers("/eureka/**").and().authorizeRequests().antMatchers("/eureka/**").hasRole("SYSTEM").anyRequest().denyAll().and().httpBasic().and().csrf()
.disable();
}
@Configuration
// no order tag means this is the last security filter to be evaluated
public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER).and().httpBasic().disable().authorizeRequests().antMatchers(HttpMethod.GET, "/").hasRole("ADMIN").antMatchers("/info", "/health").authenticated().anyRequest().denyAll()
.and().csrf().disable();
}
}
}
@@ -0,0 +1,8 @@
package com.baeldung.spring.cloud.bootstrap.discovery;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
}
@@ -0,0 +1,4 @@
spring.cloud.config.name=discovery
spring.cloud.config.uri=http://localhost:8081
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -0,0 +1,23 @@
package org.baeldung;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.spring.cloud.bootstrap.discovery.DiscoveryApplication;
/**
*
* This Live Test requires:
* * A Redis instance running in port 6379 (e.g. using `docker run --name some-redis -p 6379:6379 -d redis`)
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DiscoveryApplication.class)
public class SpringContextLiveTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>gateway</name>
<parent>
<artifactId>parent-boot-1</artifactId>
<groupId>com.baeldung</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../../parent-boot-1</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</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>Dalston.RELEASE</spring-cloud-dependencies.version>
</properties>
</project>
@@ -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": {}
}
}
@@ -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
@@ -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
@@ -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).
@@ -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!');
});
});
@@ -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();
}
}
@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types":[
"jasmine",
"node"
]
}
}
@@ -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
});
};
@@ -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"
}
}
@@ -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 } }));
}
};
@@ -0,0 +1,3 @@
.custom-close {
float:right;
}
@@ -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>
@@ -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;
}
}
@@ -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 { }
@@ -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;
}
}
@@ -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>
@@ -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);
}
}
@@ -0,0 +1,3 @@
.custom-close {
float:right;
}
@@ -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>
@@ -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);
}
}
@@ -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();
}
}
@@ -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});
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -0,0 +1,11 @@
div.progress {
margin-top: 5px;
}
.rating:hover {
border: solid blue;
}
.selected {
border: solid blue;
}
@@ -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>
@@ -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);
});
}
}
@@ -0,0 +1,3 @@
export const environment = {
production: true
};
@@ -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

@@ -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>
@@ -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);
@@ -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`.
@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */
@@ -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();
@@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"baseUrl": "",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
@@ -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"
]
}
@@ -0,0 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}
@@ -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"
]
}
}
@@ -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
}
}
@@ -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;
}
}
@@ -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"));
}
}
@@ -0,0 +1,81 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import com.baeldung.spring.cloud.bootstrap.gateway.client.book.BooksClient;
import com.baeldung.spring.cloud.bootstrap.gateway.client.rating.RatingsClient;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClientSpecification;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.sleuth.metric.SpanMetricReporter;
import org.springframework.cloud.sleuth.zipkin.HttpZipkinSpanReporter;
import org.springframework.cloud.sleuth.zipkin.ZipkinProperties;
import org.springframework.cloud.sleuth.zipkin.ZipkinSpanReporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.client.RestTemplate;
import zipkin.Span;
import java.util.ArrayList;
import java.util.List;
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableFeignClients
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Autowired
private EurekaClient eurekaClient;
@Autowired
private SpanMetricReporter spanMetricReporter;
@Autowired
private ZipkinProperties zipkinProperties;
@Value("${spring.sleuth.web.skipPattern}")
private String skipPattern;
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
public ZipkinSpanReporter makeZipkinSpanReporter() {
return new ZipkinSpanReporter() {
private HttpZipkinSpanReporter delegate;
private String baseUrl;
@Override
public void report(Span span) {
InstanceInfo instance = eurekaClient.getNextServerFromEureka("zipkin", false);
if (!(baseUrl != null && instance.getHomePageUrl().equals(baseUrl))) {
baseUrl = instance.getHomePageUrl();
delegate = new HttpZipkinSpanReporter(new RestTemplate(), baseUrl, zipkinProperties.getFlushInterval(), spanMetricReporter);
if (!span.name.matches(skipPattern)) delegate.report(span);
}
if (!span.name.matches(skipPattern)) delegate.report(span);
}
};
}
}
@@ -0,0 +1,37 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("admin").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.defaultSuccessUrl("/home/index.html", true)
.and()
.authorizeRequests()
.antMatchers("/book-service/**", "/rating-service/**", "/login*", "/").permitAll()
.antMatchers("/eureka/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.logout()
.and()
.csrf().disable();
}
}
@@ -0,0 +1,11 @@
package com.baeldung.spring.cloud.bootstrap.gateway;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;
@Configuration
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
}
@@ -0,0 +1,46 @@
package com.baeldung.spring.cloud.bootstrap.gateway.client.book;
import com.baeldung.spring.cloud.bootstrap.gateway.client.rating.Rating;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Book {
private Long id;
private String author;
private String title;
private List<Rating> ratings;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
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 List<Rating> getRatings() {
return ratings;
}
public void setRatings(List<Rating> ratings) {
this.ratings = ratings;
}
}
@@ -0,0 +1,14 @@
package com.baeldung.spring.cloud.bootstrap.gateway.client.book;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value = "book-service")
public interface BooksClient {
@RequestMapping(value = "/books/{bookId}", method = {RequestMethod.GET})
Book getBookById(@PathVariable("bookId") Long bookId);
}
@@ -0,0 +1,34 @@
package com.baeldung.spring.cloud.bootstrap.gateway.client.rating;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Rating {
private Long id;
private Long bookId;
private int 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;
}
}
@@ -0,0 +1,17 @@
package com.baeldung.spring.cloud.bootstrap.gateway.client.rating;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(value = "rating-service")
public interface RatingsClient {
@RequestMapping(value = "/ratings", method = {RequestMethod.GET})
List<Rating> getRatingsByBookId(@RequestParam("bookId") Long bookId, @RequestHeader("Cookie") String session);
}
@@ -0,0 +1,32 @@
package com.baeldung.spring.cloud.bootstrap.gateway.controller;
import com.baeldung.spring.cloud.bootstrap.gateway.client.book.Book;
import com.baeldung.spring.cloud.bootstrap.gateway.client.book.BooksClient;
import com.baeldung.spring.cloud.bootstrap.gateway.client.rating.Rating;
import com.baeldung.spring.cloud.bootstrap.gateway.client.rating.RatingsClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/combined")
public class CombinedController {
private final BooksClient booksClient;
private final RatingsClient ratingsClient;
@Autowired
public CombinedController(BooksClient booksClient, RatingsClient ratingsClient) {
this.booksClient = booksClient;
this.ratingsClient = ratingsClient;
}
@GetMapping
public Book getCombinedResponse(@RequestParam Long bookId, @CookieValue("SESSION") String session){
Book book = booksClient.getBookById(bookId);
List<Rating> ratings = ratingsClient.getRatingsByBookId(bookId, "SESSION="+session);
book.setRatings(ratings);
return book;
}
}
@@ -0,0 +1,47 @@
package com.baeldung.spring.cloud.bootstrap.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
@Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private SessionRepository repository;
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
Session session = repository.getSession(httpSession.getId());
context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());
log.info("ZuulPreFilter session proxy: {}", session.getId());
return null;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
}
@@ -0,0 +1,7 @@
spring.cloud.config.name=gateway
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 83 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

@@ -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>
@@ -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
@@ -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

Some files were not shown because too many files have changed in this diff Show More