employeeList = employeeServiceProxy.getAllEmployees();
+ assertEquals(employeeList.size(), employeeCount);
+ }
+
+ @Test
+ public void givenEmployees_whenGetAvailableEmployee_thenCorrectEmployeeReturned() throws EmployeeNotFound {
+ Employee employee = employeeServiceProxy.getEmployee(2);
+ assertEquals(employee.getFirstName(), "Jack");
+ }
+
+ @Test(expected = EmployeeNotFound.class)
+ public void givenEmployees_whenGetNonAvailableEmployee_thenEmployeeNotFoundException() throws EmployeeNotFound {
+ employeeServiceProxy.getEmployee(20);
+ }
+
+ @Test
+ public void givenEmployees_whenAddNewEmployee_thenEmployeeCountIncreased() throws EmployeeAlreadyExists {
+ int employeeCount = employeeServiceProxy.countEmployees();
+ employeeServiceProxy.addEmployee(4, "Anna");
+ assertEquals(employeeServiceProxy.countEmployees(), employeeCount + 1);
+ }
+
+ @Test(expected = EmployeeAlreadyExists.class)
+ public void givenEmployees_whenAddAlreadyExistingEmployee_thenEmployeeAlreadyExistsException() throws EmployeeAlreadyExists {
+ employeeServiceProxy.addEmployee(1, "Anna");
+ }
+
+ @Test
+ public void givenEmployees_whenUpdateExistingEmployee_thenUpdatedEmployeeReturned() throws EmployeeNotFound {
+ Employee updated = employeeServiceProxy.updateEmployee(1, "Joan");
+ assertEquals(updated.getFirstName(), "Joan");
+ }
+
+ @Test(expected = EmployeeNotFound.class)
+ public void givenEmployees_whenUpdateNonExistingEmployee_thenEmployeeNotFoundException() throws EmployeeNotFound {
+ employeeServiceProxy.updateEmployee(20, "Joan");
+ }
+
+ @Test
+ public void givenEmployees_whenDeleteExistingEmployee_thenSuccessReturned() throws EmployeeNotFound {
+ boolean deleteEmployee = employeeServiceProxy.deleteEmployee(3);
+ assertEquals(deleteEmployee, true);
+ }
+
+ @Test(expected = EmployeeNotFound.class)
+ public void givenEmployee_whenDeleteNonExistingEmployee_thenEmployeeNotFoundException() throws EmployeeNotFound {
+ employeeServiceProxy.deleteEmployee(20);
+ }
+
+}
+*/
\ No newline at end of file
diff --git a/jee7/src/test/java/com/baeldung/timer/ScheduleTimerBeanIntegrationTest.java b/jee7/src/test/java/com/baeldung/timer/ScheduleTimerBeanIntegrationTest.java
index c3c5ad44de8b..580edade77d3 100644
--- a/jee7/src/test/java/com/baeldung/timer/ScheduleTimerBeanIntegrationTest.java
+++ b/jee7/src/test/java/com/baeldung/timer/ScheduleTimerBeanIntegrationTest.java
@@ -19,25 +19,27 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
-
@RunWith(Arquillian.class)
public class ScheduleTimerBeanIntegrationTest {
- final static long TIMEOUT = 5000l;
- final static long TOLERANCE = 1000l;
+ private final static long TIMEOUT = 5000l;
+ private final static long TOLERANCE = 1000l;
- @Inject
- TimerEventListener timerEventListener;
+ @Inject TimerEventListener timerEventListener;
@Deployment
public static WebArchive deploy() {
- File[] jars = Maven.resolver().loadPomFromFile("pom.xml")
- .resolve("com.jayway.awaitility:awaitility")
- .withTransitivity().asFile();
+ File[] jars = Maven
+ .resolver()
+ .loadPomFromFile("pom.xml")
+ .resolve("com.jayway.awaitility:awaitility")
+ .withTransitivity()
+ .asFile();
- return ShrinkWrap.create(WebArchive.class)
- .addAsLibraries(jars)
- .addClasses(WithinWindowMatcher.class, TimerEvent.class, TimerEventListener.class, ScheduleTimerBean.class);
+ return ShrinkWrap
+ .create(WebArchive.class)
+ .addAsLibraries(jars)
+ .addClasses(WithinWindowMatcher.class, TimerEvent.class, TimerEventListener.class, ScheduleTimerBean.class);
}
@Test
@@ -46,9 +48,15 @@ public void should_receive_three_pings() {
Awaitility.setDefaultTimeout(30, TimeUnit.SECONDS);
await().untilCall(to(timerEventListener.getEvents()).size(), equalTo(3));
- TimerEvent firstEvent = timerEventListener.getEvents().get(0);
- TimerEvent secondEvent = timerEventListener.getEvents().get(1);
- TimerEvent thirdEvent = timerEventListener.getEvents().get(2);
+ TimerEvent firstEvent = timerEventListener
+ .getEvents()
+ .get(0);
+ TimerEvent secondEvent = timerEventListener
+ .getEvents()
+ .get(1);
+ TimerEvent thirdEvent = timerEventListener
+ .getEvents()
+ .get(2);
long delay = secondEvent.getTime() - firstEvent.getTime();
assertThat(delay, Matchers.is(WithinWindowMatcher.withinWindow(TIMEOUT, TOLERANCE)));
diff --git a/jhipster/.editorconfig b/jhipster/.editorconfig
new file mode 100644
index 000000000000..a03599dd0422
--- /dev/null
+++ b/jhipster/.editorconfig
@@ -0,0 +1,24 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+[*]
+
+# Change these settings to your own preference
+indent_style = space
+indent_size = 4
+
+# We recommend you to keep these unchanged
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[{package,bower}.json]
+indent_style = space
+indent_size = 2
diff --git a/jhipster/.gitattributes b/jhipster/.gitattributes
new file mode 100644
index 000000000000..2a13efa88f5a
--- /dev/null
+++ b/jhipster/.gitattributes
@@ -0,0 +1,22 @@
+# All text files should have the "lf" (Unix) line endings
+* text eol=lf
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.java text
+*.js text
+*.css text
+*.html text
+
+# Denote all files that are truly binary and should not be modified.
+*.png binary
+*.jpg binary
+*.jar binary
+*.pdf binary
+*.eot binary
+*.ttf binary
+*.gzip binary
+*.gz binary
+*.ai binary
+*.eps binary
+*.swf binary
diff --git a/jhipster/.gitignore b/jhipster/.gitignore
new file mode 100644
index 000000000000..c9f735a4965f
--- /dev/null
+++ b/jhipster/.gitignore
@@ -0,0 +1,143 @@
+######################
+# Project Specific
+######################
+/src/main/webapp/content/css/main.css
+/target/www/**
+/src/test/javascript/coverage/
+/src/test/javascript/PhantomJS*/
+
+######################
+# Node
+######################
+/node/
+node_tmp/
+node_modules/
+npm-debug.log.*
+
+######################
+# SASS
+######################
+.sass-cache/
+
+######################
+# Eclipse
+######################
+*.pydevproject
+.project
+.metadata
+tmp/
+tmp/**/*
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.classpath
+.settings/
+.loadpath
+.factorypath
+/src/main/resources/rebel.xml
+
+# External tool builders
+.externalToolBuilders/**
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# PDT-specific
+.buildpath
+
+######################
+# Intellij
+######################
+.idea/
+*.iml
+*.iws
+*.ipr
+*.ids
+*.orig
+
+######################
+# Visual Studio Code
+######################
+.vscode/
+
+######################
+# Maven
+######################
+/log/
+/target/
+
+######################
+# Gradle
+######################
+.gradle/
+/build/
+
+######################
+# Package Files
+######################
+*.jar
+*.war
+*.ear
+*.db
+
+######################
+# Windows
+######################
+# Windows image file caches
+Thumbs.db
+
+# Folder config file
+Desktop.ini
+
+######################
+# Mac OSX
+######################
+.DS_Store
+.svn
+
+# Thumbnails
+._*
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+######################
+# Directories
+######################
+/bin/
+/deploy/
+
+######################
+# Logs
+######################
+*.log
+
+######################
+# Others
+######################
+*.class
+*.*~
+*~
+.merge_file*
+
+######################
+# Gradle Wrapper
+######################
+!gradle/wrapper/gradle-wrapper.jar
+
+######################
+# Maven Wrapper
+######################
+!.mvn/wrapper/maven-wrapper.jar
+
+######################
+# ESLint
+######################
+.eslintcache
+/.apt_generated/
diff --git a/jhipster/.gitlab-ci.yml b/jhipster/.gitlab-ci.yml
new file mode 100644
index 000000000000..1cf574251a54
--- /dev/null
+++ b/jhipster/.gitlab-ci.yml
@@ -0,0 +1,56 @@
+
+cache:
+ key: "$CI_BUILD_REF_NAME"
+ paths:
+ - node_modules
+ - .maven
+stages:
+ - build
+ - test
+ - package
+
+before_script:
+ - export MAVEN_USER_HOME=`pwd`/.maven
+ - chmod +x mvnw
+ - ./mvnw com.github.eirslett:frontend-maven-plugin:install-node-and-npm -DnodeVersion=v6.10.0 -DnpmVersion=4.3.0
+ - ./mvnw com.github.eirslett:frontend-maven-plugin:npm
+
+maven-build:
+ stage: build
+ script: ./mvnw compile -Dmaven.repo.local=$MAVEN_USER_HOME
+
+maven-test:
+ stage: test
+ script:
+ - ./mvnw test -Dmaven.repo.local=$MAVEN_USER_HOME
+ artifacts:
+ paths:
+ - target/surefire-reports/*
+maven-front-test:
+ stage: test
+ script:
+ - ./mvnw com.github.eirslett:frontend-maven-plugin:npm -Dfrontend.yarn.arguments=test
+ artifacts:
+ paths:
+ - target/test-results/karma/*
+gatling-test:
+ stage: test
+ allow_failure: true
+ script:
+ - ./mvnw gatling:execute -Dmaven.repo.local=$MAVEN_USER_HOME
+ before_script:
+ - export MAVEN_USER_HOME=`pwd`/.maven
+ - chmod +x mvnw
+ - ./mvnw com.github.eirslett:frontend-maven-plugin:install-node-and-npm -DnodeVersion=v6.10.0 -DnpmVersion=4.3.0
+ - ./mvnw com.github.eirslett:frontend-maven-plugin:npm
+ - ./mvnw &
+ artifacts:
+ paths:
+ - target/gatling/*
+maven-package:
+ stage: package
+ script:
+ - ./mvnw package -Pprod -DskipTests -Dmaven.repo.local=$MAVEN_USER_HOME
+ artifacts:
+ paths:
+ - target/*.war
diff --git a/jhipster/.jhipster/Comment.json b/jhipster/.jhipster/Comment.json
new file mode 100644
index 000000000000..c6022daa60a7
--- /dev/null
+++ b/jhipster/.jhipster/Comment.json
@@ -0,0 +1,39 @@
+{
+ "fluentMethods": true,
+ "relationships": [
+ {
+ "relationshipName": "post",
+ "otherEntityName": "post",
+ "relationshipType": "many-to-one",
+ "relationshipValidateRules": [
+ "required"
+ ],
+ "otherEntityField": "title"
+ }
+ ],
+ "fields": [
+ {
+ "fieldName": "text",
+ "fieldType": "String",
+ "fieldValidateRules": [
+ "required",
+ "minlength",
+ "maxlength"
+ ],
+ "fieldValidateRulesMinlength": "10",
+ "fieldValidateRulesMaxlength": "100"
+ },
+ {
+ "fieldName": "creationDate",
+ "fieldType": "LocalDate",
+ "fieldValidateRules": [
+ "required"
+ ]
+ }
+ ],
+ "changelogDate": "20170316224021",
+ "dto": "no",
+ "service": "no",
+ "entityTableName": "comment",
+ "pagination": "infinite-scroll"
+}
diff --git a/jhipster/.jhipster/Post.json b/jhipster/.jhipster/Post.json
new file mode 100644
index 000000000000..595cf435980d
--- /dev/null
+++ b/jhipster/.jhipster/Post.json
@@ -0,0 +1,52 @@
+{
+ "fluentMethods": true,
+ "relationships": [
+ {
+ "relationshipName": "creator",
+ "otherEntityName": "user",
+ "relationshipType": "many-to-one",
+ "relationshipValidateRules": [
+ "required"
+ ],
+ "otherEntityField": "login",
+ "ownerSide": true,
+ "otherEntityRelationshipName": "post"
+ }
+ ],
+ "fields": [
+ {
+ "fieldName": "title",
+ "fieldType": "String",
+ "fieldValidateRules": [
+ "required",
+ "minlength",
+ "maxlength"
+ ],
+ "fieldValidateRulesMinlength": "10",
+ "fieldValidateRulesMaxlength": "100"
+ },
+ {
+ "fieldName": "content",
+ "fieldType": "String",
+ "fieldValidateRules": [
+ "required",
+ "minlength",
+ "maxlength"
+ ],
+ "fieldValidateRulesMinlength": "10",
+ "fieldValidateRulesMaxlength": "1000"
+ },
+ {
+ "fieldName": "creationDate",
+ "fieldType": "LocalDate",
+ "fieldValidateRules": [
+ "required"
+ ]
+ }
+ ],
+ "changelogDate": "20170316223211",
+ "dto": "no",
+ "service": "no",
+ "entityTableName": "post",
+ "pagination": "infinite-scroll"
+}
diff --git a/jhipster/.mvn/wrapper/maven-wrapper.jar b/jhipster/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000000..5fd4d5023f14
Binary files /dev/null and b/jhipster/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/jhipster/.mvn/wrapper/maven-wrapper.properties b/jhipster/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000000..c954cec91ccb
--- /dev/null
+++ b/jhipster/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip
diff --git a/jhipster/.travis.yml b/jhipster/.travis.yml
new file mode 100644
index 000000000000..c34d1a7da680
--- /dev/null
+++ b/jhipster/.travis.yml
@@ -0,0 +1,41 @@
+os:
+ - linux
+services:
+ - docker
+language: node_js
+node_js:
+ - "6.10.0"
+jdk:
+ - oraclejdk8
+sudo: false
+cache:
+ directories:
+ - node
+ - node_modules
+ - $HOME/.m2
+env:
+ global:
+ - NODE_VERSION=6.10.0
+ - SPRING_OUTPUT_ANSI_ENABLED=ALWAYS
+ - SPRING_JPA_SHOW_SQL=false
+before_install:
+ - jdk_switcher use oraclejdk8
+ - java -version
+ - sudo /etc/init.d/mysql stop
+ - sudo /etc/init.d/postgresql stop
+ - nvm install $NODE_VERSION
+ - npm install -g npm
+ - node -v
+ - npm -v
+install:
+ - npm install
+script:
+ - chmod +x mvnw
+ - ./mvnw clean test
+ - npm test
+ - ./mvnw package -Pprod -DskipTests
+notifications:
+ webhooks:
+ on_success: change # options: [always|never|change] default: always
+ on_failure: always # options: [always|never|change] default: always
+ on_start: false # default: false
diff --git a/jhipster/.yo-rc.json b/jhipster/.yo-rc.json
new file mode 100644
index 000000000000..155e33b1c5f5
--- /dev/null
+++ b/jhipster/.yo-rc.json
@@ -0,0 +1,36 @@
+{
+ "generator-jhipster": {
+ "jhipsterVersion": "4.0.8",
+ "baseName": "baeldung",
+ "packageName": "com.baeldung",
+ "packageFolder": "com/baeldung",
+ "serverPort": "8080",
+ "authenticationType": "jwt",
+ "hibernateCache": "ehcache",
+ "clusteredHttpSession": false,
+ "websocket": false,
+ "databaseType": "sql",
+ "devDatabaseType": "h2Disk",
+ "prodDatabaseType": "mysql",
+ "searchEngine": false,
+ "messageBroker": false,
+ "serviceDiscoveryType": false,
+ "buildTool": "maven",
+ "enableSocialSignIn": false,
+ "jwtSecretKey": "e1d4b69d3f953e3fa622121e882e6f459ca20ca4",
+ "clientFramework": "angular2",
+ "useSass": true,
+ "clientPackageManager": "npm",
+ "applicationType": "monolith",
+ "testFrameworks": [
+ "gatling",
+ "protractor"
+ ],
+ "jhiPrefix": "jhi",
+ "enableTranslation": true,
+ "nativeLanguage": "en",
+ "languages": [
+ "en"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/jhipster/Jenkinsfile b/jhipster/Jenkinsfile
new file mode 100644
index 000000000000..1f0873a47284
--- /dev/null
+++ b/jhipster/Jenkinsfile
@@ -0,0 +1,50 @@
+#!/usr/bin/env groovy
+
+node {
+ stage('checkout') {
+ checkout scm
+ }
+
+ stage('check java') {
+ sh "java -version"
+ }
+
+ stage('clean') {
+ sh "chmod +x mvnw"
+ sh "./mvnw clean"
+ }
+
+ stage('install tools') {
+ sh "./mvnw com.github.eirslett:frontend-maven-plugin:install-node-and-npm -DnodeVersion=v6.10.0 -DnpmVersion=4.3.0"
+ }
+
+ stage('npm install') {
+ sh "./mvnw com.github.eirslett:frontend-maven-plugin:npm"
+ }
+
+ stage('backend tests') {
+ try {
+ sh "./mvnw test"
+ } catch(err) {
+ throw err
+ } finally {
+ junit '**/target/surefire-reports/TEST-*.xml'
+ }
+ }
+
+ stage('frontend tests') {
+ try {
+ sh "./mvnw com.github.eirslett:frontend-maven-plugin:npm -Dfrontend.yarn.arguments=test"
+ } catch(err) {
+ throw err
+ } finally {
+ junit '**/target/test-results/karma/TESTS-*.xml'
+ }
+ }
+
+ stage('packaging') {
+ sh "./mvnw package -Pprod -DskipTests"
+ archiveArtifacts artifacts: '**/target/*.war', fingerprint: true
+ }
+
+}
diff --git a/jhipster/README.md b/jhipster/README.md
new file mode 100644
index 000000000000..d321e9e81ec4
--- /dev/null
+++ b/jhipster/README.md
@@ -0,0 +1,158 @@
+### Relevant articles
+
+- [Intro to JHipster](http://www.baeldung.com/jhipster)
+
+
+# baeldung
+This application was generated using JHipster 4.0.8, you can find documentation and help at [https://jhipster.github.io/documentation-archive/v4.0.8](https://jhipster.github.io/documentation-archive/v4.0.8).
+
+## Development
+
+Before you can build this project, you must install and configure the following dependencies on your machine:
+
+1. [Node.js][]: We use Node to run a development web server and build the project.
+ Depending on your system, you can install Node either from source or as a pre-packaged bundle.
+
+After installing Node, you should be able to run the following command to install development tools.
+You will only need to run this command when dependencies change in [package.json](package.json).
+
+ npm install
+
+We use npm scripts and [Webpack][] as our build system.
+
+
+Run the following commands in two separate terminals to create a blissful development experience where your browser
+auto-refreshes when files change on your hard drive.
+
+ ./mvnw
+ npm start
+
+[Npm][] is also used to manage CSS and JavaScript dependencies used in this application. You can upgrade dependencies by
+specifying a newer version in [package.json](package.json). You can also run `npm update` and `npm install` to manage dependencies.
+Add the `help` flag on any command to see how you can use it. For example, `npm help update`.
+
+The `npm run` command will list all of the scripts available to run for this project.
+
+### Managing dependencies
+
+For example, to add [Leaflet][] library as a runtime dependency of your application, you would run following command:
+
+ npm install --save --save-exact leaflet
+
+To benefit from TypeScript type definitions from [DefinitelyTyped][] repository in development, you would run following command:
+
+ npm install --save-dev --save-exact @types/leaflet
+
+Then you would import the JS and CSS files specified in library's installation instructions so that [Webpack][] knows about them:
+
+Edit [src/main/webapp/app/vendor.ts](src/main/webapp/app/vendor.ts) file:
+~~~
+import 'leaflet/dist/leaflet.js';
+~~~
+
+Edit [src/main/webapp/content/css/vendor.css](src/main/webapp/content/css/vendor.css) file:
+~~~
+@import '~leaflet/dist/leaflet.css';
+~~~
+
+Note: there are still few other things remaining to do for Leaflet that we won't detail here.
+
+For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][].
+
+### Using angular-cli
+
+You can also use [Angular CLI][] to generate some custom client code.
+
+For example, the following command:
+
+ ng generate component my-component
+
+will generate few files:
+
+ create src/main/webapp/app/my-component/my-component.component.html
+ create src/main/webapp/app/my-component/my-component.component.ts
+ update src/main/webapp/app/app.module.ts
+
+## Building for production
+
+To optimize the baeldung application for production, run:
+
+ ./mvnw -Pprod clean package
+
+This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files.
+To ensure everything worked, run:
+
+ java -jar target/*.war
+
+Then navigate to [http://localhost:8080](http://localhost:8080) in your browser.
+
+Refer to [Using JHipster in production][] for more details.
+
+## Testing
+
+To launch your application's tests, run:
+
+ ./mvnw clean test
+
+### Client tests
+
+Unit tests are run by [Karma][] and written with [Jasmine][]. They're located in [src/test/javascript/](src/test/javascript/) and can be run with:
+
+ npm test
+
+UI end-to-end tests are powered by [Protractor][], which is built on top of WebDriverJS. They're located in [src/test/javascript/e2e](src/test/javascript/e2e)
+and can be run by starting Spring Boot in one terminal (`./mvnw spring-boot:run`) and running the tests (`gulp itest`) in a second one.
+### Other tests
+
+Performance tests are run by [Gatling][] and written in Scala. They're located in [src/test/gatling](src/test/gatling) and can be run with:
+
+ ./mvnw gatling:execute
+
+For more information, refer to the [Running tests page][].
+
+## Using Docker to simplify development (optional)
+
+You can use Docker to improve your JHipster development experience. A number of docker-compose configuration are available in the [src/main/docker](src/main/docker) folder to launch required third party services.
+For example, to start a mysql database in a docker container, run:
+
+ docker-compose -f src/main/docker/mysql.yml up -d
+
+To stop it and remove the container, run:
+
+ docker-compose -f src/main/docker/mysql.yml down
+
+You can also fully dockerize your application and all the services that it depends on.
+To achieve this, first build a docker image of your app by running:
+
+ ./mvnw package -Pprod docker:build
+
+Then run:
+
+ docker-compose -f src/main/docker/app.yml up -d
+
+For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the docker-compose sub-generator (`yo jhipster:docker-compose`), which is able to generate docker configurations for one or several JHipster applications.
+
+## Continuous Integration (optional)
+
+To configure CI for your project, run the ci-cd sub-generator (`yo jhipster:ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information.
+
+[JHipster Homepage and latest documentation]: https://jhipster.github.io
+[JHipster 4.0.8 archive]: https://jhipster.github.io/documentation-archive/v4.0.8
+
+[Using JHipster in development]: https://jhipster.github.io/documentation-archive/v4.0.8/development/
+[Using Docker and Docker-Compose]: https://jhipster.github.io/documentation-archive/v4.0.8/docker-compose
+[Using JHipster in production]: https://jhipster.github.io/documentation-archive/v4.0.8/production/
+[Running tests page]: https://jhipster.github.io/documentation-archive/v4.0.8/running-tests/
+[Setting up Continuous Integration]: https://jhipster.github.io/documentation-archive/v4.0.8/setting-up-ci/
+
+[Gatling]: http://gatling.io/
+[Node.js]: https://nodejs.org/
+[Yarn]: https://yarnpkg.org/
+[Webpack]: https://webpack.github.io/
+[Angular CLI]: https://cli.angular.io/
+[BrowserSync]: http://www.browsersync.io/
+[Karma]: http://karma-runner.github.io/
+[Jasmine]: http://jasmine.github.io/2.0/introduction.html
+[Protractor]: https://angular.github.io/protractor/
+[Leaflet]: http://leafletjs.com/
+[DefinitelyTyped]: http://definitelytyped.org/
diff --git a/jhipster/angular-cli.json b/jhipster/angular-cli.json
new file mode 100644
index 000000000000..15558a2faea5
--- /dev/null
+++ b/jhipster/angular-cli.json
@@ -0,0 +1,55 @@
+{
+ "project": {
+ "version": "1.0.0-beta.24",
+ "name": "baeldung"
+ },
+ "apps": [
+ {
+ "root": "src/main/webapp/",
+ "outDir": "target/www/app",
+ "assets": [
+ "content",
+ "favicon.ico"
+ ],
+ "index": "index.html",
+ "main": "app/app.main.ts",
+ "test": "",
+ "tsconfig": "../../../tsconfig.json",
+ "prefix": "jhi",
+ "mobile": false,
+ "styles": [
+ "content/css/main.css"
+ ],
+ "scripts": [],
+ "environments": {}
+ }
+ ],
+ "addons": [],
+ "packages": [],
+ "e2e": {
+ "protractor": {
+ "config": "src/test/javascript/protractor.conf.js"
+ }
+ },
+ "test": {
+ "karma": {
+ "config": "src/test/javascript/karma.conf.js"
+ }
+ },
+ "defaults": {
+ "styleExt": "css",
+ "prefixInterfaces": false,
+ "inline": {
+ "style": true,
+ "template": false
+ },
+ "spec": {
+ "class": false,
+ "component": false,
+ "directive": false,
+ "module": false,
+ "pipe": false,
+ "service": false
+ }
+ }
+}
diff --git a/jhipster/circle.yml b/jhipster/circle.yml
new file mode 100644
index 000000000000..bc9371e94fc8
--- /dev/null
+++ b/jhipster/circle.yml
@@ -0,0 +1,25 @@
+machine:
+ services:
+ - docker
+ java:
+ version: oraclejdk8
+ node:
+ version: 6.10.0
+dependencies:
+ cache_directories:
+ - node
+ - node_modules
+ - ~/.m2
+ override:
+ - java -version
+ - npm install -g npm
+ - node -v
+ - npm -v
+ - java -version
+ - npm install
+test:
+ override:
+ - chmod +x mvnw
+ - ./mvnw clean test
+ - npm test
+ - ./mvnw package -Pprod -DskipTests
diff --git a/jhipster/mvnw b/jhipster/mvnw
new file mode 100755
index 000000000000..a1ba1bf554bb
--- /dev/null
+++ b/jhipster/mvnw
@@ -0,0 +1,233 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ #
+ # Look for the Apple JDKs first to preserve the existing behaviour, and then look
+ # for the new JDKs provided by Oracle.
+ #
+ if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
+ #
+ # Oracle JDKs
+ #
+ export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=`/usr/libexec/java_home`
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Migwn, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ local basedir=$(pwd)
+ local wdir=$(pwd)
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ wdir=$(cd "$wdir/.."; pwd)
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} "$@"
diff --git a/jhipster/mvnw.cmd b/jhipster/mvnw.cmd
new file mode 100644
index 000000000000..2b934e89dd1d
--- /dev/null
+++ b/jhipster/mvnw.cmd
@@ -0,0 +1,145 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+set MAVEN_CMD_LINE_ARGS=%*
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+
+set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar""
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
\ No newline at end of file
diff --git a/jhipster/package.json b/jhipster/package.json
new file mode 100644
index 000000000000..40bbd28799e0
--- /dev/null
+++ b/jhipster/package.json
@@ -0,0 +1,112 @@
+{
+ "name": "baeldung",
+ "version": "0.0.0",
+ "description": "Description for baeldung",
+ "private": true,
+ "cacheDirectories": [
+ "node_modules"
+ ],
+ "dependencies": {
+ "@angular/common": "2.4.7",
+ "@angular/compiler": "2.4.7",
+ "@angular/core": "2.4.7",
+ "@angular/forms": "2.4.7",
+ "@angular/http": "2.4.7",
+ "@angular/platform-browser": "2.4.7",
+ "@angular/platform-browser-dynamic": "2.4.7",
+ "@angular/router": "3.4.7",
+ "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.20",
+ "angular2-infinite-scroll": "0.3.0",
+ "bootstrap": "4.0.0-alpha.6",
+ "font-awesome": "4.7.0",
+ "angular2-cookie": "1.2.6",
+ "core-js": "2.4.1",
+ "jquery": "3.1.1",
+ "ng-jhipster": "0.1.9",
+ "ng2-webstorage": "1.5.0",
+ "reflect-metadata": "0.1.9",
+ "rxjs": "5.1.0",
+ "swagger-ui": "2.2.10",
+ "tether": "1.4.0",
+ "zone.js": "0.7.6"
+ },
+ "devDependencies": {
+ "@angular/cli": "1.0.0-beta.28.3",
+ "@types/jasmine": "2.5.42",
+ "@types/node": "7.0.5",
+ "@types/selenium-webdriver": "2.53.39",
+ "add-asset-html-webpack-plugin": "1.0.2",
+ "angular2-template-loader": "0.6.2",
+ "awesome-typescript-loader": "3.0.7",
+ "browser-sync": "2.18.7",
+ "browser-sync-webpack-plugin": "1.1.4",
+ "codelyzer": "2.0.0",
+ "copy-webpack-plugin": "4.0.1",
+ "css-loader": "0.26.1",
+ "del": "2.2.2",
+ "event-stream": "3.3.4",
+ "exports-loader": "0.6.3",
+ "extract-text-webpack-plugin": "2.0.0-beta.5",
+ "file-loader": "0.10.0",
+ "generator-jhipster": "4.0.8",
+ "html-webpack-plugin": "2.28.0",
+ "image-webpack-loader": "3.2.0",
+ "jasmine-core": "2.5.2",
+ "jasmine-reporters": "2.2.0",
+ "karma": "1.4.1",
+ "karma-chrome-launcher": "2.0.0",
+ "karma-coverage": "1.1.1",
+ "karma-intl-shim": "1.0.3",
+ "karma-jasmine": "1.1.0",
+ "karma-junit-reporter": "1.2.0",
+ "karma-phantomjs-launcher": "1.0.2",
+ "karma-remap-istanbul": "0.6.0",
+ "karma-sourcemap-loader": "0.3.7",
+ "karma-webpack": "2.0.2",
+ "lazypipe": "1.0.1",
+ "lodash": "4.17.4",
+ "map-stream": "0.0.6",
+ "phantomjs-prebuilt": "2.1.14",
+ "protractor": "5.1.1",
+ "protractor-jasmine2-screenshot-reporter": "0.3.3",
+ "ts-node": "2.1.0",
+ "proxy-middleware": "0.15.0",
+ "raw-loader": "0.5.1",
+ "run-sequence": "1.2.2",
+ "sourcemap-istanbul-instrumenter-loader": "0.2.0",
+ "string-replace-webpack-plugin": "0.0.5",
+ "style-loader": "0.13.1",
+ "to-string-loader": "1.1.5",
+ "tslint": "4.4.2",
+ "tslint-loader": "3.4.1",
+ "typescript": "2.1.6",
+ "webpack": "2.2.1",
+ "webpack-dev-server": "2.3.0",
+ "webpack-merge": "2.6.1",
+ "webpack-visualizer-plugin": "0.1.10",
+ "write-file-webpack-plugin": "3.4.2",
+ "xml2js": "0.4.17",
+ "sass-loader": "5.0.1",
+ "node-sass": "4.5.0",
+ "postcss-loader": "1.3.0",
+ "yargs": "6.6.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "scripts": {
+ "lint": "tslint 'src/main/webapp/app/**/*.ts' --force",
+ "lint:fix": "tslint 'src/main/webapp/app/**/*.ts' --fix --force",
+ "tsc": "tsc",
+ "tsc:w": "tsc -w",
+ "start": "npm run webpack:dev",
+ "webpack:build": "webpack --config webpack/webpack.vendor.js && webpack --config webpack/webpack.dev.js",
+ "webpack:build:dev": "webpack --config webpack/webpack.dev.js",
+ "webpack:dev": "webpack-dev-server --config webpack/webpack.dev.js --progress --inline --hot --profile --port=9060",
+ "webpack:prod": "npm test && webpack -p --config webpack/webpack.vendor.js && webpack -p --config webpack/webpack.prod.js",
+ "test": "npm run lint && karma start src/test/javascript/karma.conf.js",
+ "test:watch": "karma start --watch",
+ "e2e": "protractor src/test/javascript/protractor.conf.js",
+ "postinstall": "webdriver-manager update && npm run webpack:build"
+ }
+}
diff --git a/jhipster/pom.xml b/jhipster/pom.xml
new file mode 100644
index 000000000000..ba78ad4e2b0c
--- /dev/null
+++ b/jhipster/pom.xml
@@ -0,0 +1,1034 @@
+
+
+ 4.0.0
+
+
+ spring-boot-starter-parent
+ org.springframework.boot
+ 1.5.2.RELEASE
+
+
+
+ com.baeldung
+ jhipster-monolithic
+ 0.0.1-SNAPSHOT
+ war
+ JHipster Monolithic Application
+
+
+ ${maven.version}
+
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-hibernate5
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-hppc
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-json-org
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+ com.h2database
+ h2
+
+
+ com.jayway.jsonpath
+ json-path
+ test
+
+
+
+ com.jcraft
+ jzlib
+ ${jzlib.version}
+
+
+ com.mattbertolini
+ liquibase-slf4j
+ ${liquibase-slf4j.version}
+
+
+ com.ryantenney.metrics
+ metrics-spring
+ ${metrics-spring.version}
+
+
+ metrics-annotation
+ com.codahale.metrics
+
+
+ metrics-core
+ com.codahale.metrics
+
+
+ metrics-healthchecks
+ com.codahale.metrics
+
+
+
+
+ com.zaxxer
+ HikariCP
+
+
+ tools
+ com.sun
+
+
+
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+ io.dropwizard.metrics
+ metrics-annotation
+ ${dropwizard-metrics.version}
+
+
+ io.dropwizard.metrics
+ metrics-core
+
+
+ io.dropwizard.metrics
+ metrics-json
+ ${dropwizard-metrics.version}
+
+
+ io.dropwizard.metrics
+ metrics-jvm
+ ${dropwizard-metrics.version}
+
+
+ io.dropwizard.metrics
+ metrics-servlet
+ ${dropwizard-metrics.version}
+
+
+ io.dropwizard.metrics
+ metrics-servlets
+
+
+ io.gatling.highcharts
+ gatling-charts-highcharts
+ ${gatling.version}
+ test
+
+
+ io.github.jhipster
+ jhipster
+ ${jhipster.server.version}
+
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
+
+ io.springfox
+ springfox-bean-validators
+ ${springfox.version}
+
+
+ io.springfox
+ springfox-swagger2
+ ${springfox.version}
+
+
+ mapstruct
+ org.mapstruct
+
+
+
+
+ javax.cache
+ cache-api
+
+
+ mysql
+ mysql-connector-java
+
+
+
+ net.logstash.logback
+ logstash-logback-encoder
+ ${logstash-logback-encoder.version}
+
+
+ logback-core
+ ch.qos.logback
+
+
+ logback-classic
+ ch.qos.logback
+
+
+ logback-access
+ ch.qos.logback
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang.version}
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.awaitility
+ awaitility
+ ${awaitility.version}
+ test
+
+
+ org.ehcache
+ ehcache
+
+
+ org.hibernate
+ hibernate-envers
+
+
+ org.hibernate
+ hibernate-jcache
+ ${hibernate.version}
+
+
+ org.hibernate
+ hibernate-validator
+
+
+ org.liquibase
+ liquibase-core
+
+
+ jetty-servlet
+ org.eclipse.jetty
+
+
+
+
+ org.mapstruct
+ mapstruct-jdk8
+ ${mapstruct.version}
+
+
+ org.springframework
+ spring-context-support
+
+
+ org.springframework.boot
+ spring-boot-actuator
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.boot
+ spring-boot-loader-tools
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+ org.springframework.boot
+ spring-boot-starter-cloud-connectors
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ spring-boot-starter-tomcat
+ org.springframework.boot
+
+
+
+
+ org.springframework.boot
+ spring-boot-test
+ test
+
+
+
+ org.springframework.security
+ spring-security-data
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+
+ spring-boot:run
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+ prepare-agent
+
+
+
+
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ ${frontend-maven-plugin.version}
+
+ install-node-and-npm
+ npm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.github.ekryd.sortpom
+ sortpom-maven-plugin
+ ${sortpom-maven-plugin.version}
+
+
+ verify
+
+ sort
+
+
+
+
+ true
+ 4
+ groupId,artifactId
+ groupId,artifactId
+ true
+ false
+
+
+
+ com.spotify
+ docker-maven-plugin
+ ${docker-maven-plugin.version}
+
+ baeldung
+ src/main/docker
+
+
+ /
+ ${project.build.directory}
+ ${project.build.finalName}.war
+
+
+
+
+
+ io.gatling
+ gatling-maven-plugin
+ ${gatling-maven-plugin.version}
+
+ src/test/gatling/conf
+ src/test/gatling/data
+ target/gatling/results
+ src/test/gatling/bodies
+ src/test/gatling/simulations
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ 1.8
+ 1.8
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-eclipse-plugin
+
+ true
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ ${maven-enforcer-plugin.version}
+
+
+ enforce-versions
+
+ enforce
+
+
+
+
+
+
+ You are running an older version of
+ Maven. JHipster requires at least Maven
+ ${maven.version}
+ [${maven.version},)
+
+
+ You are running an older version of
+ Java. JHipster requires at least JDK
+ ${java.version}
+ [${java.version}.0,)
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${maven-resources-plugin.version}
+
+
+ default-resources
+ validate
+
+ copy-resources
+
+
+ target/classes
+ false
+
+ #
+
+
+
+ src/main/resources/
+ true
+
+ **/*.xml
+ **/*.yml
+
+
+
+ src/main/resources/
+ false
+
+ **/*.xml
+ **/*.yml
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ alphabetical
+
+ **/*IntTest.java
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+
+ pre-unit-tests
+
+ prepare-agent
+
+
+
+ ${project.testresult.directory}/coverage/jacoco/jacoco.exec
+
+
+
+
+ post-unit-test
+ test
+
+ report
+
+
+ ${project.testresult.directory}/coverage/jacoco/jacoco.exec
+ ${project.testresult.directory}/coverage/jacoco
+
+
+
+
+
+ org.liquibase
+ liquibase-maven-plugin
+ ${liquibase.version}
+
+
+ javax.validation
+ validation-api
+ ${validation-api.version}
+
+
+ org.javassist
+ javassist
+ ${javassist.version}
+
+
+ org.liquibase.ext
+ liquibase-hibernate5
+ ${liquibase-hibernate5.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ ${project.parent.version}
+
+
+
+ src/main/resources/config/liquibase/master.xml
+ src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml
+ org.h2.Driver
+ jdbc:h2:file:./target/h2db/db/baeldung
+
+ baeldung
+
+ hibernate:spring:com.baeldung.domain?dialect=org.hibernate.dialect.H2Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+ true
+ debug
+
+
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ ${sonar-maven-plugin.version}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+ true
+
+
+
+
+
+
+
+
+ no-liquibase
+
+ ,no-liquibase
+
+
+
+ swagger
+
+ ,swagger
+
+
+
+ webpack
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ ${frontend-maven-plugin.version}
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+ ${node.version}
+ ${npm.version}
+
+
+
+ webpack build dev
+ generate-resources
+
+ npm
+
+
+ run webpack:build:dev
+
+
+
+
+
+
+
+
+ dev
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+ src/main/webapp/
+
+
+
+
+
+
+ DEBUG
+
+ dev${profile.no-liquibase}
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+
+
+ prod
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ ${frontend-maven-plugin.version}
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+ ${node.version}
+ ${npm.version}
+
+
+
+ npm install
+
+ npm
+
+
+ install
+
+
+
+ npm rebuild node-sass
+
+ npm
+
+
+ rebuild node-sass
+
+
+
+ webpack build prod
+ generate-resources
+
+ npm
+
+
+ run webpack:prod
+
+
+
+
+
+ maven-clean-plugin
+
+
+
+ target/www/
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+ target/www/
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ build-info
+
+
+
+
+ true
+
+
+
+
+
+
+ INFO
+
+ prod${profile.swagger}${profile.no-liquibase}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+
+
+
+ cc
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ ${scala-maven-plugin.version}
+
+
+ compile
+ compile
+
+ add-source
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ add-source
+ testCompile
+
+
+
+
+ incremental
+ true
+ ${scala.version}
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ default-compile
+ none
+
+
+ default-testCompile
+ none
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+ src/main/webapp/
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+ true
+ true
+
+
+
+
+
+
+
+ DEBUG
+
+ dev,swagger
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+
+
+
+ graphite
+
+
+ io.dropwizard.metrics
+ metrics-graphite
+
+
+
+
+
+ prometheus
+
+
+ io.prometheus
+ simpleclient
+ ${prometheus-simpleclient.version}
+
+
+ io.prometheus
+ simpleclient_dropwizard
+ ${prometheus-simpleclient.version}
+
+
+ io.prometheus
+ simpleclient_servlet
+ ${prometheus-simpleclient.version}
+
+
+
+
+
+ IDE
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+
+
+
+
+ integration
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ integration-test
+
+ test
+
+
+
+ **/*IntTest.java
+
+
+
+
+
+
+
+
+
+
+
+ -Djava.security.egd=file:/dev/./urandom -Xmx256m
+ 3.6.2
+ 2.0.0
+ 2.5
+ 3.5
+ 0.4.13
+ 1.3
+ 2.2.1
+ 2.2.3
+ 5.2.8.Final
+ 2.6.0
+ 0.7.9
+ 1.8
+ 3.21.0-GA
+ 1.0.0
+ 1.1.0
+ 0.7.0
+ 1.1.3
+ 3.6
+ 2.0.0
+ 4.8
+ jdt_apt
+ 1.1.0.Final
+ 3.6.0
+ 1.4.1
+ 3.0.1
+ yyyyMMddHHmmss
+ ${java.version}
+ ${java.version}
+ 3.0.0
+ 3.1.3
+ v6.10.0
+ 4.3.0
+
+
+
+
+ ${project.build.directory}/test-results
+ 0.0.20
+ false
+ 3.2.2
+ 2.12.1
+ 3.2
+
+ src/main/webapp/content/**/*.*,
+ src/main/webapp/bower_components/**/*.*,
+ src/main/webapp/i18n/*.js, target/www/**/*.*
+
+ S3437,UndocumentedApi,BoldAndItalicTagsCheck
+
+
+ src/main/webapp/app/**/*.*
+ Web:BoldAndItalicTagsCheck
+
+ src/main/java/**/*
+ squid:S3437
+
+ src/main/java/**/*
+ squid:UndocumentedApi
+
+ ${project.testresult.directory}/coverage/jacoco/jacoco-it.exec
+ ${project.testresult.directory}/coverage/jacoco/jacoco.exec
+ jacoco
+
+ ${project.testresult.directory}/karma
+
+ ${project.testresult.directory}/coverage/report-lcov/lcov.info
+
+ ${project.testresult.directory}/coverage/report-lcov/lcov.info
+
+ ${project.basedir}/src/main/
+ ${project.testresult.directory}/surefire-reports
+ ${project.basedir}/src/test/
+
+ 2.5.0
+
+ 2.6.1
+ 1.4.10.Final
+ 1.1.0.Final
+
+
diff --git a/jhipster/postcss.config.js b/jhipster/postcss.config.js
new file mode 100644
index 000000000000..f549c034d589
--- /dev/null
+++ b/jhipster/postcss.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ plugins: []
+}
diff --git a/jhipster/src/main/docker/Dockerfile b/jhipster/src/main/docker/Dockerfile
new file mode 100644
index 000000000000..66991b2998d4
--- /dev/null
+++ b/jhipster/src/main/docker/Dockerfile
@@ -0,0 +1,13 @@
+FROM openjdk:8-jre-alpine
+
+ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
+ JHIPSTER_SLEEP=0
+
+# add directly the war
+ADD *.war /app.war
+
+VOLUME /tmp
+EXPOSE 8080
+CMD echo "The application will start in ${JHIPSTER_SLEEP}s..." && \
+ sleep ${JHIPSTER_SLEEP} && \
+ java -Djava.security.egd=file:/dev/./urandom -jar /app.war
diff --git a/jhipster/src/main/docker/app.yml b/jhipster/src/main/docker/app.yml
new file mode 100644
index 000000000000..78cd2c1602c6
--- /dev/null
+++ b/jhipster/src/main/docker/app.yml
@@ -0,0 +1,14 @@
+version: '2'
+services:
+ baeldung-app:
+ image: baeldung
+ environment:
+ - SPRING_PROFILES_ACTIVE=prod,swagger
+ - SPRING_DATASOURCE_URL=jdbc:mysql://baeldung-mysql:3306/baeldung?useUnicode=true&characterEncoding=utf8&useSSL=false
+ - JHIPSTER_SLEEP=10 # gives time for the database to boot before the application
+ ports:
+ - 8080:8080
+ baeldung-mysql:
+ extends:
+ file: mysql.yml
+ service: baeldung-mysql
diff --git a/jhipster/src/main/docker/mysql.yml b/jhipster/src/main/docker/mysql.yml
new file mode 100644
index 000000000000..5038d095041c
--- /dev/null
+++ b/jhipster/src/main/docker/mysql.yml
@@ -0,0 +1,13 @@
+version: '2'
+services:
+ baeldung-mysql:
+ image: mysql:5.7.13
+ # volumes:
+ # - ~/volumes/jhipster/baeldung/mysql/:/var/lib/mysql/
+ environment:
+ - MYSQL_USER=root
+ - MYSQL_ALLOW_EMPTY_PASSWORD=yes
+ - MYSQL_DATABASE=baeldung
+ ports:
+ - 3306:3306
+ command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8
diff --git a/jhipster/src/main/docker/sonar.yml b/jhipster/src/main/docker/sonar.yml
new file mode 100644
index 000000000000..718be83b4d53
--- /dev/null
+++ b/jhipster/src/main/docker/sonar.yml
@@ -0,0 +1,7 @@
+version: '2'
+services:
+ baeldung-sonar:
+ image: sonarqube:6.2-alpine
+ ports:
+ - 9000:9000
+ - 9092:9092
diff --git a/jhipster/src/main/java/com/baeldung/ApplicationWebXml.java b/jhipster/src/main/java/com/baeldung/ApplicationWebXml.java
new file mode 100644
index 000000000000..762d18420c7f
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/ApplicationWebXml.java
@@ -0,0 +1,21 @@
+package com.baeldung;
+
+import com.baeldung.config.DefaultProfileUtil;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.support.SpringBootServletInitializer;
+
+/**
+ * This is a helper Java class that provides an alternative to creating a web.xml.
+ * This will be invoked only when the application is deployed to a servlet container like Tomcat, JBoss etc.
+ */
+public class ApplicationWebXml extends SpringBootServletInitializer {
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ /**
+ * set a default to use when no profile is configured.
+ */
+ DefaultProfileUtil.addDefaultProfile(application.application());
+ return application.sources(BaeldungApp.class);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/BaeldungApp.java b/jhipster/src/main/java/com/baeldung/BaeldungApp.java
new file mode 100644
index 000000000000..13b6b2b3fa9a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/BaeldungApp.java
@@ -0,0 +1,84 @@
+package com.baeldung;
+
+import com.baeldung.config.ApplicationProperties;
+import com.baeldung.config.DefaultProfileUtil;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.actuate.autoconfigure.*;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.core.env.Environment;
+
+import javax.annotation.PostConstruct;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collection;
+
+@ComponentScan
+@EnableAutoConfiguration(exclude = {MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class})
+@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
+public class BaeldungApp {
+
+ private static final Logger log = LoggerFactory.getLogger(BaeldungApp.class);
+
+ private final Environment env;
+
+ public BaeldungApp(Environment env) {
+ this.env = env;
+ }
+
+ /**
+ * Initializes baeldung.
+ *
+ * Spring profiles can be configured with a program arguments --spring.profiles.active=your-active-profile
+ *
+ * You can find more information on how profiles work with JHipster on http://jhipster.github.io/profiles/ .
+ */
+ @PostConstruct
+ public void initApplication() {
+ Collection activeProfiles = Arrays.asList(env.getActiveProfiles());
+ if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
+ log.error("You have misconfigured your application! It should not run " +
+ "with both the 'dev' and 'prod' profiles at the same time.");
+ }
+ if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) {
+ log.error("You have misconfigured your application! It should not" +
+ "run with both the 'dev' and 'cloud' profiles at the same time.");
+ }
+ }
+
+ /**
+ * Main method, used to run the application.
+ *
+ * @param args the command line arguments
+ * @throws UnknownHostException if the local host name could not be resolved into an address
+ */
+ public static void main(String[] args) throws UnknownHostException {
+ SpringApplication app = new SpringApplication(BaeldungApp.class);
+ DefaultProfileUtil.addDefaultProfile(app);
+ Environment env = app.run(args).getEnvironment();
+ String protocol = "http";
+ if (env.getProperty("server.ssl.key-store") != null) {
+ protocol = "https";
+ }
+ log.info("\n----------------------------------------------------------\n\t" +
+ "Application '{}' is running! Access URLs:\n\t" +
+ "Local: \t\t{}://localhost:{}\n\t" +
+ "External: \t{}://{}:{}\n\t" +
+ "Profile(s): \t{}\n----------------------------------------------------------",
+ env.getProperty("spring.application.name"),
+ protocol,
+ env.getProperty("server.port"),
+ protocol,
+ InetAddress.getLocalHost().getHostAddress(),
+ env.getProperty("server.port"),
+ env.getActiveProfiles());
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/aop/logging/LoggingAspect.java b/jhipster/src/main/java/com/baeldung/aop/logging/LoggingAspect.java
new file mode 100644
index 000000000000..7fbd3528201b
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/aop/logging/LoggingAspect.java
@@ -0,0 +1,79 @@
+package com.baeldung.aop.logging;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.env.Environment;
+
+import java.util.Arrays;
+
+/**
+ * Aspect for logging execution of service and repository Spring components.
+ *
+ * By default, it only runs with the "dev" profile.
+ */
+@Aspect
+public class LoggingAspect {
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private final Environment env;
+
+ public LoggingAspect(Environment env) {
+ this.env = env;
+ }
+
+ /**
+ * Pointcut that matches all repositories, services and Web REST endpoints.
+ */
+ @Pointcut("within(com.baeldung.repository..*) || within(com.baeldung.service..*) || within(com.baeldung.web.rest..*)")
+ public void loggingPointcut() {
+ // Method is empty as this is just a Pointcut, the implementations are in the advices.
+ }
+
+ /**
+ * Advice that logs methods throwing exceptions.
+ */
+ @AfterThrowing(pointcut = "loggingPointcut()", throwing = "e")
+ public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
+ if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
+ log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL", e.getMessage(), e);
+
+ } else {
+ log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL");
+ }
+ }
+
+ /**
+ * Advice that logs when a method is entered and exited.
+ */
+ @Around("loggingPointcut()")
+ public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
+ if (log.isDebugEnabled()) {
+ log.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
+ }
+ try {
+ Object result = joinPoint.proceed();
+ if (log.isDebugEnabled()) {
+ log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
+ joinPoint.getSignature().getName(), result);
+ }
+ return result;
+ } catch (IllegalArgumentException e) {
+ log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()),
+ joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
+
+ throw e;
+ }
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/ApplicationProperties.java b/jhipster/src/main/java/com/baeldung/config/ApplicationProperties.java
new file mode 100644
index 000000000000..add1782678a1
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/ApplicationProperties.java
@@ -0,0 +1,15 @@
+package com.baeldung.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Properties specific to JHipster.
+ *
+ *
+ * Properties are configured in the application.yml file.
+ *
+ */
+@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
+public class ApplicationProperties {
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/AsyncConfiguration.java b/jhipster/src/main/java/com/baeldung/config/AsyncConfiguration.java
new file mode 100644
index 000000000000..dc1ba3751404
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/AsyncConfiguration.java
@@ -0,0 +1,46 @@
+package com.baeldung.config;
+
+import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
+import io.github.jhipster.config.JHipsterProperties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.*;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+
+@Configuration
+@EnableAsync
+@EnableScheduling
+public class AsyncConfiguration implements AsyncConfigurer {
+
+ private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
+
+ private final JHipsterProperties jHipsterProperties;
+
+ public AsyncConfiguration(JHipsterProperties jHipsterProperties) {
+ this.jHipsterProperties = jHipsterProperties;
+ }
+
+ @Override
+ @Bean(name = "taskExecutor")
+ public Executor getAsyncExecutor() {
+ log.debug("Creating Async Task Executor");
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
+ executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
+ executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
+ executor.setThreadNamePrefix("baeldung-Executor-");
+ return new ExceptionHandlingAsyncTaskExecutor(executor);
+ }
+
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return new SimpleAsyncUncaughtExceptionHandler();
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/CacheConfiguration.java b/jhipster/src/main/java/com/baeldung/config/CacheConfiguration.java
new file mode 100644
index 000000000000..4323fa076ab0
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/CacheConfiguration.java
@@ -0,0 +1,48 @@
+package com.baeldung.config;
+
+import io.github.jhipster.config.JHipsterProperties;
+import org.ehcache.config.builders.CacheConfigurationBuilder;
+import org.ehcache.config.builders.ResourcePoolsBuilder;
+import org.ehcache.expiry.Duration;
+import org.ehcache.expiry.Expirations;
+import org.ehcache.jsr107.Eh107Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.*;
+
+@Configuration
+@EnableCaching
+@AutoConfigureAfter(value = { MetricsConfiguration.class })
+@AutoConfigureBefore(value = { WebConfigurer.class, DatabaseConfiguration.class })
+public class CacheConfiguration {
+
+ private final javax.cache.configuration.Configuration jcacheConfiguration;
+
+ public CacheConfiguration(JHipsterProperties jHipsterProperties) {
+ JHipsterProperties.Cache.Ehcache ehcache =
+ jHipsterProperties.getCache().getEhcache();
+
+ jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
+ CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
+ ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
+ .withExpiry(Expirations.timeToLiveExpiration(Duration.of(ehcache.getTimeToLiveSeconds(), TimeUnit.SECONDS)))
+ .build());
+ }
+
+ @Bean
+ public JCacheManagerCustomizer cacheManagerCustomizer() {
+ return cm -> {
+ cm.createCache(com.baeldung.domain.User.class.getName(), jcacheConfiguration);
+ cm.createCache(com.baeldung.domain.Authority.class.getName(), jcacheConfiguration);
+ cm.createCache(com.baeldung.domain.User.class.getName() + ".authorities", jcacheConfiguration);
+ cm.createCache(com.baeldung.domain.Post.class.getName(), jcacheConfiguration);
+ cm.createCache(com.baeldung.domain.Comment.class.getName(), jcacheConfiguration);
+ // jhipster-needle-ehcache-add-entry
+ };
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/CloudDatabaseConfiguration.java b/jhipster/src/main/java/com/baeldung/config/CloudDatabaseConfiguration.java
new file mode 100644
index 000000000000..cae9ec1e8246
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/CloudDatabaseConfiguration.java
@@ -0,0 +1,23 @@
+package com.baeldung.config;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.config.java.AbstractCloudConfig;
+import org.springframework.context.annotation.*;
+
+import javax.sql.DataSource;
+
+@Configuration
+@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD)
+public class CloudDatabaseConfiguration extends AbstractCloudConfig {
+
+ private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class);
+
+ @Bean
+ public DataSource dataSource() {
+ log.info("Configuring JDBC datasource from a cloud provider");
+ return connectionFactory().dataSource();
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/Constants.java b/jhipster/src/main/java/com/baeldung/config/Constants.java
new file mode 100644
index 000000000000..9d797c19348a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/Constants.java
@@ -0,0 +1,16 @@
+package com.baeldung.config;
+
+/**
+ * Application constants.
+ */
+public final class Constants {
+
+ //Regex for acceptable logins
+ public static final String LOGIN_REGEX = "^[_'.@A-Za-z0-9-]*$";
+
+ public static final String SYSTEM_ACCOUNT = "system";
+ public static final String ANONYMOUS_USER = "anonymoususer";
+
+ private Constants() {
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/DatabaseConfiguration.java b/jhipster/src/main/java/com/baeldung/config/DatabaseConfiguration.java
new file mode 100644
index 000000000000..b64fd7cc8228
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/DatabaseConfiguration.java
@@ -0,0 +1,75 @@
+package com.baeldung.config;
+
+import io.github.jhipster.config.JHipsterConstants;
+import io.github.jhipster.config.liquibase.AsyncSpringLiquibase;
+
+import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
+import liquibase.integration.spring.SpringLiquibase;
+import org.h2.tools.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+import java.sql.SQLException;
+
+@Configuration
+@EnableJpaRepositories("com.baeldung.repository")
+@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
+@EnableTransactionManagement
+public class DatabaseConfiguration {
+
+ private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
+
+ private final Environment env;
+
+ public DatabaseConfiguration(Environment env) {
+ this.env = env;
+ }
+
+ /**
+ * Open the TCP port for the H2 database, so it is available remotely.
+ *
+ * @return the H2 database TCP server
+ * @throws SQLException if the server failed to start
+ */
+ @Bean(initMethod = "start", destroyMethod = "stop")
+ @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
+ public Server h2TCPServer() throws SQLException {
+ return Server.createTcpServer("-tcp","-tcpAllowOthers");
+ }
+
+ @Bean
+ public SpringLiquibase liquibase(@Qualifier("taskExecutor") TaskExecutor taskExecutor,
+ DataSource dataSource, LiquibaseProperties liquibaseProperties) {
+
+ // Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously
+ SpringLiquibase liquibase = new AsyncSpringLiquibase(taskExecutor, env);
+ liquibase.setDataSource(dataSource);
+ liquibase.setChangeLog("classpath:config/liquibase/master.xml");
+ liquibase.setContexts(liquibaseProperties.getContexts());
+ liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
+ liquibase.setDropFirst(liquibaseProperties.isDropFirst());
+ if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
+ liquibase.setShouldRun(false);
+ } else {
+ liquibase.setShouldRun(liquibaseProperties.isEnabled());
+ log.debug("Configuring Liquibase");
+ }
+ return liquibase;
+ }
+
+ @Bean
+ public Hibernate5Module hibernate5Module() {
+ return new Hibernate5Module();
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/DateTimeFormatConfiguration.java b/jhipster/src/main/java/com/baeldung/config/DateTimeFormatConfiguration.java
new file mode 100644
index 000000000000..aab57adfb039
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/DateTimeFormatConfiguration.java
@@ -0,0 +1,17 @@
+package com.baeldung.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@Configuration
+public class DateTimeFormatConfiguration extends WebMvcConfigurerAdapter {
+
+ @Override
+ public void addFormatters(FormatterRegistry registry) {
+ DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
+ registrar.setUseIsoFormat(true);
+ registrar.registerFormatters(registry);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/DefaultProfileUtil.java b/jhipster/src/main/java/com/baeldung/config/DefaultProfileUtil.java
new file mode 100644
index 000000000000..7918fb49a403
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/DefaultProfileUtil.java
@@ -0,0 +1,48 @@
+package com.baeldung.config;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.core.env.Environment;
+
+import java.util.*;
+
+/**
+ * Utility class to load a Spring profile to be used as default
+ * when there is no spring.profiles.active set in the environment or as command line argument.
+ * If the value is not available in application.yml then dev profile will be used as default.
+ */
+public final class DefaultProfileUtil {
+
+ private static final String SPRING_PROFILE_DEFAULT = "spring.profiles.default";
+
+ private DefaultProfileUtil() {
+ }
+
+ /**
+ * Set a default to use when no profile is configured.
+ *
+ * @param app the Spring application
+ */
+ public static void addDefaultProfile(SpringApplication app) {
+ Map defProperties = new HashMap<>();
+ /*
+ * The default profile to use when no other profiles are defined
+ * This cannot be set in the application.yml file.
+ * See https://github.com/spring-projects/spring-boot/issues/1219
+ */
+ defProperties.put(SPRING_PROFILE_DEFAULT, JHipsterConstants.SPRING_PROFILE_DEVELOPMENT);
+ app.setDefaultProperties(defProperties);
+ }
+
+ /**
+ * Get the profiles that are applied else get default profiles.
+ */
+ public static String[] getActiveProfiles(Environment env) {
+ String[] profiles = env.getActiveProfiles();
+ if (profiles.length == 0) {
+ return env.getDefaultProfiles();
+ }
+ return profiles;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/LocaleConfiguration.java b/jhipster/src/main/java/com/baeldung/config/LocaleConfiguration.java
new file mode 100644
index 000000000000..dd3a499d5608
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/LocaleConfiguration.java
@@ -0,0 +1,35 @@
+package com.baeldung.config;
+
+import io.github.jhipster.config.locale.AngularCookieLocaleResolver;
+
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.web.servlet.LocaleResolver;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
+
+@Configuration
+public class LocaleConfiguration extends WebMvcConfigurerAdapter implements EnvironmentAware {
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ // unused
+ }
+
+ @Bean(name = "localeResolver")
+ public LocaleResolver localeResolver() {
+ AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver();
+ cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY");
+ return cookieLocaleResolver;
+ }
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
+ localeChangeInterceptor.setParamName("language");
+ registry.addInterceptor(localeChangeInterceptor);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/LoggingAspectConfiguration.java b/jhipster/src/main/java/com/baeldung/config/LoggingAspectConfiguration.java
new file mode 100644
index 000000000000..936f54709af6
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/LoggingAspectConfiguration.java
@@ -0,0 +1,19 @@
+package com.baeldung.config;
+
+import com.baeldung.aop.logging.LoggingAspect;
+
+import io.github.jhipster.config.JHipsterConstants;
+
+import org.springframework.context.annotation.*;
+import org.springframework.core.env.Environment;
+
+@Configuration
+@EnableAspectJAutoProxy
+public class LoggingAspectConfiguration {
+
+ @Bean
+ @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
+ public LoggingAspect loggingAspect(Environment env) {
+ return new LoggingAspect(env);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/LoggingConfiguration.java b/jhipster/src/main/java/com/baeldung/config/LoggingConfiguration.java
new file mode 100644
index 000000000000..3ca6a4882187
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/LoggingConfiguration.java
@@ -0,0 +1,109 @@
+package com.baeldung.config;
+
+import io.github.jhipster.config.JHipsterProperties;
+
+import ch.qos.logback.classic.AsyncAppender;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.LoggerContextListener;
+import ch.qos.logback.core.spi.ContextAwareBase;
+import net.logstash.logback.appender.LogstashSocketAppender;
+import net.logstash.logback.stacktrace.ShortenedThrowableConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class LoggingConfiguration {
+
+ private final Logger log = LoggerFactory.getLogger(LoggingConfiguration.class);
+
+ private LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+
+ @Value("${spring.application.name}")
+ private String appName;
+
+ @Value("${server.port}")
+ private String serverPort;
+
+ private final JHipsterProperties jHipsterProperties;
+
+ public LoggingConfiguration(JHipsterProperties jHipsterProperties) {
+ this.jHipsterProperties = jHipsterProperties;
+ if (jHipsterProperties.getLogging().getLogstash().isEnabled()) {
+ addLogstashAppender(context);
+
+ // Add context listener
+ LogbackLoggerContextListener loggerContextListener = new LogbackLoggerContextListener();
+ loggerContextListener.setContext(context);
+ context.addListener(loggerContextListener);
+ }
+ }
+
+ public void addLogstashAppender(LoggerContext context) {
+ log.info("Initializing Logstash logging");
+
+ LogstashSocketAppender logstashAppender = new LogstashSocketAppender();
+ logstashAppender.setName("LOGSTASH");
+ logstashAppender.setContext(context);
+ String customFields = "{\"app_name\":\"" + appName + "\",\"app_port\":\"" + serverPort + "\"}";
+
+ // Set the Logstash appender config from JHipster properties
+ logstashAppender.setSyslogHost(jHipsterProperties.getLogging().getLogstash().getHost());
+ logstashAppender.setPort(jHipsterProperties.getLogging().getLogstash().getPort());
+ logstashAppender.setCustomFields(customFields);
+
+ // Limit the maximum length of the forwarded stacktrace so that it won't exceed the 8KB UDP limit of logstash
+ ShortenedThrowableConverter throwableConverter = new ShortenedThrowableConverter();
+ throwableConverter.setMaxLength(7500);
+ throwableConverter.setRootCauseFirst(true);
+ logstashAppender.setThrowableConverter(throwableConverter);
+
+ logstashAppender.start();
+
+ // Wrap the appender in an Async appender for performance
+ AsyncAppender asyncLogstashAppender = new AsyncAppender();
+ asyncLogstashAppender.setContext(context);
+ asyncLogstashAppender.setName("ASYNC_LOGSTASH");
+ asyncLogstashAppender.setQueueSize(jHipsterProperties.getLogging().getLogstash().getQueueSize());
+ asyncLogstashAppender.addAppender(logstashAppender);
+ asyncLogstashAppender.start();
+
+ context.getLogger("ROOT").addAppender(asyncLogstashAppender);
+ }
+
+ /**
+ * Logback configuration is achieved by configuration file and API.
+ * When configuration file change is detected, the configuration is reset.
+ * This listener ensures that the programmatic configuration is also re-applied after reset.
+ */
+ class LogbackLoggerContextListener extends ContextAwareBase implements LoggerContextListener {
+
+ @Override
+ public boolean isResetResistant() {
+ return true;
+ }
+
+ @Override
+ public void onStart(LoggerContext context) {
+ addLogstashAppender(context);
+ }
+
+ @Override
+ public void onReset(LoggerContext context) {
+ addLogstashAppender(context);
+ }
+
+ @Override
+ public void onStop(LoggerContext context) {
+ // Nothing to do.
+ }
+
+ @Override
+ public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) {
+ // Nothing to do.
+ }
+ }
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/MetricsConfiguration.java b/jhipster/src/main/java/com/baeldung/config/MetricsConfiguration.java
new file mode 100644
index 000000000000..94a50bed91fb
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/MetricsConfiguration.java
@@ -0,0 +1,94 @@
+package com.baeldung.config;
+
+import io.github.jhipster.config.JHipsterProperties;
+import io.github.jhipster.config.jcache.JCacheGaugeSet;
+
+import com.codahale.metrics.JmxReporter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Slf4jReporter;
+import com.codahale.metrics.health.HealthCheckRegistry;
+import com.codahale.metrics.jvm.*;
+import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
+import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter;
+import com.zaxxer.hikari.HikariDataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.*;
+
+import javax.annotation.PostConstruct;
+import java.lang.management.ManagementFactory;
+import java.util.concurrent.TimeUnit;
+
+@Configuration
+@EnableMetrics(proxyTargetClass = true)
+public class MetricsConfiguration extends MetricsConfigurerAdapter {
+
+ private static final String PROP_METRIC_REG_JVM_MEMORY = "jvm.memory";
+ private static final String PROP_METRIC_REG_JVM_GARBAGE = "jvm.garbage";
+ private static final String PROP_METRIC_REG_JVM_THREADS = "jvm.threads";
+ private static final String PROP_METRIC_REG_JVM_FILES = "jvm.files";
+ private static final String PROP_METRIC_REG_JVM_BUFFERS = "jvm.buffers";
+
+ private static final String PROP_METRIC_REG_JCACHE_STATISTICS = "jcache.statistics";
+ private final Logger log = LoggerFactory.getLogger(MetricsConfiguration.class);
+
+ private MetricRegistry metricRegistry = new MetricRegistry();
+
+ private HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
+
+ private final JHipsterProperties jHipsterProperties;
+
+ private HikariDataSource hikariDataSource;
+
+ public MetricsConfiguration(JHipsterProperties jHipsterProperties) {
+ this.jHipsterProperties = jHipsterProperties;
+ }
+
+ @Autowired(required = false)
+ public void setHikariDataSource(HikariDataSource hikariDataSource) {
+ this.hikariDataSource = hikariDataSource;
+ }
+
+ @Override
+ @Bean
+ public MetricRegistry getMetricRegistry() {
+ return metricRegistry;
+ }
+
+ @Override
+ @Bean
+ public HealthCheckRegistry getHealthCheckRegistry() {
+ return healthCheckRegistry;
+ }
+
+ @PostConstruct
+ public void init() {
+ log.debug("Registering JVM gauges");
+ metricRegistry.register(PROP_METRIC_REG_JVM_MEMORY, new MemoryUsageGaugeSet());
+ metricRegistry.register(PROP_METRIC_REG_JVM_GARBAGE, new GarbageCollectorMetricSet());
+ metricRegistry.register(PROP_METRIC_REG_JVM_THREADS, new ThreadStatesGaugeSet());
+ metricRegistry.register(PROP_METRIC_REG_JVM_FILES, new FileDescriptorRatioGauge());
+ metricRegistry.register(PROP_METRIC_REG_JVM_BUFFERS, new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
+
+ metricRegistry.register(PROP_METRIC_REG_JCACHE_STATISTICS, new JCacheGaugeSet());
+ if (hikariDataSource != null) {
+ log.debug("Monitoring the datasource");
+ hikariDataSource.setMetricRegistry(metricRegistry);
+ }
+ if (jHipsterProperties.getMetrics().getJmx().isEnabled()) {
+ log.debug("Initializing Metrics JMX reporting");
+ JmxReporter jmxReporter = JmxReporter.forRegistry(metricRegistry).build();
+ jmxReporter.start();
+ }
+ if (jHipsterProperties.getMetrics().getLogs().isEnabled()) {
+ log.info("Initializing Metrics Log reporting");
+ final Slf4jReporter reporter = Slf4jReporter.forRegistry(metricRegistry)
+ .outputTo(LoggerFactory.getLogger("metrics"))
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .build();
+ reporter.start(jHipsterProperties.getMetrics().getLogs().getReportFrequency(), TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/SecurityConfiguration.java b/jhipster/src/main/java/com/baeldung/config/SecurityConfiguration.java
new file mode 100644
index 000000000000..118d302fcfe2
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/SecurityConfiguration.java
@@ -0,0 +1,127 @@
+package com.baeldung.config;
+
+import com.baeldung.security.*;
+import com.baeldung.security.jwt.*;
+
+import io.github.jhipster.security.*;
+
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+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;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.filter.CorsFilter;
+
+import javax.annotation.PostConstruct;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ private final AuthenticationManagerBuilder authenticationManagerBuilder;
+
+ private final UserDetailsService userDetailsService;
+
+ private final TokenProvider tokenProvider;
+
+ private final CorsFilter corsFilter;
+
+ public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService,
+ TokenProvider tokenProvider,
+ CorsFilter corsFilter) {
+
+ this.authenticationManagerBuilder = authenticationManagerBuilder;
+ this.userDetailsService = userDetailsService;
+ this.tokenProvider = tokenProvider;
+ this.corsFilter = corsFilter;
+ }
+
+ @PostConstruct
+ public void init() {
+ try {
+ authenticationManagerBuilder
+ .userDetailsService(userDetailsService)
+ .passwordEncoder(passwordEncoder());
+ } catch (Exception e) {
+ throw new BeanInitializationException("Security configuration failed", e);
+ }
+ }
+
+ @Bean
+ public Http401UnauthorizedEntryPoint http401UnauthorizedEntryPoint() {
+ return new Http401UnauthorizedEntryPoint();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web.ignoring()
+ .antMatchers(HttpMethod.OPTIONS, "/**")
+ .antMatchers("/app/**/*.{js,html}")
+ .antMatchers("/bower_components/**")
+ .antMatchers("/i18n/**")
+ .antMatchers("/content/**")
+ .antMatchers("/swagger-ui/index.html")
+ .antMatchers("/test/**")
+ .antMatchers("/h2-console/**");
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
+ .exceptionHandling()
+ .authenticationEntryPoint(http401UnauthorizedEntryPoint())
+ .and()
+ .csrf()
+ .disable()
+ .headers()
+ .frameOptions()
+ .disable()
+ .and()
+ .sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and()
+ .authorizeRequests()
+ .antMatchers("/api/register").permitAll()
+ .antMatchers("/api/activate").permitAll()
+ .antMatchers("/api/authenticate").permitAll()
+ .antMatchers("/api/account/reset_password/init").permitAll()
+ .antMatchers("/api/account/reset_password/finish").permitAll()
+ .antMatchers("/api/profile-info").permitAll()
+ .antMatchers("/api/**").authenticated()
+ .antMatchers("/management/health").permitAll()
+ .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
+ .antMatchers("/v2/api-docs/**").permitAll()
+ .antMatchers("/swagger-resources/configuration/ui").permitAll()
+ .antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN)
+ .and()
+ .apply(securityConfigurerAdapter());
+
+ }
+
+ private JWTConfigurer securityConfigurerAdapter() {
+ return new JWTConfigurer(tokenProvider);
+ }
+
+ @Bean
+ public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
+ return new SecurityEvaluationContextExtension();
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/ThymeleafConfiguration.java b/jhipster/src/main/java/com/baeldung/config/ThymeleafConfiguration.java
new file mode 100644
index 000000000000..68afcae71a2d
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/ThymeleafConfiguration.java
@@ -0,0 +1,26 @@
+package com.baeldung.config;
+
+import org.apache.commons.lang3.CharEncoding;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.*;
+import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
+
+@Configuration
+public class ThymeleafConfiguration {
+
+ @SuppressWarnings("unused")
+ private final Logger log = LoggerFactory.getLogger(ThymeleafConfiguration.class);
+
+ @Bean
+ @Description("Thymeleaf template resolver serving HTML 5 emails")
+ public ClassLoaderTemplateResolver emailTemplateResolver() {
+ ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver();
+ emailTemplateResolver.setPrefix("mails/");
+ emailTemplateResolver.setSuffix(".html");
+ emailTemplateResolver.setTemplateMode("HTML5");
+ emailTemplateResolver.setCharacterEncoding(CharEncoding.UTF_8);
+ emailTemplateResolver.setOrder(1);
+ return emailTemplateResolver;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/WebConfigurer.java b/jhipster/src/main/java/com/baeldung/config/WebConfigurer.java
new file mode 100644
index 000000000000..bd80a063eb04
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/WebConfigurer.java
@@ -0,0 +1,186 @@
+package com.baeldung.config;
+
+import io.github.jhipster.config.JHipsterConstants;
+import io.github.jhipster.config.JHipsterProperties;
+import io.github.jhipster.web.filter.CachingHttpHeadersFilter;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.servlet.InstrumentedFilter;
+import com.codahale.metrics.servlets.MetricsServlet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.embedded.*;
+import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
+import io.undertow.UndertowOptions;
+import org.springframework.boot.web.servlet.ServletContextInitializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.*;
+import javax.servlet.*;
+
+/**
+ * Configuration of web application with Servlet 3.0 APIs.
+ */
+@Configuration
+public class WebConfigurer implements ServletContextInitializer, EmbeddedServletContainerCustomizer {
+
+ private final Logger log = LoggerFactory.getLogger(WebConfigurer.class);
+
+ private final Environment env;
+
+ private final JHipsterProperties jHipsterProperties;
+
+ private MetricRegistry metricRegistry;
+
+ public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) {
+
+ this.env = env;
+ this.jHipsterProperties = jHipsterProperties;
+ }
+
+ @Override
+ public void onStartup(ServletContext servletContext) throws ServletException {
+ if (env.getActiveProfiles().length != 0) {
+ log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
+ }
+ EnumSet disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
+ initMetrics(servletContext, disps);
+ if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
+ initCachingHttpHeadersFilter(servletContext, disps);
+ }
+ if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
+ initH2Console(servletContext);
+ }
+ log.info("Web application fully configured");
+ }
+
+ /**
+ * Customize the Servlet engine: Mime types, the document root, the cache.
+ */
+ @Override
+ public void customize(ConfigurableEmbeddedServletContainer container) {
+ MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
+ // IE issue, see https://github.com/jhipster/generator-jhipster/pull/711
+ mappings.add("html", "text/html;charset=utf-8");
+ // CloudFoundry issue, see https://github.com/cloudfoundry/gorouter/issues/64
+ mappings.add("json", "text/html;charset=utf-8");
+ container.setMimeMappings(mappings);
+ // When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets.
+ setLocationForStaticAssets(container);
+
+ /*
+ * Enable HTTP/2 for Undertow - https://twitter.com/ankinson/status/829256167700492288
+ * HTTP/2 requires HTTPS, so HTTP requests will fallback to HTTP/1.1.
+ * See the JHipsterProperties class and your application-*.yml configuration files
+ * for more information.
+ */
+ if (jHipsterProperties.getHttp().getVersion().equals(JHipsterProperties.Http.Version.V_2_0) &&
+ container instanceof UndertowEmbeddedServletContainerFactory) {
+
+ ((UndertowEmbeddedServletContainerFactory) container)
+ .addBuilderCustomizers(builder ->
+ builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true));
+ }
+ }
+
+ private void setLocationForStaticAssets(ConfigurableEmbeddedServletContainer container) {
+ File root;
+ String prefixPath = resolvePathPrefix();
+ root = new File(prefixPath + "target/www/");
+ if (root.exists() && root.isDirectory()) {
+ container.setDocumentRoot(root);
+ }
+ }
+
+ /**
+ * Resolve path prefix to static resources.
+ */
+ private String resolvePathPrefix() {
+ String fullExecutablePath = this.getClass().getResource("").getPath();
+ String rootPath = Paths.get(".").toUri().normalize().getPath();
+ String extractedPath = fullExecutablePath.replace(rootPath, "");
+ int extractionEndIndex = extractedPath.indexOf("target/");
+ if(extractionEndIndex <= 0) {
+ return "";
+ }
+ return extractedPath.substring(0, extractionEndIndex);
+ }
+
+ /**
+ * Initializes the caching HTTP Headers Filter.
+ */
+ private void initCachingHttpHeadersFilter(ServletContext servletContext,
+ EnumSet disps) {
+ log.debug("Registering Caching HTTP Headers Filter");
+ FilterRegistration.Dynamic cachingHttpHeadersFilter =
+ servletContext.addFilter("cachingHttpHeadersFilter",
+ new CachingHttpHeadersFilter(jHipsterProperties));
+
+ cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/content/*");
+ cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/app/*");
+ cachingHttpHeadersFilter.setAsyncSupported(true);
+ }
+
+ /**
+ * Initializes Metrics.
+ */
+ private void initMetrics(ServletContext servletContext, EnumSet disps) {
+ log.debug("Initializing Metrics registries");
+ servletContext.setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE,
+ metricRegistry);
+ servletContext.setAttribute(MetricsServlet.METRICS_REGISTRY,
+ metricRegistry);
+
+ log.debug("Registering Metrics Filter");
+ FilterRegistration.Dynamic metricsFilter = servletContext.addFilter("webappMetricsFilter",
+ new InstrumentedFilter());
+
+ metricsFilter.addMappingForUrlPatterns(disps, true, "/*");
+ metricsFilter.setAsyncSupported(true);
+
+ log.debug("Registering Metrics Servlet");
+ ServletRegistration.Dynamic metricsAdminServlet =
+ servletContext.addServlet("metricsServlet", new MetricsServlet());
+
+ metricsAdminServlet.addMapping("/management/metrics/*");
+ metricsAdminServlet.setAsyncSupported(true);
+ metricsAdminServlet.setLoadOnStartup(2);
+ }
+
+ @Bean
+ public CorsFilter corsFilter() {
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ CorsConfiguration config = jHipsterProperties.getCors();
+ if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) {
+ log.debug("Registering CORS filter");
+ source.registerCorsConfiguration("/api/**", config);
+ source.registerCorsConfiguration("/v2/api-docs", config);
+ }
+ return new CorsFilter(source);
+ }
+
+ /**
+ * Initializes H2 console.
+ */
+ private void initH2Console(ServletContext servletContext) {
+ log.debug("Initialize H2 console");
+ ServletRegistration.Dynamic h2ConsoleServlet = servletContext.addServlet("H2Console", new org.h2.server.web.WebServlet());
+ h2ConsoleServlet.addMapping("/h2-console/*");
+ h2ConsoleServlet.setInitParameter("-properties", "src/main/resources/");
+ h2ConsoleServlet.setLoadOnStartup(1);
+ }
+
+ @Autowired(required = false)
+ public void setMetricRegistry(MetricRegistry metricRegistry) {
+ this.metricRegistry = metricRegistry;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/audit/AuditEventConverter.java b/jhipster/src/main/java/com/baeldung/config/audit/AuditEventConverter.java
new file mode 100644
index 000000000000..206232075184
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/audit/AuditEventConverter.java
@@ -0,0 +1,91 @@
+package com.baeldung.config.audit;
+
+import com.baeldung.domain.PersistentAuditEvent;
+
+import org.springframework.boot.actuate.audit.AuditEvent;
+import org.springframework.security.web.authentication.WebAuthenticationDetails;
+import org.springframework.stereotype.Component;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.*;
+
+@Component
+public class AuditEventConverter {
+
+ /**
+ * Convert a list of PersistentAuditEvent to a list of AuditEvent
+ *
+ * @param persistentAuditEvents the list to convert
+ * @return the converted list.
+ */
+ public List convertToAuditEvent(Iterable persistentAuditEvents) {
+ if (persistentAuditEvents == null) {
+ return Collections.emptyList();
+ }
+ List auditEvents = new ArrayList<>();
+ for (PersistentAuditEvent persistentAuditEvent : persistentAuditEvents) {
+ auditEvents.add(convertToAuditEvent(persistentAuditEvent));
+ }
+ return auditEvents;
+ }
+
+ /**
+ * Convert a PersistentAuditEvent to an AuditEvent
+ *
+ * @param persistentAuditEvent the event to convert
+ * @return the converted list.
+ */
+ public AuditEvent convertToAuditEvent(PersistentAuditEvent persistentAuditEvent) {
+ Instant instant = persistentAuditEvent.getAuditEventDate().atZone(ZoneId.systemDefault()).toInstant();
+ return new AuditEvent(Date.from(instant), persistentAuditEvent.getPrincipal(),
+ persistentAuditEvent.getAuditEventType(), convertDataToObjects(persistentAuditEvent.getData()));
+ }
+
+ /**
+ * Internal conversion. This is needed to support the current SpringBoot actuator AuditEventRepository interface
+ *
+ * @param data the data to convert
+ * @return a map of String, Object
+ */
+ public Map convertDataToObjects(Map data) {
+ Map results = new HashMap<>();
+
+ if (data != null) {
+ for (Map.Entry entry : data.entrySet()) {
+ results.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Internal conversion. This method will allow to save additional data.
+ * By default, it will save the object as string
+ *
+ * @param data the data to convert
+ * @return a map of String, String
+ */
+ public Map convertDataToStrings(Map data) {
+ Map results = new HashMap<>();
+
+ if (data != null) {
+ for (Map.Entry entry : data.entrySet()) {
+ Object object = entry.getValue();
+
+ // Extract the data that will be saved.
+ if (object instanceof WebAuthenticationDetails) {
+ WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) object;
+ results.put("remoteAddress", authenticationDetails.getRemoteAddress());
+ results.put("sessionId", authenticationDetails.getSessionId());
+ } else if (object != null) {
+ results.put(entry.getKey(), object.toString());
+ } else {
+ results.put(entry.getKey(), "null");
+ }
+ }
+ }
+
+ return results;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/config/audit/package-info.java b/jhipster/src/main/java/com/baeldung/config/audit/package-info.java
new file mode 100644
index 000000000000..8ed4f851f041
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/audit/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Audit specific code.
+ */
+package com.baeldung.config.audit;
diff --git a/jhipster/src/main/java/com/baeldung/config/package-info.java b/jhipster/src/main/java/com/baeldung/config/package-info.java
new file mode 100644
index 000000000000..5e54ad6d033b
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/config/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring Framework configuration files.
+ */
+package com.baeldung.config;
diff --git a/jhipster/src/main/java/com/baeldung/domain/AbstractAuditingEntity.java b/jhipster/src/main/java/com/baeldung/domain/AbstractAuditingEntity.java
new file mode 100644
index 000000000000..2d181e5dc84a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/domain/AbstractAuditingEntity.java
@@ -0,0 +1,80 @@
+package com.baeldung.domain;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.hibernate.envers.Audited;
+import org.springframework.data.annotation.CreatedBy;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedBy;
+import org.springframework.data.annotation.LastModifiedDate;
+
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+import java.time.ZonedDateTime;
+import javax.persistence.Column;
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+
+/**
+ * Base abstract class for entities which will hold definitions for created, last modified by and created,
+ * last modified by date.
+ */
+@MappedSuperclass
+@Audited
+@EntityListeners(AuditingEntityListener.class)
+public abstract class AbstractAuditingEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @CreatedBy
+ @Column(name = "created_by", nullable = false, length = 50, updatable = false)
+ @JsonIgnore
+ private String createdBy;
+
+ @CreatedDate
+ @Column(name = "created_date", nullable = false)
+ @JsonIgnore
+ private ZonedDateTime createdDate = ZonedDateTime.now();
+
+ @LastModifiedBy
+ @Column(name = "last_modified_by", length = 50)
+ @JsonIgnore
+ private String lastModifiedBy;
+
+ @LastModifiedDate
+ @Column(name = "last_modified_date")
+ @JsonIgnore
+ private ZonedDateTime lastModifiedDate = ZonedDateTime.now();
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public ZonedDateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ public void setCreatedDate(ZonedDateTime createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public String getLastModifiedBy() {
+ return lastModifiedBy;
+ }
+
+ public void setLastModifiedBy(String lastModifiedBy) {
+ this.lastModifiedBy = lastModifiedBy;
+ }
+
+ public ZonedDateTime getLastModifiedDate() {
+ return lastModifiedDate;
+ }
+
+ public void setLastModifiedDate(ZonedDateTime lastModifiedDate) {
+ this.lastModifiedDate = lastModifiedDate;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/domain/Authority.java b/jhipster/src/main/java/com/baeldung/domain/Authority.java
new file mode 100644
index 000000000000..8a94ad0bb250
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/domain/Authority.java
@@ -0,0 +1,66 @@
+package com.baeldung.domain;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Column;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+
+/**
+ * An authority (a security role) used by Spring Security.
+ */
+@Entity
+@Table(name = "jhi_authority")
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+public class Authority implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @NotNull
+ @Size(min = 0, max = 50)
+ @Id
+ @Column(length = 50)
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Authority authority = (Authority) o;
+
+ if (name != null ? !name.equals(authority.name) : authority.name != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return name != null ? name.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Authority{" +
+ "name='" + name + '\'' +
+ "}";
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/domain/Comment.java b/jhipster/src/main/java/com/baeldung/domain/Comment.java
new file mode 100644
index 000000000000..c4587b62316f
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/domain/Comment.java
@@ -0,0 +1,114 @@
+package com.baeldung.domain;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+
+import javax.persistence.*;
+import javax.validation.constraints.*;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.Objects;
+
+/**
+ * A Comment.
+ */
+@Entity
+@Table(name = "comment")
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+public class Comment implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull
+ @Size(min = 10, max = 100)
+ @Column(name = "text", length = 100, nullable = false)
+ private String text;
+
+ @NotNull
+ @Column(name = "creation_date", nullable = false)
+ private LocalDate creationDate;
+
+ @ManyToOne(optional = false)
+ @NotNull
+ private Post post;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public Comment text(String text) {
+ this.text = text;
+ return this;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public LocalDate getCreationDate() {
+ return creationDate;
+ }
+
+ public Comment creationDate(LocalDate creationDate) {
+ this.creationDate = creationDate;
+ return this;
+ }
+
+ public void setCreationDate(LocalDate creationDate) {
+ this.creationDate = creationDate;
+ }
+
+ public Post getPost() {
+ return post;
+ }
+
+ public Comment post(Post post) {
+ this.post = post;
+ return this;
+ }
+
+ public void setPost(Post post) {
+ this.post = post;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Comment comment = (Comment) o;
+ if (comment.id == null || id == null) {
+ return false;
+ }
+ return Objects.equals(id, comment.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id);
+ }
+
+ @Override
+ public String toString() {
+ return "Comment{" +
+ "id=" + id +
+ ", text='" + text + "'" +
+ ", creationDate='" + creationDate + "'" +
+ '}';
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/domain/PersistentAuditEvent.java b/jhipster/src/main/java/com/baeldung/domain/PersistentAuditEvent.java
new file mode 100644
index 000000000000..c0fb0cb146c1
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/domain/PersistentAuditEvent.java
@@ -0,0 +1,78 @@
+package com.baeldung.domain;
+
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Persist AuditEvent managed by the Spring Boot actuator
+ * @see org.springframework.boot.actuate.audit.AuditEvent
+ */
+@Entity
+@Table(name = "jhi_persistent_audit_event")
+public class PersistentAuditEvent implements Serializable {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "event_id")
+ private Long id;
+
+ @NotNull
+ @Column(nullable = false)
+ private String principal;
+
+ @Column(name = "event_date")
+ private LocalDateTime auditEventDate;
+ @Column(name = "event_type")
+ private String auditEventType;
+
+ @ElementCollection
+ @MapKeyColumn(name = "name")
+ @Column(name = "value")
+ @CollectionTable(name = "jhi_persistent_audit_evt_data", joinColumns=@JoinColumn(name="event_id"))
+ private Map data = new HashMap<>();
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getPrincipal() {
+ return principal;
+ }
+
+ public void setPrincipal(String principal) {
+ this.principal = principal;
+ }
+
+ public LocalDateTime getAuditEventDate() {
+ return auditEventDate;
+ }
+
+ public void setAuditEventDate(LocalDateTime auditEventDate) {
+ this.auditEventDate = auditEventDate;
+ }
+
+ public String getAuditEventType() {
+ return auditEventType;
+ }
+
+ public void setAuditEventType(String auditEventType) {
+ this.auditEventType = auditEventType;
+ }
+
+ public Map getData() {
+ return data;
+ }
+
+ public void setData(Map data) {
+ this.data = data;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/domain/Post.java b/jhipster/src/main/java/com/baeldung/domain/Post.java
new file mode 100644
index 000000000000..7f084dc1770a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/domain/Post.java
@@ -0,0 +1,133 @@
+package com.baeldung.domain;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+
+import javax.persistence.*;
+import javax.validation.constraints.*;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.Objects;
+
+/**
+ * A Post.
+ */
+@Entity
+@Table(name = "post")
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+public class Post implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull
+ @Size(min = 10, max = 100)
+ @Column(name = "title", length = 100, nullable = false)
+ private String title;
+
+ @NotNull
+ @Size(min = 10, max = 1000)
+ @Column(name = "content", length = 1000, nullable = false)
+ private String content;
+
+ @NotNull
+ @Column(name = "creation_date", nullable = false)
+ private LocalDate creationDate;
+
+ @ManyToOne(optional = false)
+ @NotNull
+ private User creator;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public Post title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public Post content(String content) {
+ this.content = content;
+ return this;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public LocalDate getCreationDate() {
+ return creationDate;
+ }
+
+ public Post creationDate(LocalDate creationDate) {
+ this.creationDate = creationDate;
+ return this;
+ }
+
+ public void setCreationDate(LocalDate creationDate) {
+ this.creationDate = creationDate;
+ }
+
+ public User getCreator() {
+ return creator;
+ }
+
+ public Post creator(User user) {
+ this.creator = user;
+ return this;
+ }
+
+ public void setCreator(User user) {
+ this.creator = user;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Post post = (Post) o;
+ if (post.id == null || id == null) {
+ return false;
+ }
+ return Objects.equals(id, post.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id);
+ }
+
+ @Override
+ public String toString() {
+ return "Post{" +
+ "id=" + id +
+ ", title='" + title + "'" +
+ ", content='" + content + "'" +
+ ", creationDate='" + creationDate + "'" +
+ '}';
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/domain/User.java b/jhipster/src/main/java/com/baeldung/domain/User.java
new file mode 100644
index 000000000000..76aa3a520994
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/domain/User.java
@@ -0,0 +1,235 @@
+package com.baeldung.domain;
+
+import com.baeldung.config.Constants;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.hibernate.annotations.BatchSize;
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+import org.hibernate.validator.constraints.Email;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.time.ZonedDateTime;
+
+/**
+ * A user.
+ */
+@Entity
+@Table(name = "jhi_user")
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+public class User extends AbstractAuditingEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull
+ @Pattern(regexp = Constants.LOGIN_REGEX)
+ @Size(min = 1, max = 50)
+ @Column(length = 50, unique = true, nullable = false)
+ private String login;
+
+ @JsonIgnore
+ @NotNull
+ @Size(min = 60, max = 60)
+ @Column(name = "password_hash",length = 60)
+ private String password;
+
+ @Size(max = 50)
+ @Column(name = "first_name", length = 50)
+ private String firstName;
+
+ @Size(max = 50)
+ @Column(name = "last_name", length = 50)
+ private String lastName;
+
+ @Email
+ @Size(max = 100)
+ @Column(length = 100, unique = true)
+ private String email;
+
+ @NotNull
+ @Column(nullable = false)
+ private boolean activated = false;
+
+ @Size(min = 2, max = 5)
+ @Column(name = "lang_key", length = 5)
+ private String langKey;
+
+ @Size(max = 256)
+ @Column(name = "image_url", length = 256)
+ private String imageUrl;
+
+ @Size(max = 20)
+ @Column(name = "activation_key", length = 20)
+ @JsonIgnore
+ private String activationKey;
+
+ @Size(max = 20)
+ @Column(name = "reset_key", length = 20)
+ private String resetKey;
+
+ @Column(name = "reset_date")
+ private ZonedDateTime resetDate = null;
+
+ @JsonIgnore
+ @ManyToMany
+ @JoinTable(
+ name = "jhi_user_authority",
+ joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
+ inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})
+ @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+ @BatchSize(size = 20)
+ private Set authorities = new HashSet<>();
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ //Lowercase the login before saving it in database
+ public void setLogin(String login) {
+ this.login = login.toLowerCase(Locale.ENGLISH);
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getImageUrl() {
+ return imageUrl;
+ }
+
+ public void setImageUrl(String imageUrl) {
+ this.imageUrl = imageUrl;
+ }
+
+ public boolean getActivated() {
+ return activated;
+ }
+
+ public void setActivated(boolean activated) {
+ this.activated = activated;
+ }
+
+ public String getActivationKey() {
+ return activationKey;
+ }
+
+ public void setActivationKey(String activationKey) {
+ this.activationKey = activationKey;
+ }
+
+ public String getResetKey() {
+ return resetKey;
+ }
+
+ public void setResetKey(String resetKey) {
+ this.resetKey = resetKey;
+ }
+
+ public ZonedDateTime getResetDate() {
+ return resetDate;
+ }
+
+ public void setResetDate(ZonedDateTime resetDate) {
+ this.resetDate = resetDate;
+ }
+
+ public String getLangKey() {
+ return langKey;
+ }
+
+ public void setLangKey(String langKey) {
+ this.langKey = langKey;
+ }
+
+ public Set getAuthorities() {
+ return authorities;
+ }
+
+ public void setAuthorities(Set authorities) {
+ this.authorities = authorities;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ User user = (User) o;
+
+ if (!login.equals(user.login)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return login.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "login='" + login + '\'' +
+ ", firstName='" + firstName + '\'' +
+ ", lastName='" + lastName + '\'' +
+ ", email='" + email + '\'' +
+ ", imageUrl='" + imageUrl + '\'' +
+ ", activated='" + activated + '\'' +
+ ", langKey='" + langKey + '\'' +
+ ", activationKey='" + activationKey + '\'' +
+ "}";
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/domain/package-info.java b/jhipster/src/main/java/com/baeldung/domain/package-info.java
new file mode 100644
index 000000000000..2492048c77e6
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/domain/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * JPA domain objects.
+ */
+package com.baeldung.domain;
diff --git a/jhipster/src/main/java/com/baeldung/repository/AuthorityRepository.java b/jhipster/src/main/java/com/baeldung/repository/AuthorityRepository.java
new file mode 100644
index 000000000000..fab63174aaa2
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/repository/AuthorityRepository.java
@@ -0,0 +1,11 @@
+package com.baeldung.repository;
+
+import com.baeldung.domain.Authority;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * Spring Data JPA repository for the Authority entity.
+ */
+public interface AuthorityRepository extends JpaRepository {
+}
diff --git a/jhipster/src/main/java/com/baeldung/repository/CommentRepository.java b/jhipster/src/main/java/com/baeldung/repository/CommentRepository.java
new file mode 100644
index 000000000000..d2d2618c36ec
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/repository/CommentRepository.java
@@ -0,0 +1,15 @@
+package com.baeldung.repository;
+
+import com.baeldung.domain.Comment;
+
+import org.springframework.data.jpa.repository.*;
+
+import java.util.List;
+
+/**
+ * Spring Data JPA repository for the Comment entity.
+ */
+@SuppressWarnings("unused")
+public interface CommentRepository extends JpaRepository {
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/repository/CustomAuditEventRepository.java b/jhipster/src/main/java/com/baeldung/repository/CustomAuditEventRepository.java
new file mode 100644
index 000000000000..fcfa9baeec98
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/repository/CustomAuditEventRepository.java
@@ -0,0 +1,81 @@
+package com.baeldung.repository;
+
+import com.baeldung.config.Constants;
+import com.baeldung.config.audit.AuditEventConverter;
+import com.baeldung.domain.PersistentAuditEvent;
+
+import org.springframework.boot.actuate.audit.AuditEvent;
+import org.springframework.boot.actuate.audit.AuditEventRepository;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * An implementation of Spring Boot's AuditEventRepository.
+ */
+@Repository
+public class CustomAuditEventRepository implements AuditEventRepository {
+
+ private static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
+
+ private final PersistenceAuditEventRepository persistenceAuditEventRepository;
+
+ private final AuditEventConverter auditEventConverter;
+
+ public CustomAuditEventRepository(PersistenceAuditEventRepository persistenceAuditEventRepository,
+ AuditEventConverter auditEventConverter) {
+
+ this.persistenceAuditEventRepository = persistenceAuditEventRepository;
+ this.auditEventConverter = auditEventConverter;
+ }
+
+ @Override
+ public List find(Date after) {
+ Iterable persistentAuditEvents =
+ persistenceAuditEventRepository.findByAuditEventDateAfter(LocalDateTime.from(after.toInstant()));
+ return auditEventConverter.convertToAuditEvent(persistentAuditEvents);
+ }
+
+ @Override
+ public List find(String principal, Date after) {
+ Iterable persistentAuditEvents;
+ if (principal == null && after == null) {
+ persistentAuditEvents = persistenceAuditEventRepository.findAll();
+ } else if (after == null) {
+ persistentAuditEvents = persistenceAuditEventRepository.findByPrincipal(principal);
+ } else {
+ persistentAuditEvents =
+ persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfter(principal, LocalDateTime.from(after.toInstant()));
+ }
+ return auditEventConverter.convertToAuditEvent(persistentAuditEvents);
+ }
+
+ @Override
+ public List find(String principal, Date after, String type) {
+ Iterable persistentAuditEvents =
+ persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfterAndAuditEventType(principal, LocalDateTime.from(after.toInstant()), type);
+ return auditEventConverter.convertToAuditEvent(persistentAuditEvents);
+ }
+
+ @Override
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
+ public void add(AuditEvent event) {
+ if (!AUTHORIZATION_FAILURE.equals(event.getType()) &&
+ !Constants.ANONYMOUS_USER.equals(event.getPrincipal())) {
+
+ PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent();
+ persistentAuditEvent.setPrincipal(event.getPrincipal());
+ persistentAuditEvent.setAuditEventType(event.getType());
+ Instant instant = Instant.ofEpochMilli(event.getTimestamp().getTime());
+ persistentAuditEvent.setAuditEventDate(LocalDateTime.ofInstant(instant, ZoneId.systemDefault()));
+ persistentAuditEvent.setData(auditEventConverter.convertDataToStrings(event.getData()));
+ persistenceAuditEventRepository.save(persistentAuditEvent);
+ }
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/repository/PersistenceAuditEventRepository.java b/jhipster/src/main/java/com/baeldung/repository/PersistenceAuditEventRepository.java
new file mode 100644
index 000000000000..f4b7f1c5bf32
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/repository/PersistenceAuditEventRepository.java
@@ -0,0 +1,26 @@
+package com.baeldung.repository;
+
+import com.baeldung.domain.PersistentAuditEvent;
+
+import java.time.LocalDateTime;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+
+/**
+ * Spring Data JPA repository for the PersistentAuditEvent entity.
+ */
+public interface PersistenceAuditEventRepository extends JpaRepository {
+
+ List findByPrincipal(String principal);
+
+ List findByAuditEventDateAfter(LocalDateTime after);
+
+ List findByPrincipalAndAuditEventDateAfter(String principal, LocalDateTime after);
+
+ List findByPrincipalAndAuditEventDateAfterAndAuditEventType(String principle, LocalDateTime after, String type);
+
+ Page findAllByAuditEventDateBetween(LocalDateTime fromDate, LocalDateTime toDate, Pageable pageable);
+}
diff --git a/jhipster/src/main/java/com/baeldung/repository/PostRepository.java b/jhipster/src/main/java/com/baeldung/repository/PostRepository.java
new file mode 100644
index 000000000000..22e8aa1372a8
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/repository/PostRepository.java
@@ -0,0 +1,18 @@
+package com.baeldung.repository;
+
+import com.baeldung.domain.Post;
+
+import org.springframework.data.jpa.repository.*;
+
+import java.util.List;
+
+/**
+ * Spring Data JPA repository for the Post entity.
+ */
+@SuppressWarnings("unused")
+public interface PostRepository extends JpaRepository {
+
+ @Query("select post from Post post where post.creator.login = ?#{principal.username}")
+ List findByCreatorIsCurrentUser();
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/repository/UserRepository.java b/jhipster/src/main/java/com/baeldung/repository/UserRepository.java
new file mode 100644
index 000000000000..a8f81499107b
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/repository/UserRepository.java
@@ -0,0 +1,37 @@
+package com.baeldung.repository;
+
+import com.baeldung.domain.User;
+
+import java.time.ZonedDateTime;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.EntityGraph;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Spring Data JPA repository for the User entity.
+ */
+public interface UserRepository extends JpaRepository {
+
+ Optional findOneByActivationKey(String activationKey);
+
+ List findAllByActivatedIsFalseAndCreatedDateBefore(ZonedDateTime dateTime);
+
+ Optional findOneByResetKey(String resetKey);
+
+ Optional findOneByEmail(String email);
+
+ Optional findOneByLogin(String login);
+
+ @EntityGraph(attributePaths = "authorities")
+ User findOneWithAuthoritiesById(Long id);
+
+ @EntityGraph(attributePaths = "authorities")
+ Optional findOneWithAuthoritiesByLogin(String login);
+
+ Page findAllByLoginNot(Pageable pageable, String login);
+}
diff --git a/jhipster/src/main/java/com/baeldung/repository/package-info.java b/jhipster/src/main/java/com/baeldung/repository/package-info.java
new file mode 100644
index 000000000000..9d1fb837c32d
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/repository/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring Data JPA repositories.
+ */
+package com.baeldung.repository;
diff --git a/jhipster/src/main/java/com/baeldung/security/AuthoritiesConstants.java b/jhipster/src/main/java/com/baeldung/security/AuthoritiesConstants.java
new file mode 100644
index 000000000000..40cf54e4f610
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/AuthoritiesConstants.java
@@ -0,0 +1,16 @@
+package com.baeldung.security;
+
+/**
+ * Constants for Spring Security authorities.
+ */
+public final class AuthoritiesConstants {
+
+ public static final String ADMIN = "ROLE_ADMIN";
+
+ public static final String USER = "ROLE_USER";
+
+ public static final String ANONYMOUS = "ROLE_ANONYMOUS";
+
+ private AuthoritiesConstants() {
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/security/DomainUserDetailsService.java b/jhipster/src/main/java/com/baeldung/security/DomainUserDetailsService.java
new file mode 100644
index 000000000000..6ce07739f719
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/DomainUserDetailsService.java
@@ -0,0 +1,51 @@
+package com.baeldung.security;
+
+import com.baeldung.domain.User;
+import com.baeldung.repository.UserRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Authenticate a user from the database.
+ */
+@Component("userDetailsService")
+public class DomainUserDetailsService implements UserDetailsService {
+
+ private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class);
+
+ private final UserRepository userRepository;
+
+ public DomainUserDetailsService(UserRepository userRepository) {
+ this.userRepository = userRepository;
+ }
+
+ @Override
+ @Transactional
+ public UserDetails loadUserByUsername(final String login) {
+ log.debug("Authenticating {}", login);
+ String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
+ Optional userFromDatabase = userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin);
+ return userFromDatabase.map(user -> {
+ if (!user.getActivated()) {
+ throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
+ }
+ List grantedAuthorities = user.getAuthorities().stream()
+ .map(authority -> new SimpleGrantedAuthority(authority.getName()))
+ .collect(Collectors.toList());
+ return new org.springframework.security.core.userdetails.User(lowercaseLogin,
+ user.getPassword(),
+ grantedAuthorities);
+ }).orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " +
+ "database"));
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/security/SecurityUtils.java b/jhipster/src/main/java/com/baeldung/security/SecurityUtils.java
new file mode 100644
index 000000000000..96aaef380564
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/SecurityUtils.java
@@ -0,0 +1,68 @@
+package com.baeldung.security;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * Utility class for Spring Security.
+ */
+public final class SecurityUtils {
+
+ private SecurityUtils() {
+ }
+
+ /**
+ * Get the login of the current user.
+ *
+ * @return the login of the current user
+ */
+ public static String getCurrentUserLogin() {
+ SecurityContext securityContext = SecurityContextHolder.getContext();
+ Authentication authentication = securityContext.getAuthentication();
+ String userName = null;
+ if (authentication != null) {
+ if (authentication.getPrincipal() instanceof UserDetails) {
+ UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
+ userName = springSecurityUser.getUsername();
+ } else if (authentication.getPrincipal() instanceof String) {
+ userName = (String) authentication.getPrincipal();
+ }
+ }
+ return userName;
+ }
+
+ /**
+ * Check if a user is authenticated.
+ *
+ * @return true if the user is authenticated, false otherwise
+ */
+ public static boolean isAuthenticated() {
+ SecurityContext securityContext = SecurityContextHolder.getContext();
+ Authentication authentication = securityContext.getAuthentication();
+ if (authentication != null) {
+ return authentication.getAuthorities().stream()
+ .noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS));
+ }
+ return false;
+ }
+
+ /**
+ * If the current user has a specific authority (security role).
+ *
+ * The name of this method comes from the isUserInRole() method in the Servlet API
+ *
+ * @param authority the authority to check
+ * @return true if the current user has the authority, false otherwise
+ */
+ public static boolean isCurrentUserInRole(String authority) {
+ SecurityContext securityContext = SecurityContextHolder.getContext();
+ Authentication authentication = securityContext.getAuthentication();
+ if (authentication != null) {
+ return authentication.getAuthorities().stream()
+ .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority));
+ }
+ return false;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/security/SpringSecurityAuditorAware.java b/jhipster/src/main/java/com/baeldung/security/SpringSecurityAuditorAware.java
new file mode 100644
index 000000000000..d59917153cf1
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/SpringSecurityAuditorAware.java
@@ -0,0 +1,19 @@
+package com.baeldung.security;
+
+import com.baeldung.config.Constants;
+
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Implementation of AuditorAware based on Spring Security.
+ */
+@Component
+public class SpringSecurityAuditorAware implements AuditorAware {
+
+ @Override
+ public String getCurrentAuditor() {
+ String userName = SecurityUtils.getCurrentUserLogin();
+ return userName != null ? userName : Constants.SYSTEM_ACCOUNT;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/security/UserNotActivatedException.java b/jhipster/src/main/java/com/baeldung/security/UserNotActivatedException.java
new file mode 100644
index 000000000000..7265188cad6b
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/UserNotActivatedException.java
@@ -0,0 +1,19 @@
+package com.baeldung.security;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * This exception is thrown in case of a not activated user trying to authenticate.
+ */
+public class UserNotActivatedException extends AuthenticationException {
+
+ private static final long serialVersionUID = 1L;
+
+ public UserNotActivatedException(String message) {
+ super(message);
+ }
+
+ public UserNotActivatedException(String message, Throwable t) {
+ super(message, t);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/security/jwt/JWTConfigurer.java b/jhipster/src/main/java/com/baeldung/security/jwt/JWTConfigurer.java
new file mode 100644
index 000000000000..5720812b9f25
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/jwt/JWTConfigurer.java
@@ -0,0 +1,23 @@
+package com.baeldung.security.jwt;
+
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+public class JWTConfigurer extends SecurityConfigurerAdapter {
+
+ public static final String AUTHORIZATION_HEADER = "Authorization";
+
+ private TokenProvider tokenProvider;
+
+ public JWTConfigurer(TokenProvider tokenProvider) {
+ this.tokenProvider = tokenProvider;
+ }
+
+ @Override
+ public void configure(HttpSecurity http) throws Exception {
+ JWTFilter customFilter = new JWTFilter(tokenProvider);
+ http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/security/jwt/JWTFilter.java b/jhipster/src/main/java/com/baeldung/security/jwt/JWTFilter.java
new file mode 100644
index 000000000000..89b1947e058d
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/jwt/JWTFilter.java
@@ -0,0 +1,58 @@
+package com.baeldung.security.jwt;
+
+import java.io.IOException;
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.GenericFilterBean;
+
+import io.jsonwebtoken.ExpiredJwtException;
+
+/**
+ * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is
+ * found.
+ */
+public class JWTFilter extends GenericFilterBean {
+
+ private final Logger log = LoggerFactory.getLogger(JWTFilter.class);
+
+ private TokenProvider tokenProvider;
+
+ public JWTFilter(TokenProvider tokenProvider) {
+ this.tokenProvider = tokenProvider;
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+ throws IOException, ServletException {
+ try {
+ HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
+ String jwt = resolveToken(httpServletRequest);
+ if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {
+ Authentication authentication = this.tokenProvider.getAuthentication(jwt);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ filterChain.doFilter(servletRequest, servletResponse);
+ } catch (ExpiredJwtException eje) {
+ log.info("Security exception for user {} - {}",
+ eje.getClaims().getSubject(), eje.getMessage());
+
+ log.trace("Security exception trace: {}", eje);
+ ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+ }
+
+ private String resolveToken(HttpServletRequest request){
+ String bearerToken = request.getHeader(JWTConfigurer.AUTHORIZATION_HEADER);
+ if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
+ return bearerToken.substring(7, bearerToken.length());
+ }
+ return null;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/security/jwt/TokenProvider.java b/jhipster/src/main/java/com/baeldung/security/jwt/TokenProvider.java
new file mode 100644
index 000000000000..3ba4d7c793d2
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/jwt/TokenProvider.java
@@ -0,0 +1,109 @@
+package com.baeldung.security.jwt;
+
+import io.github.jhipster.config.JHipsterProperties;
+
+import java.util.*;
+import java.util.stream.Collectors;
+import javax.annotation.PostConstruct;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.stereotype.Component;
+
+import io.jsonwebtoken.*;
+
+@Component
+public class TokenProvider {
+
+ private final Logger log = LoggerFactory.getLogger(TokenProvider.class);
+
+ private static final String AUTHORITIES_KEY = "auth";
+
+ private String secretKey;
+
+ private long tokenValidityInMilliseconds;
+
+ private long tokenValidityInMillisecondsForRememberMe;
+
+ private final JHipsterProperties jHipsterProperties;
+
+ public TokenProvider(JHipsterProperties jHipsterProperties) {
+ this.jHipsterProperties = jHipsterProperties;
+ }
+
+ @PostConstruct
+ public void init() {
+ this.secretKey =
+ jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret();
+
+ this.tokenValidityInMilliseconds =
+ 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds();
+ this.tokenValidityInMillisecondsForRememberMe =
+ 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSecondsForRememberMe();
+ }
+
+ public String createToken(Authentication authentication, Boolean rememberMe) {
+ String authorities = authentication.getAuthorities().stream()
+ .map(GrantedAuthority::getAuthority)
+ .collect(Collectors.joining(","));
+
+ long now = (new Date()).getTime();
+ Date validity;
+ if (rememberMe) {
+ validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);
+ } else {
+ validity = new Date(now + this.tokenValidityInMilliseconds);
+ }
+
+ return Jwts.builder()
+ .setSubject(authentication.getName())
+ .claim(AUTHORITIES_KEY, authorities)
+ .signWith(SignatureAlgorithm.HS512, secretKey)
+ .setExpiration(validity)
+ .compact();
+ }
+
+ public Authentication getAuthentication(String token) {
+ Claims claims = Jwts.parser()
+ .setSigningKey(secretKey)
+ .parseClaimsJws(token)
+ .getBody();
+
+ Collection extends GrantedAuthority> authorities =
+ Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
+ .map(SimpleGrantedAuthority::new)
+ .collect(Collectors.toList());
+
+ User principal = new User(claims.getSubject(), "", authorities);
+
+ return new UsernamePasswordAuthenticationToken(principal, "", authorities);
+ }
+
+ public boolean validateToken(String authToken) {
+ try {
+ Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
+ return true;
+ } catch (SignatureException e) {
+ log.info("Invalid JWT signature.");
+ log.trace("Invalid JWT signature trace: {}", e);
+ } catch (MalformedJwtException e) {
+ log.info("Invalid JWT token.");
+ log.trace("Invalid JWT token trace: {}", e);
+ } catch (ExpiredJwtException e) {
+ log.info("Expired JWT token.");
+ log.trace("Expired JWT token trace: {}", e);
+ } catch (UnsupportedJwtException e) {
+ log.info("Unsupported JWT token.");
+ log.trace("Unsupported JWT token trace: {}", e);
+ } catch (IllegalArgumentException e) {
+ log.info("JWT token compact of handler are invalid.");
+ log.trace("JWT token compact of handler are invalid trace: {}", e);
+ }
+ return false;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/security/package-info.java b/jhipster/src/main/java/com/baeldung/security/package-info.java
new file mode 100644
index 000000000000..1e29c15b4e2f
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/security/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring Security configuration.
+ */
+package com.baeldung.security;
diff --git a/jhipster/src/main/java/com/baeldung/service/AuditEventService.java b/jhipster/src/main/java/com/baeldung/service/AuditEventService.java
new file mode 100644
index 000000000000..87018549222a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/AuditEventService.java
@@ -0,0 +1,50 @@
+package com.baeldung.service;
+
+import com.baeldung.config.audit.AuditEventConverter;
+import com.baeldung.repository.PersistenceAuditEventRepository;
+import java.time.LocalDateTime;
+import org.springframework.boot.actuate.audit.AuditEvent;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
+
+/**
+ * Service for managing audit events.
+ *
+ * This is the default implementation to support SpringBoot Actuator AuditEventRepository
+ *
+ */
+@Service
+@Transactional
+public class AuditEventService {
+
+ private final PersistenceAuditEventRepository persistenceAuditEventRepository;
+
+ private final AuditEventConverter auditEventConverter;
+
+ public AuditEventService(
+ PersistenceAuditEventRepository persistenceAuditEventRepository,
+ AuditEventConverter auditEventConverter) {
+
+ this.persistenceAuditEventRepository = persistenceAuditEventRepository;
+ this.auditEventConverter = auditEventConverter;
+ }
+
+ public Page findAll(Pageable pageable) {
+ return persistenceAuditEventRepository.findAll(pageable)
+ .map(auditEventConverter::convertToAuditEvent);
+ }
+
+ public Page findByDates(LocalDateTime fromDate, LocalDateTime toDate, Pageable pageable) {
+ return persistenceAuditEventRepository.findAllByAuditEventDateBetween(fromDate, toDate, pageable)
+ .map(auditEventConverter::convertToAuditEvent);
+ }
+
+ public Optional find(Long id) {
+ return Optional.ofNullable(persistenceAuditEventRepository.findOne(id)).map
+ (auditEventConverter::convertToAuditEvent);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/service/MailService.java b/jhipster/src/main/java/com/baeldung/service/MailService.java
new file mode 100644
index 000000000000..9f85531da733
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/MailService.java
@@ -0,0 +1,108 @@
+package com.baeldung.service;
+
+import com.baeldung.domain.User;
+
+import io.github.jhipster.config.JHipsterProperties;
+
+import org.apache.commons.lang3.CharEncoding;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.MessageSource;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.thymeleaf.context.Context;
+import org.thymeleaf.spring4.SpringTemplateEngine;
+
+import javax.mail.internet.MimeMessage;
+import java.util.Locale;
+
+/**
+ * Service for sending e-mails.
+ *
+ * We use the @Async annotation to send e-mails asynchronously.
+ *
+ */
+@Service
+public class MailService {
+
+ private final Logger log = LoggerFactory.getLogger(MailService.class);
+
+ private static final String USER = "user";
+
+ private static final String BASE_URL = "baseUrl";
+
+ private final JHipsterProperties jHipsterProperties;
+
+ private final JavaMailSender javaMailSender;
+
+ private final MessageSource messageSource;
+
+ private final SpringTemplateEngine templateEngine;
+
+ public MailService(JHipsterProperties jHipsterProperties, JavaMailSender javaMailSender,
+ MessageSource messageSource, SpringTemplateEngine templateEngine) {
+
+ this.jHipsterProperties = jHipsterProperties;
+ this.javaMailSender = javaMailSender;
+ this.messageSource = messageSource;
+ this.templateEngine = templateEngine;
+ }
+
+ @Async
+ public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) {
+ log.debug("Send e-mail[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}",
+ isMultipart, isHtml, to, subject, content);
+
+ // Prepare message using a Spring helper
+ MimeMessage mimeMessage = javaMailSender.createMimeMessage();
+ try {
+ MimeMessageHelper message = new MimeMessageHelper(mimeMessage, isMultipart, CharEncoding.UTF_8);
+ message.setTo(to);
+ message.setFrom(jHipsterProperties.getMail().getFrom());
+ message.setSubject(subject);
+ message.setText(content, isHtml);
+ javaMailSender.send(mimeMessage);
+ log.debug("Sent e-mail to User '{}'", to);
+ } catch (Exception e) {
+ log.warn("E-mail could not be sent to user '{}'", to, e);
+ }
+ }
+
+ @Async
+ public void sendActivationEmail(User user) {
+ log.debug("Sending activation e-mail to '{}'", user.getEmail());
+ Locale locale = Locale.forLanguageTag(user.getLangKey());
+ Context context = new Context(locale);
+ context.setVariable(USER, user);
+ context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl());
+ String content = templateEngine.process("activationEmail", context);
+ String subject = messageSource.getMessage("email.activation.title", null, locale);
+ sendEmail(user.getEmail(), subject, content, false, true);
+ }
+
+ @Async
+ public void sendCreationEmail(User user) {
+ log.debug("Sending creation e-mail to '{}'", user.getEmail());
+ Locale locale = Locale.forLanguageTag(user.getLangKey());
+ Context context = new Context(locale);
+ context.setVariable(USER, user);
+ context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl());
+ String content = templateEngine.process("creationEmail", context);
+ String subject = messageSource.getMessage("email.activation.title", null, locale);
+ sendEmail(user.getEmail(), subject, content, false, true);
+ }
+
+ @Async
+ public void sendPasswordResetMail(User user) {
+ log.debug("Sending password reset e-mail to '{}'", user.getEmail());
+ Locale locale = Locale.forLanguageTag(user.getLangKey());
+ Context context = new Context(locale);
+ context.setVariable(USER, user);
+ context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl());
+ String content = templateEngine.process("passwordResetEmail", context);
+ String subject = messageSource.getMessage("email.reset.title", null, locale);
+ sendEmail(user.getEmail(), subject, content, false, true);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/service/UserService.java b/jhipster/src/main/java/com/baeldung/service/UserService.java
new file mode 100644
index 000000000000..7b9096e0dade
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/UserService.java
@@ -0,0 +1,228 @@
+package com.baeldung.service;
+
+import com.baeldung.domain.Authority;
+import com.baeldung.domain.User;
+import com.baeldung.repository.AuthorityRepository;
+import com.baeldung.config.Constants;
+import com.baeldung.repository.UserRepository;
+import com.baeldung.security.AuthoritiesConstants;
+import com.baeldung.security.SecurityUtils;
+import com.baeldung.service.util.RandomUtil;
+import com.baeldung.service.dto.UserDTO;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.ZonedDateTime;
+import java.util.*;
+
+/**
+ * Service class for managing users.
+ */
+@Service
+@Transactional
+public class UserService {
+
+ private final Logger log = LoggerFactory.getLogger(UserService.class);
+
+ private final UserRepository userRepository;
+
+ private final PasswordEncoder passwordEncoder;
+
+ private final AuthorityRepository authorityRepository;
+
+ public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthorityRepository authorityRepository) {
+ this.userRepository = userRepository;
+ this.passwordEncoder = passwordEncoder;
+ this.authorityRepository = authorityRepository;
+ }
+
+ public Optional activateRegistration(String key) {
+ log.debug("Activating user for activation key {}", key);
+ return userRepository.findOneByActivationKey(key)
+ .map(user -> {
+ // activate given user for the registration key.
+ user.setActivated(true);
+ user.setActivationKey(null);
+ log.debug("Activated user: {}", user);
+ return user;
+ });
+ }
+
+ public Optional completePasswordReset(String newPassword, String key) {
+ log.debug("Reset user password for reset key {}", key);
+
+ return userRepository.findOneByResetKey(key)
+ .filter(user -> {
+ ZonedDateTime oneDayAgo = ZonedDateTime.now().minusHours(24);
+ return user.getResetDate().isAfter(oneDayAgo);
+ })
+ .map(user -> {
+ user.setPassword(passwordEncoder.encode(newPassword));
+ user.setResetKey(null);
+ user.setResetDate(null);
+ return user;
+ });
+ }
+
+ public Optional requestPasswordReset(String mail) {
+ return userRepository.findOneByEmail(mail)
+ .filter(User::getActivated)
+ .map(user -> {
+ user.setResetKey(RandomUtil.generateResetKey());
+ user.setResetDate(ZonedDateTime.now());
+ return user;
+ });
+ }
+
+ public User createUser(String login, String password, String firstName, String lastName, String email,
+ String imageUrl, String langKey) {
+
+ User newUser = new User();
+ Authority authority = authorityRepository.findOne(AuthoritiesConstants.USER);
+ Set authorities = new HashSet<>();
+ String encryptedPassword = passwordEncoder.encode(password);
+ newUser.setLogin(login);
+ // new user gets initially a generated password
+ newUser.setPassword(encryptedPassword);
+ newUser.setFirstName(firstName);
+ newUser.setLastName(lastName);
+ newUser.setEmail(email);
+ newUser.setImageUrl(imageUrl);
+ newUser.setLangKey(langKey);
+ // new user is not active
+ newUser.setActivated(false);
+ // new user gets registration key
+ newUser.setActivationKey(RandomUtil.generateActivationKey());
+ authorities.add(authority);
+ newUser.setAuthorities(authorities);
+ userRepository.save(newUser);
+ log.debug("Created Information for User: {}", newUser);
+ return newUser;
+ }
+
+ public User createUser(UserDTO userDTO) {
+ User user = new User();
+ user.setLogin(userDTO.getLogin());
+ user.setFirstName(userDTO.getFirstName());
+ user.setLastName(userDTO.getLastName());
+ user.setEmail(userDTO.getEmail());
+ user.setImageUrl(userDTO.getImageUrl());
+ if (userDTO.getLangKey() == null) {
+ user.setLangKey("en"); // default language
+ } else {
+ user.setLangKey(userDTO.getLangKey());
+ }
+ if (userDTO.getAuthorities() != null) {
+ Set authorities = new HashSet<>();
+ userDTO.getAuthorities().forEach(
+ authority -> authorities.add(authorityRepository.findOne(authority))
+ );
+ user.setAuthorities(authorities);
+ }
+ String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword());
+ user.setPassword(encryptedPassword);
+ user.setResetKey(RandomUtil.generateResetKey());
+ user.setResetDate(ZonedDateTime.now());
+ user.setActivated(true);
+ userRepository.save(user);
+ log.debug("Created Information for User: {}", user);
+ return user;
+ }
+
+ /**
+ * Update basic information (first name, last name, email, language) for the current user.
+ */
+ public void updateUser(String firstName, String lastName, String email, String langKey) {
+ userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).ifPresent(user -> {
+ user.setFirstName(firstName);
+ user.setLastName(lastName);
+ user.setEmail(email);
+ user.setLangKey(langKey);
+ log.debug("Changed Information for User: {}", user);
+ });
+ }
+
+ /**
+ * Update all information for a specific user, and return the modified user.
+ */
+ public Optional updateUser(UserDTO userDTO) {
+ return Optional.of(userRepository
+ .findOne(userDTO.getId()))
+ .map(user -> {
+ user.setLogin(userDTO.getLogin());
+ user.setFirstName(userDTO.getFirstName());
+ user.setLastName(userDTO.getLastName());
+ user.setEmail(userDTO.getEmail());
+ user.setImageUrl(userDTO.getImageUrl());
+ user.setActivated(userDTO.isActivated());
+ user.setLangKey(userDTO.getLangKey());
+ Set managedAuthorities = user.getAuthorities();
+ managedAuthorities.clear();
+ userDTO.getAuthorities().stream()
+ .map(authorityRepository::findOne)
+ .forEach(managedAuthorities::add);
+ log.debug("Changed Information for User: {}", user);
+ return user;
+ })
+ .map(UserDTO::new);
+ }
+
+ public void deleteUser(String login) {
+ userRepository.findOneByLogin(login).ifPresent(user -> {
+ userRepository.delete(user);
+ log.debug("Deleted User: {}", user);
+ });
+ }
+
+ public void changePassword(String password) {
+ userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).ifPresent(user -> {
+ String encryptedPassword = passwordEncoder.encode(password);
+ user.setPassword(encryptedPassword);
+ log.debug("Changed password for User: {}", user);
+ });
+ }
+
+ @Transactional(readOnly = true)
+ public Page getAllManagedUsers(Pageable pageable) {
+ return userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER).map(UserDTO::new);
+ }
+
+ @Transactional(readOnly = true)
+ public Optional getUserWithAuthoritiesByLogin(String login) {
+ return userRepository.findOneWithAuthoritiesByLogin(login);
+ }
+
+ @Transactional(readOnly = true)
+ public User getUserWithAuthorities(Long id) {
+ return userRepository.findOneWithAuthoritiesById(id);
+ }
+
+ @Transactional(readOnly = true)
+ public User getUserWithAuthorities() {
+ return userRepository.findOneWithAuthoritiesByLogin(SecurityUtils.getCurrentUserLogin()).orElse(null);
+ }
+
+
+ /**
+ * Not activated users should be automatically deleted after 3 days.
+ *
+ * This is scheduled to get fired everyday, at 01:00 (am).
+ *
+ */
+ @Scheduled(cron = "0 0 1 * * ?")
+ public void removeNotActivatedUsers() {
+ ZonedDateTime now = ZonedDateTime.now();
+ List users = userRepository.findAllByActivatedIsFalseAndCreatedDateBefore(now.minusDays(3));
+ for (User user : users) {
+ log.debug("Deleting not activated user {}", user.getLogin());
+ userRepository.delete(user);
+ }
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/service/dto/UserDTO.java b/jhipster/src/main/java/com/baeldung/service/dto/UserDTO.java
new file mode 100644
index 000000000000..1080bab7b81a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/dto/UserDTO.java
@@ -0,0 +1,167 @@
+package com.baeldung.service.dto;
+
+import com.baeldung.config.Constants;
+
+import com.baeldung.domain.Authority;
+import com.baeldung.domain.User;
+
+import org.hibernate.validator.constraints.Email;
+
+import javax.validation.constraints.*;
+import java.time.ZonedDateTime;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A DTO representing a user, with his authorities.
+ */
+public class UserDTO {
+
+ private Long id;
+
+ @Pattern(regexp = Constants.LOGIN_REGEX)
+ @Size(min = 1, max = 50)
+ private String login;
+
+ @Size(max = 50)
+ private String firstName;
+
+ @Size(max = 50)
+ private String lastName;
+
+ @Email
+ @Size(min = 5, max = 100)
+ private String email;
+
+ @Size(max = 256)
+ private String imageUrl;
+
+ private boolean activated = false;
+
+ @Size(min = 2, max = 5)
+ private String langKey;
+
+ private String createdBy;
+
+ private ZonedDateTime createdDate;
+
+ private String lastModifiedBy;
+
+ private ZonedDateTime lastModifiedDate;
+
+ private Set authorities;
+
+ public UserDTO() {
+ // Empty constructor needed for MapStruct.
+ }
+
+ public UserDTO(User user) {
+ this(user.getId(), user.getLogin(), user.getFirstName(), user.getLastName(),
+ user.getEmail(), user.getActivated(), user.getImageUrl(), user.getLangKey(),
+ user.getCreatedBy(), user.getCreatedDate(), user.getLastModifiedBy(), user.getLastModifiedDate(),
+ user.getAuthorities().stream().map(Authority::getName)
+ .collect(Collectors.toSet()));
+ }
+
+ public UserDTO(Long id, String login, String firstName, String lastName,
+ String email, boolean activated, String imageUrl, String langKey,
+ String createdBy, ZonedDateTime createdDate, String lastModifiedBy, ZonedDateTime lastModifiedDate,
+ Set authorities) {
+
+ this.id = id;
+ this.login = login;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.email = email;
+ this.activated = activated;
+ this.imageUrl = imageUrl;
+ this.langKey = langKey;
+ this.createdBy = createdBy;
+ this.createdDate = createdDate;
+ this.lastModifiedBy = lastModifiedBy;
+ this.lastModifiedDate = lastModifiedDate;
+ this.authorities = authorities;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ public void setLogin(String login) {
+ this.login = login;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getImageUrl() {
+ return imageUrl;
+ }
+
+ public boolean isActivated() {
+ return activated;
+ }
+
+ public String getLangKey() {
+ return langKey;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public ZonedDateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ public String getLastModifiedBy() {
+ return lastModifiedBy;
+ }
+
+ public ZonedDateTime getLastModifiedDate() {
+ return lastModifiedDate;
+ }
+
+ public void setLastModifiedDate(ZonedDateTime lastModifiedDate) {
+ this.lastModifiedDate = lastModifiedDate;
+ }
+
+ public Set getAuthorities() {
+ return authorities;
+ }
+
+ @Override
+ public String toString() {
+ return "UserDTO{" +
+ "login='" + login + '\'' +
+ ", firstName='" + firstName + '\'' +
+ ", lastName='" + lastName + '\'' +
+ ", email='" + email + '\'' +
+ ", imageUrl='" + imageUrl + '\'' +
+ ", activated=" + activated +
+ ", langKey='" + langKey + '\'' +
+ ", createdBy=" + createdBy +
+ ", createdDate=" + createdDate +
+ ", lastModifiedBy='" + lastModifiedBy + '\'' +
+ ", lastModifiedDate=" + lastModifiedDate +
+ ", authorities=" + authorities +
+ "}";
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/service/dto/package-info.java b/jhipster/src/main/java/com/baeldung/service/dto/package-info.java
new file mode 100644
index 000000000000..12d048eb7a7e
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/dto/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Data Transfer Objects.
+ */
+package com.baeldung.service.dto;
diff --git a/jhipster/src/main/java/com/baeldung/service/mapper/UserMapper.java b/jhipster/src/main/java/com/baeldung/service/mapper/UserMapper.java
new file mode 100644
index 000000000000..aaada37162e3
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/mapper/UserMapper.java
@@ -0,0 +1,55 @@
+package com.baeldung.service.mapper;
+
+import com.baeldung.domain.Authority;
+import com.baeldung.domain.User;
+import com.baeldung.service.dto.UserDTO;
+import org.mapstruct.*;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Mapper for the entity User and its DTO UserDTO.
+ */
+@Mapper(componentModel = "spring", uses = {})
+public interface UserMapper {
+
+ UserDTO userToUserDTO(User user);
+
+ List usersToUserDTOs(List users);
+
+ @Mapping(target = "createdBy", ignore = true)
+ @Mapping(target = "createdDate", ignore = true)
+ @Mapping(target = "lastModifiedBy", ignore = true)
+ @Mapping(target = "lastModifiedDate", ignore = true)
+ @Mapping(target = "activationKey", ignore = true)
+ @Mapping(target = "resetKey", ignore = true)
+ @Mapping(target = "resetDate", ignore = true)
+ @Mapping(target = "password", ignore = true)
+ User userDTOToUser(UserDTO userDTO);
+
+ List userDTOsToUsers(List userDTOs);
+
+ default User userFromId(Long id) {
+ if (id == null) {
+ return null;
+ }
+ User user = new User();
+ user.setId(id);
+ return user;
+ }
+
+ default Set stringsFromAuthorities (Set authorities) {
+ return authorities.stream().map(Authority::getName)
+ .collect(Collectors.toSet());
+ }
+
+ default Set authoritiesFromStrings(Set strings) {
+ return strings.stream().map(string -> {
+ Authority auth = new Authority();
+ auth.setName(string);
+ return auth;
+ }).collect(Collectors.toSet());
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/service/mapper/package-info.java b/jhipster/src/main/java/com/baeldung/service/mapper/package-info.java
new file mode 100644
index 000000000000..75f8d24f08d1
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/mapper/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * MapStruct mappers for mapping domain objects and Data Transfer Objects.
+ */
+package com.baeldung.service.mapper;
diff --git a/jhipster/src/main/java/com/baeldung/service/package-info.java b/jhipster/src/main/java/com/baeldung/service/package-info.java
new file mode 100644
index 000000000000..0695bc147305
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Service layer beans.
+ */
+package com.baeldung.service;
diff --git a/jhipster/src/main/java/com/baeldung/service/util/RandomUtil.java b/jhipster/src/main/java/com/baeldung/service/util/RandomUtil.java
new file mode 100644
index 000000000000..1f4d9f8b5d32
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/service/util/RandomUtil.java
@@ -0,0 +1,41 @@
+package com.baeldung.service.util;
+
+import org.apache.commons.lang3.RandomStringUtils;
+
+/**
+ * Utility class for generating random Strings.
+ */
+public final class RandomUtil {
+
+ private static final int DEF_COUNT = 20;
+
+ private RandomUtil() {
+ }
+
+ /**
+ * Generate a password.
+ *
+ * @return the generated password
+ */
+ public static String generatePassword() {
+ return RandomStringUtils.randomAlphanumeric(DEF_COUNT);
+ }
+
+ /**
+ * Generate an activation key.
+ *
+ * @return the generated activation key
+ */
+ public static String generateActivationKey() {
+ return RandomStringUtils.randomNumeric(DEF_COUNT);
+ }
+
+ /**
+ * Generate a reset key.
+ *
+ * @return the generated reset key
+ */
+ public static String generateResetKey() {
+ return RandomStringUtils.randomNumeric(DEF_COUNT);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/AccountResource.java b/jhipster/src/main/java/com/baeldung/web/rest/AccountResource.java
new file mode 100644
index 000000000000..c1009fc9184a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/AccountResource.java
@@ -0,0 +1,202 @@
+package com.baeldung.web.rest;
+
+import com.codahale.metrics.annotation.Timed;
+
+import com.baeldung.domain.User;
+import com.baeldung.repository.UserRepository;
+import com.baeldung.security.SecurityUtils;
+import com.baeldung.service.MailService;
+import com.baeldung.service.UserService;
+import com.baeldung.service.dto.UserDTO;
+import com.baeldung.web.rest.vm.KeyAndPasswordVM;
+import com.baeldung.web.rest.vm.ManagedUserVM;
+import com.baeldung.web.rest.util.HeaderUtil;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+import java.util.*;
+
+/**
+ * REST controller for managing the current user's account.
+ */
+@RestController
+@RequestMapping("/api")
+public class AccountResource {
+
+ private final Logger log = LoggerFactory.getLogger(AccountResource.class);
+
+ private final UserRepository userRepository;
+
+ private final UserService userService;
+
+ private final MailService mailService;
+
+ public AccountResource(UserRepository userRepository, UserService userService,
+ MailService mailService) {
+
+ this.userRepository = userRepository;
+ this.userService = userService;
+ this.mailService = mailService;
+ }
+
+ /**
+ * POST /register : register the user.
+ *
+ * @param managedUserVM the managed user View Model
+ * @return the ResponseEntity with status 201 (Created) if the user is registered or 400 (Bad Request) if the login or e-mail is already in use
+ */
+ @PostMapping(path = "/register",
+ produces={MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE})
+ @Timed
+ public ResponseEntity registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) {
+
+ HttpHeaders textPlainHeaders = new HttpHeaders();
+ textPlainHeaders.setContentType(MediaType.TEXT_PLAIN);
+
+ return userRepository.findOneByLogin(managedUserVM.getLogin().toLowerCase())
+ .map(user -> new ResponseEntity<>("login already in use", textPlainHeaders, HttpStatus.BAD_REQUEST))
+ .orElseGet(() -> userRepository.findOneByEmail(managedUserVM.getEmail())
+ .map(user -> new ResponseEntity<>("e-mail address already in use", textPlainHeaders, HttpStatus.BAD_REQUEST))
+ .orElseGet(() -> {
+ User user = userService
+ .createUser(managedUserVM.getLogin(), managedUserVM.getPassword(),
+ managedUserVM.getFirstName(), managedUserVM.getLastName(),
+ managedUserVM.getEmail().toLowerCase(), managedUserVM.getImageUrl(), managedUserVM.getLangKey());
+
+ mailService.sendActivationEmail(user);
+ return new ResponseEntity<>(HttpStatus.CREATED);
+ })
+ );
+ }
+
+ /**
+ * GET /activate : activate the registered user.
+ *
+ * @param key the activation key
+ * @return the ResponseEntity with status 200 (OK) and the activated user in body, or status 500 (Internal Server Error) if the user couldn't be activated
+ */
+ @GetMapping("/activate")
+ @Timed
+ public ResponseEntity activateAccount(@RequestParam(value = "key") String key) {
+ return userService.activateRegistration(key)
+ .map(user -> new ResponseEntity(HttpStatus.OK))
+ .orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+
+ /**
+ * GET /authenticate : check if the user is authenticated, and return its login.
+ *
+ * @param request the HTTP request
+ * @return the login if the user is authenticated
+ */
+ @GetMapping("/authenticate")
+ @Timed
+ public String isAuthenticated(HttpServletRequest request) {
+ log.debug("REST request to check if the current user is authenticated");
+ return request.getRemoteUser();
+ }
+
+ /**
+ * GET /account : get the current user.
+ *
+ * @return the ResponseEntity with status 200 (OK) and the current user in body, or status 500 (Internal Server Error) if the user couldn't be returned
+ */
+ @GetMapping("/account")
+ @Timed
+ public ResponseEntity getAccount() {
+ return Optional.ofNullable(userService.getUserWithAuthorities())
+ .map(user -> new ResponseEntity<>(new UserDTO(user), HttpStatus.OK))
+ .orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+
+ /**
+ * POST /account : update the current user information.
+ *
+ * @param userDTO the current user information
+ * @return the ResponseEntity with status 200 (OK), or status 400 (Bad Request) or 500 (Internal Server Error) if the user couldn't be updated
+ */
+ @PostMapping("/account")
+ @Timed
+ public ResponseEntity saveAccount(@Valid @RequestBody UserDTO userDTO) {
+ Optional existingUser = userRepository.findOneByEmail(userDTO.getEmail());
+ if (existingUser.isPresent() && (!existingUser.get().getLogin().equalsIgnoreCase(userDTO.getLogin()))) {
+ return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert("user-management", "emailexists", "Email already in use")).body(null);
+ }
+ return userRepository
+ .findOneByLogin(SecurityUtils.getCurrentUserLogin())
+ .map(u -> {
+ userService.updateUser(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(),
+ userDTO.getLangKey());
+ return new ResponseEntity(HttpStatus.OK);
+ })
+ .orElseGet(() -> new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+
+ /**
+ * POST /account/change_password : changes the current user's password
+ *
+ * @param password the new password
+ * @return the ResponseEntity with status 200 (OK), or status 400 (Bad Request) if the new password is not strong enough
+ */
+ @PostMapping(path = "/account/change_password",
+ produces = MediaType.TEXT_PLAIN_VALUE)
+ @Timed
+ public ResponseEntity changePassword(@RequestBody String password) {
+ if (!checkPasswordLength(password)) {
+ return new ResponseEntity<>("Incorrect password", HttpStatus.BAD_REQUEST);
+ }
+ userService.changePassword(password);
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+
+ /**
+ * POST /account/reset_password/init : Send an e-mail to reset the password of the user
+ *
+ * @param mail the mail of the user
+ * @return the ResponseEntity with status 200 (OK) if the e-mail was sent, or status 400 (Bad Request) if the e-mail address is not registered
+ */
+ @PostMapping(path = "/account/reset_password/init",
+ produces = MediaType.TEXT_PLAIN_VALUE)
+ @Timed
+ public ResponseEntity requestPasswordReset(@RequestBody String mail) {
+ return userService.requestPasswordReset(mail)
+ .map(user -> {
+ mailService.sendPasswordResetMail(user);
+ return new ResponseEntity<>("e-mail was sent", HttpStatus.OK);
+ }).orElse(new ResponseEntity<>("e-mail address not registered", HttpStatus.BAD_REQUEST));
+ }
+
+ /**
+ * POST /account/reset_password/finish : Finish to reset the password of the user
+ *
+ * @param keyAndPassword the generated key and the new password
+ * @return the ResponseEntity with status 200 (OK) if the password has been reset,
+ * or status 400 (Bad Request) or 500 (Internal Server Error) if the password could not be reset
+ */
+ @PostMapping(path = "/account/reset_password/finish",
+ produces = MediaType.TEXT_PLAIN_VALUE)
+ @Timed
+ public ResponseEntity finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) {
+ if (!checkPasswordLength(keyAndPassword.getNewPassword())) {
+ return new ResponseEntity<>("Incorrect password", HttpStatus.BAD_REQUEST);
+ }
+ return userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey())
+ .map(user -> new ResponseEntity(HttpStatus.OK))
+ .orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+
+ private boolean checkPasswordLength(String password) {
+ return !StringUtils.isEmpty(password) &&
+ password.length() >= ManagedUserVM.PASSWORD_MIN_LENGTH &&
+ password.length() <= ManagedUserVM.PASSWORD_MAX_LENGTH;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/AuditResource.java b/jhipster/src/main/java/com/baeldung/web/rest/AuditResource.java
new file mode 100644
index 000000000000..3101d134ed8e
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/AuditResource.java
@@ -0,0 +1,76 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.service.AuditEventService;
+import com.baeldung.web.rest.util.PaginationUtil;
+
+import io.github.jhipster.web.util.ResponseUtil;
+import io.swagger.annotations.ApiParam;
+import org.springframework.boot.actuate.audit.AuditEvent;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.net.URISyntaxException;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * REST controller for getting the audit events.
+ */
+@RestController
+@RequestMapping("/management/audits")
+public class AuditResource {
+
+ private final AuditEventService auditEventService;
+
+ public AuditResource(AuditEventService auditEventService) {
+ this.auditEventService = auditEventService;
+ }
+
+ /**
+ * GET /audits : get a page of AuditEvents.
+ *
+ * @param pageable the pagination information
+ * @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body
+ */
+ @GetMapping
+ public ResponseEntity> getAll(@ApiParam Pageable pageable) {
+ Page page = auditEventService.findAll(pageable);
+ HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits");
+ return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
+ }
+
+ /**
+ * GET /audits : get a page of AuditEvents between the fromDate and toDate.
+ *
+ * @param fromDate the start of the time period of AuditEvents to get
+ * @param toDate the end of the time period of AuditEvents to get
+ * @param pageable the pagination information
+ * @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body
+ */
+
+ @GetMapping(params = {"fromDate", "toDate"})
+ public ResponseEntity> getByDates(
+ @RequestParam(value = "fromDate") LocalDate fromDate,
+ @RequestParam(value = "toDate") LocalDate toDate,
+ @ApiParam Pageable pageable) {
+
+ Page page = auditEventService.findByDates(fromDate.atTime(0, 0), toDate.atTime(23, 59), pageable);
+ HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits");
+ return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
+ }
+
+ /**
+ * GET /audits/:id : get an AuditEvent by id.
+ *
+ * @param id the id of the entity to get
+ * @return the ResponseEntity with status 200 (OK) and the AuditEvent in body, or status 404 (Not Found)
+ */
+ @GetMapping("/{id:.+}")
+ public ResponseEntity get(@PathVariable Long id) {
+ return ResponseUtil.wrapOrNotFound(auditEventService.find(id));
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/CommentResource.java b/jhipster/src/main/java/com/baeldung/web/rest/CommentResource.java
new file mode 100644
index 000000000000..3e3f13b5e98a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/CommentResource.java
@@ -0,0 +1,129 @@
+package com.baeldung.web.rest;
+
+import com.codahale.metrics.annotation.Timed;
+import com.baeldung.domain.Comment;
+
+import com.baeldung.repository.CommentRepository;
+import com.baeldung.web.rest.util.HeaderUtil;
+import com.baeldung.web.rest.util.PaginationUtil;
+import io.swagger.annotations.ApiParam;
+import io.github.jhipster.web.util.ResponseUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * REST controller for managing Comment.
+ */
+@RestController
+@RequestMapping("/api")
+public class CommentResource {
+
+ private final Logger log = LoggerFactory.getLogger(CommentResource.class);
+
+ private static final String ENTITY_NAME = "comment";
+
+ private final CommentRepository commentRepository;
+
+ public CommentResource(CommentRepository commentRepository) {
+ this.commentRepository = commentRepository;
+ }
+
+ /**
+ * POST /comments : Create a new comment.
+ *
+ * @param comment the comment to create
+ * @return the ResponseEntity with status 201 (Created) and with body the new comment, or with status 400 (Bad Request) if the comment has already an ID
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ @PostMapping("/comments")
+ @Timed
+ public ResponseEntity createComment(@Valid @RequestBody Comment comment) throws URISyntaxException {
+ log.debug("REST request to save Comment : {}", comment);
+ if (comment.getId() != null) {
+ return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new comment cannot already have an ID")).body(null);
+ }
+ Comment result = commentRepository.save(comment);
+ return ResponseEntity.created(new URI("/api/comments/" + result.getId()))
+ .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
+ .body(result);
+ }
+
+ /**
+ * PUT /comments : Updates an existing comment.
+ *
+ * @param comment the comment to update
+ * @return the ResponseEntity with status 200 (OK) and with body the updated comment,
+ * or with status 400 (Bad Request) if the comment is not valid,
+ * or with status 500 (Internal Server Error) if the comment couldnt be updated
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ @PutMapping("/comments")
+ @Timed
+ public ResponseEntity updateComment(@Valid @RequestBody Comment comment) throws URISyntaxException {
+ log.debug("REST request to update Comment : {}", comment);
+ if (comment.getId() == null) {
+ return createComment(comment);
+ }
+ Comment result = commentRepository.save(comment);
+ return ResponseEntity.ok()
+ .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, comment.getId().toString()))
+ .body(result);
+ }
+
+ /**
+ * GET /comments : get all the comments.
+ *
+ * @param pageable the pagination information
+ * @return the ResponseEntity with status 200 (OK) and the list of comments in body
+ * @throws URISyntaxException if there is an error to generate the pagination HTTP headers
+ */
+ @GetMapping("/comments")
+ @Timed
+ public ResponseEntity> getAllComments(@ApiParam Pageable pageable) {
+ log.debug("REST request to get a page of Comments");
+ Page page = commentRepository.findAll(pageable);
+ HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/comments");
+ return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
+ }
+
+ /**
+ * GET /comments/:id : get the "id" comment.
+ *
+ * @param id the id of the comment to retrieve
+ * @return the ResponseEntity with status 200 (OK) and with body the comment, or with status 404 (Not Found)
+ */
+ @GetMapping("/comments/{id}")
+ @Timed
+ public ResponseEntity getComment(@PathVariable Long id) {
+ log.debug("REST request to get Comment : {}", id);
+ Comment comment = commentRepository.findOne(id);
+ return ResponseUtil.wrapOrNotFound(Optional.ofNullable(comment));
+ }
+
+ /**
+ * DELETE /comments/:id : delete the "id" comment.
+ *
+ * @param id the id of the comment to delete
+ * @return the ResponseEntity with status 200 (OK)
+ */
+ @DeleteMapping("/comments/{id}")
+ @Timed
+ public ResponseEntity deleteComment(@PathVariable Long id) {
+ log.debug("REST request to delete Comment : {}", id);
+ commentRepository.delete(id);
+ return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
+ }
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/JWTToken.java b/jhipster/src/main/java/com/baeldung/web/rest/JWTToken.java
new file mode 100644
index 000000000000..c0804851f318
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/JWTToken.java
@@ -0,0 +1,24 @@
+package com.baeldung.web.rest;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Object to return as body in JWT Authentication.
+ */
+public class JWTToken {
+
+ private String idToken;
+
+ public JWTToken(String idToken) {
+ this.idToken = idToken;
+ }
+
+ @JsonProperty("id_token")
+ public String getIdToken() {
+ return idToken;
+ }
+
+ public void setIdToken(String idToken) {
+ this.idToken = idToken;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/LogsResource.java b/jhipster/src/main/java/com/baeldung/web/rest/LogsResource.java
new file mode 100644
index 000000000000..3d556e2609fb
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/LogsResource.java
@@ -0,0 +1,39 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.web.rest.vm.LoggerVM;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import com.codahale.metrics.annotation.Timed;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Controller for view and managing Log Level at runtime.
+ */
+@RestController
+@RequestMapping("/management")
+public class LogsResource {
+
+ @GetMapping("/logs")
+ @Timed
+ public List getList() {
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ return context.getLoggerList()
+ .stream()
+ .map(LoggerVM::new)
+ .collect(Collectors.toList());
+ }
+
+ @PutMapping("/logs")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @Timed
+ public void changeLevel(@RequestBody LoggerVM jsonLogger) {
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ context.getLogger(jsonLogger.getName()).setLevel(Level.valueOf(jsonLogger.getLevel()));
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/PostResource.java b/jhipster/src/main/java/com/baeldung/web/rest/PostResource.java
new file mode 100644
index 000000000000..7505e1fa468d
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/PostResource.java
@@ -0,0 +1,129 @@
+package com.baeldung.web.rest;
+
+import com.codahale.metrics.annotation.Timed;
+import com.baeldung.domain.Post;
+
+import com.baeldung.repository.PostRepository;
+import com.baeldung.web.rest.util.HeaderUtil;
+import com.baeldung.web.rest.util.PaginationUtil;
+import io.swagger.annotations.ApiParam;
+import io.github.jhipster.web.util.ResponseUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * REST controller for managing Post.
+ */
+@RestController
+@RequestMapping("/api")
+public class PostResource {
+
+ private final Logger log = LoggerFactory.getLogger(PostResource.class);
+
+ private static final String ENTITY_NAME = "post";
+
+ private final PostRepository postRepository;
+
+ public PostResource(PostRepository postRepository) {
+ this.postRepository = postRepository;
+ }
+
+ /**
+ * POST /posts : Create a new post.
+ *
+ * @param post the post to create
+ * @return the ResponseEntity with status 201 (Created) and with body the new post, or with status 400 (Bad Request) if the post has already an ID
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ @PostMapping("/posts")
+ @Timed
+ public ResponseEntity createPost(@Valid @RequestBody Post post) throws URISyntaxException {
+ log.debug("REST request to save Post : {}", post);
+ if (post.getId() != null) {
+ return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new post cannot already have an ID")).body(null);
+ }
+ Post result = postRepository.save(post);
+ return ResponseEntity.created(new URI("/api/posts/" + result.getId()))
+ .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
+ .body(result);
+ }
+
+ /**
+ * PUT /posts : Updates an existing post.
+ *
+ * @param post the post to update
+ * @return the ResponseEntity with status 200 (OK) and with body the updated post,
+ * or with status 400 (Bad Request) if the post is not valid,
+ * or with status 500 (Internal Server Error) if the post couldnt be updated
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ @PutMapping("/posts")
+ @Timed
+ public ResponseEntity updatePost(@Valid @RequestBody Post post) throws URISyntaxException {
+ log.debug("REST request to update Post : {}", post);
+ if (post.getId() == null) {
+ return createPost(post);
+ }
+ Post result = postRepository.save(post);
+ return ResponseEntity.ok()
+ .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, post.getId().toString()))
+ .body(result);
+ }
+
+ /**
+ * GET /posts : get all the posts.
+ *
+ * @param pageable the pagination information
+ * @return the ResponseEntity with status 200 (OK) and the list of posts in body
+ * @throws URISyntaxException if there is an error to generate the pagination HTTP headers
+ */
+ @GetMapping("/posts")
+ @Timed
+ public ResponseEntity> getAllPosts(@ApiParam Pageable pageable) {
+ log.debug("REST request to get a page of Posts");
+ Page page = postRepository.findAll(pageable);
+ HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/posts");
+ return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
+ }
+
+ /**
+ * GET /posts/:id : get the "id" post.
+ *
+ * @param id the id of the post to retrieve
+ * @return the ResponseEntity with status 200 (OK) and with body the post, or with status 404 (Not Found)
+ */
+ @GetMapping("/posts/{id}")
+ @Timed
+ public ResponseEntity getPost(@PathVariable Long id) {
+ log.debug("REST request to get Post : {}", id);
+ Post post = postRepository.findOne(id);
+ return ResponseUtil.wrapOrNotFound(Optional.ofNullable(post));
+ }
+
+ /**
+ * DELETE /posts/:id : delete the "id" post.
+ *
+ * @param id the id of the post to delete
+ * @return the ResponseEntity with status 200 (OK)
+ */
+ @DeleteMapping("/posts/{id}")
+ @Timed
+ public ResponseEntity deletePost(@PathVariable Long id) {
+ log.debug("REST request to delete Post : {}", id);
+ postRepository.delete(id);
+ return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build();
+ }
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/ProfileInfoResource.java b/jhipster/src/main/java/com/baeldung/web/rest/ProfileInfoResource.java
new file mode 100644
index 000000000000..b5e26c828137
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/ProfileInfoResource.java
@@ -0,0 +1,69 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.config.DefaultProfileUtil;
+
+import io.github.jhipster.config.JHipsterProperties;
+
+import org.springframework.core.env.Environment;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Resource to return information about the currently running Spring profiles.
+ */
+@RestController
+@RequestMapping("/api")
+public class ProfileInfoResource {
+
+ private final Environment env;
+
+ private final JHipsterProperties jHipsterProperties;
+
+ public ProfileInfoResource(Environment env, JHipsterProperties jHipsterProperties) {
+ this.env = env;
+ this.jHipsterProperties = jHipsterProperties;
+ }
+
+ @GetMapping("/profile-info")
+ public ProfileInfoVM getActiveProfiles() {
+ String[] activeProfiles = DefaultProfileUtil.getActiveProfiles(env);
+ return new ProfileInfoVM(activeProfiles, getRibbonEnv(activeProfiles));
+ }
+
+ private String getRibbonEnv(String[] activeProfiles) {
+ String[] displayOnActiveProfiles = jHipsterProperties.getRibbon().getDisplayOnActiveProfiles();
+ if (displayOnActiveProfiles == null) {
+ return null;
+ }
+ List ribbonProfiles = new ArrayList<>(Arrays.asList(displayOnActiveProfiles));
+ List springBootProfiles = Arrays.asList(activeProfiles);
+ ribbonProfiles.retainAll(springBootProfiles);
+ if (!ribbonProfiles.isEmpty()) {
+ return ribbonProfiles.get(0);
+ }
+ return null;
+ }
+
+ class ProfileInfoVM {
+
+ private String[] activeProfiles;
+
+ private String ribbonEnv;
+
+ ProfileInfoVM(String[] activeProfiles, String ribbonEnv) {
+ this.activeProfiles = activeProfiles;
+ this.ribbonEnv = ribbonEnv;
+ }
+
+ public String[] getActiveProfiles() {
+ return activeProfiles;
+ }
+
+ public String getRibbonEnv() {
+ return ribbonEnv;
+ }
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/UserJWTController.java b/jhipster/src/main/java/com/baeldung/web/rest/UserJWTController.java
new file mode 100644
index 000000000000..02b77c51a6aa
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/UserJWTController.java
@@ -0,0 +1,60 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.security.jwt.JWTConfigurer;
+import com.baeldung.security.jwt.TokenProvider;
+import com.baeldung.web.rest.vm.LoginVM;
+
+import java.util.Collections;
+
+import com.codahale.metrics.annotation.Timed;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+@RestController
+@RequestMapping("/api")
+public class UserJWTController {
+
+ private final Logger log = LoggerFactory.getLogger(UserJWTController.class);
+
+ private final TokenProvider tokenProvider;
+
+ private final AuthenticationManager authenticationManager;
+
+ public UserJWTController(TokenProvider tokenProvider, AuthenticationManager authenticationManager) {
+ this.tokenProvider = tokenProvider;
+ this.authenticationManager = authenticationManager;
+ }
+
+ @PostMapping("/authenticate")
+ @Timed
+ public ResponseEntity authorize(@Valid @RequestBody LoginVM loginVM, HttpServletResponse response) {
+
+ UsernamePasswordAuthenticationToken authenticationToken =
+ new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());
+
+ try {
+ Authentication authentication = this.authenticationManager.authenticate(authenticationToken);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe();
+ String jwt = tokenProvider.createToken(authentication, rememberMe);
+ response.addHeader(JWTConfigurer.AUTHORIZATION_HEADER, "Bearer " + jwt);
+ return ResponseEntity.ok(new JWTToken(jwt));
+ } catch (AuthenticationException ae) {
+ log.trace("Authentication exception trace: {}", ae);
+ return new ResponseEntity<>(Collections.singletonMap("AuthenticationException",
+ ae.getLocalizedMessage()), HttpStatus.UNAUTHORIZED);
+ }
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/UserResource.java b/jhipster/src/main/java/com/baeldung/web/rest/UserResource.java
new file mode 100644
index 000000000000..296d3e30ba45
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/UserResource.java
@@ -0,0 +1,187 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.config.Constants;
+import com.codahale.metrics.annotation.Timed;
+import com.baeldung.domain.User;
+import com.baeldung.repository.UserRepository;
+import com.baeldung.security.AuthoritiesConstants;
+import com.baeldung.service.MailService;
+import com.baeldung.service.UserService;
+import com.baeldung.service.dto.UserDTO;
+import com.baeldung.web.rest.vm.ManagedUserVM;
+import com.baeldung.web.rest.util.HeaderUtil;
+import com.baeldung.web.rest.util.PaginationUtil;
+import io.github.jhipster.web.util.ResponseUtil;
+import io.swagger.annotations.ApiParam;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.web.bind.annotation.*;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+
+/**
+ * REST controller for managing users.
+ *
+ * This class accesses the User entity, and needs to fetch its collection of authorities.
+ *
+ * For a normal use-case, it would be better to have an eager relationship between User and Authority,
+ * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join
+ * which would be good for performance.
+ *
+ *
+ * We use a View Model and a DTO for 3 reasons:
+ *
+ * We want to keep a lazy association between the user and the authorities, because people will
+ * quite often do relationships with the user, and we don't want them to get the authorities all
+ * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users'
+ * application because of this use-case.
+ * Not having an outer join causes n+1 requests to the database. This is not a real issue as
+ * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests,
+ * but then all authorities come from the cache, so in fact it's much better than doing an outer join
+ * (which will get lots of data from the database, for each HTTP call).
+ * As this manages users, for security reasons, we'd rather have a DTO layer.
+ *
+ * Another option would be to have a specific JPA entity graph to handle this case.
+ */
+@RestController
+@RequestMapping("/api")
+public class UserResource {
+
+ private final Logger log = LoggerFactory.getLogger(UserResource.class);
+
+ private static final String ENTITY_NAME = "userManagement";
+
+ private final UserRepository userRepository;
+
+ private final MailService mailService;
+
+ private final UserService userService;
+
+ public UserResource(UserRepository userRepository, MailService mailService,
+ UserService userService) {
+
+ this.userRepository = userRepository;
+ this.mailService = mailService;
+ this.userService = userService;
+ }
+
+ /**
+ * POST /users : Creates a new user.
+ *
+ * Creates a new user if the login and email are not already used, and sends an
+ * mail with an activation link.
+ * The user needs to be activated on creation.
+ *
+ *
+ * @param managedUserVM the user to create
+ * @return the ResponseEntity with status 201 (Created) and with body the new user, or with status 400 (Bad Request) if the login or email is already in use
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ @PostMapping("/users")
+ @Timed
+ @Secured(AuthoritiesConstants.ADMIN)
+ public ResponseEntity createUser(@RequestBody ManagedUserVM managedUserVM) throws URISyntaxException {
+ log.debug("REST request to save User : {}", managedUserVM);
+
+ if (managedUserVM.getId() != null) {
+ return ResponseEntity.badRequest()
+ .headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new user cannot already have an ID"))
+ .body(null);
+ // Lowercase the user login before comparing with database
+ } else if (userRepository.findOneByLogin(managedUserVM.getLogin().toLowerCase()).isPresent()) {
+ return ResponseEntity.badRequest()
+ .headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "userexists", "Login already in use"))
+ .body(null);
+ } else if (userRepository.findOneByEmail(managedUserVM.getEmail()).isPresent()) {
+ return ResponseEntity.badRequest()
+ .headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "emailexists", "Email already in use"))
+ .body(null);
+ } else {
+ User newUser = userService.createUser(managedUserVM);
+ mailService.sendCreationEmail(newUser);
+ return ResponseEntity.created(new URI("/api/users/" + newUser.getLogin()))
+ .headers(HeaderUtil.createAlert( "userManagement.created", newUser.getLogin()))
+ .body(newUser);
+ }
+ }
+
+ /**
+ * PUT /users : Updates an existing User.
+ *
+ * @param managedUserVM the user to update
+ * @return the ResponseEntity with status 200 (OK) and with body the updated user,
+ * or with status 400 (Bad Request) if the login or email is already in use,
+ * or with status 500 (Internal Server Error) if the user couldn't be updated
+ */
+ @PutMapping("/users")
+ @Timed
+ @Secured(AuthoritiesConstants.ADMIN)
+ public ResponseEntity updateUser(@RequestBody ManagedUserVM managedUserVM) {
+ log.debug("REST request to update User : {}", managedUserVM);
+ Optional existingUser = userRepository.findOneByEmail(managedUserVM.getEmail());
+ if (existingUser.isPresent() && (!existingUser.get().getId().equals(managedUserVM.getId()))) {
+ return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "emailexists", "E-mail already in use")).body(null);
+ }
+ existingUser = userRepository.findOneByLogin(managedUserVM.getLogin().toLowerCase());
+ if (existingUser.isPresent() && (!existingUser.get().getId().equals(managedUserVM.getId()))) {
+ return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "userexists", "Login already in use")).body(null);
+ }
+ Optional updatedUser = userService.updateUser(managedUserVM);
+
+ return ResponseUtil.wrapOrNotFound(updatedUser,
+ HeaderUtil.createAlert("userManagement.updated", managedUserVM.getLogin()));
+ }
+
+ /**
+ * GET /users : get all users.
+ *
+ * @param pageable the pagination information
+ * @return the ResponseEntity with status 200 (OK) and with body all users
+ */
+ @GetMapping("/users")
+ @Timed
+ public ResponseEntity> getAllUsers(@ApiParam Pageable pageable) {
+ final Page page = userService.getAllManagedUsers(pageable);
+ HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/users");
+ return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
+ }
+
+ /**
+ * GET /users/:login : get the "login" user.
+ *
+ * @param login the login of the user to find
+ * @return the ResponseEntity with status 200 (OK) and with body the "login" user, or with status 404 (Not Found)
+ */
+ @GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
+ @Timed
+ public ResponseEntity getUser(@PathVariable String login) {
+ log.debug("REST request to get User : {}", login);
+ return ResponseUtil.wrapOrNotFound(
+ userService.getUserWithAuthoritiesByLogin(login)
+ .map(UserDTO::new));
+ }
+
+ /**
+ * DELETE /users/:login : delete the "login" User.
+ *
+ * @param login the login of the user to delete
+ * @return the ResponseEntity with status 200 (OK)
+ */
+ @DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
+ @Timed
+ @Secured(AuthoritiesConstants.ADMIN)
+ public ResponseEntity deleteUser(@PathVariable String login) {
+ log.debug("REST request to delete User: {}", login);
+ userService.deleteUser(login);
+ return ResponseEntity.ok().headers(HeaderUtil.createAlert( "userManagement.deleted", login)).build();
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/CustomParameterizedException.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/CustomParameterizedException.java
new file mode 100644
index 000000000000..ab7754476baf
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/CustomParameterizedException.java
@@ -0,0 +1,34 @@
+package com.baeldung.web.rest.errors;
+
+/**
+ * Custom, parameterized exception, which can be translated on the client side.
+ * For example:
+ *
+ *
+ * throw new CustomParameterizedException("myCustomError", "hello", "world");
+ *
+ *
+ * Can be translated with:
+ *
+ *
+ * "error.myCustomError" : "The server says {{params[0]}} to {{params[1]}}"
+ *
+ */
+public class CustomParameterizedException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String message;
+ private final String[] params;
+
+ public CustomParameterizedException(String message, String... params) {
+ super(message);
+ this.message = message;
+ this.params = params;
+ }
+
+ public ParameterizedErrorVM getErrorVM() {
+ return new ParameterizedErrorVM(message, params);
+ }
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorConstants.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorConstants.java
new file mode 100644
index 000000000000..69f21ed96c4b
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorConstants.java
@@ -0,0 +1,14 @@
+package com.baeldung.web.rest.errors;
+
+public final class ErrorConstants {
+
+ public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure";
+ public static final String ERR_ACCESS_DENIED = "error.accessDenied";
+ public static final String ERR_VALIDATION = "error.validation";
+ public static final String ERR_METHOD_NOT_SUPPORTED = "error.methodNotSupported";
+ public static final String ERR_INTERNAL_SERVER_ERROR = "error.internalServerError";
+
+ private ErrorConstants() {
+ }
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorVM.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorVM.java
new file mode 100644
index 000000000000..22fb06613588
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/ErrorVM.java
@@ -0,0 +1,52 @@
+package com.baeldung.web.rest.errors;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * View Model for transferring error message with a list of field errors.
+ */
+public class ErrorVM implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String message;
+ private final String description;
+
+ private List fieldErrors;
+
+ public ErrorVM(String message) {
+ this(message, null);
+ }
+
+ public ErrorVM(String message, String description) {
+ this.message = message;
+ this.description = description;
+ }
+
+ public ErrorVM(String message, String description, List fieldErrors) {
+ this.message = message;
+ this.description = description;
+ this.fieldErrors = fieldErrors;
+ }
+
+ public void add(String objectName, String field, String message) {
+ if (fieldErrors == null) {
+ fieldErrors = new ArrayList<>();
+ }
+ fieldErrors.add(new FieldErrorVM(objectName, field, message));
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public List getFieldErrors() {
+ return fieldErrors;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/ExceptionTranslator.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/ExceptionTranslator.java
new file mode 100644
index 000000000000..51925bfa6153
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/ExceptionTranslator.java
@@ -0,0 +1,85 @@
+package com.baeldung.web.rest.errors;
+
+import java.util.List;
+
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.dao.ConcurrencyFailureException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.ResponseEntity.BodyBuilder;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * Controller advice to translate the server side exceptions to client-friendly json structures.
+ */
+@ControllerAdvice
+public class ExceptionTranslator {
+
+ @ExceptionHandler(ConcurrencyFailureException.class)
+ @ResponseStatus(HttpStatus.CONFLICT)
+ @ResponseBody
+ public ErrorVM processConcurrencyError(ConcurrencyFailureException ex) {
+ return new ErrorVM(ErrorConstants.ERR_CONCURRENCY_FAILURE);
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ResponseBody
+ public ErrorVM processValidationError(MethodArgumentNotValidException ex) {
+ BindingResult result = ex.getBindingResult();
+ List fieldErrors = result.getFieldErrors();
+
+ return processFieldErrors(fieldErrors);
+ }
+
+ @ExceptionHandler(CustomParameterizedException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ResponseBody
+ public ParameterizedErrorVM processParameterizedValidationError(CustomParameterizedException ex) {
+ return ex.getErrorVM();
+ }
+
+ @ExceptionHandler(AccessDeniedException.class)
+ @ResponseStatus(HttpStatus.FORBIDDEN)
+ @ResponseBody
+ public ErrorVM processAccessDeniedException(AccessDeniedException e) {
+ return new ErrorVM(ErrorConstants.ERR_ACCESS_DENIED, e.getMessage());
+ }
+
+ private ErrorVM processFieldErrors(List fieldErrors) {
+ ErrorVM dto = new ErrorVM(ErrorConstants.ERR_VALIDATION);
+
+ for (FieldError fieldError : fieldErrors) {
+ dto.add(fieldError.getObjectName(), fieldError.getField(), fieldError.getCode());
+ }
+
+ return dto;
+ }
+
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ @ResponseBody
+ @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
+ public ErrorVM processMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) {
+ return new ErrorVM(ErrorConstants.ERR_METHOD_NOT_SUPPORTED, exception.getMessage());
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity processRuntimeException(Exception ex) {
+ BodyBuilder builder;
+ ErrorVM errorVM;
+ ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
+ if (responseStatus != null) {
+ builder = ResponseEntity.status(responseStatus.value());
+ errorVM = new ErrorVM("error." + responseStatus.value().value(), responseStatus.reason());
+ } else {
+ builder = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
+ errorVM = new ErrorVM(ErrorConstants.ERR_INTERNAL_SERVER_ERROR, "Internal server error");
+ }
+ return builder.body(errorVM);
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/FieldErrorVM.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/FieldErrorVM.java
new file mode 100644
index 000000000000..19ab640ec7e0
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/FieldErrorVM.java
@@ -0,0 +1,33 @@
+package com.baeldung.web.rest.errors;
+
+import java.io.Serializable;
+
+public class FieldErrorVM implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String objectName;
+
+ private final String field;
+
+ private final String message;
+
+ public FieldErrorVM(String dto, String field, String message) {
+ this.objectName = dto;
+ this.field = field;
+ this.message = message;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public String getField() {
+ return field;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/errors/ParameterizedErrorVM.java b/jhipster/src/main/java/com/baeldung/web/rest/errors/ParameterizedErrorVM.java
new file mode 100644
index 000000000000..18500c51af53
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/errors/ParameterizedErrorVM.java
@@ -0,0 +1,27 @@
+package com.baeldung.web.rest.errors;
+
+import java.io.Serializable;
+
+/**
+ * View Model for sending a parameterized error message.
+ */
+public class ParameterizedErrorVM implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private final String message;
+ private final String[] params;
+
+ public ParameterizedErrorVM(String message, String... params) {
+ this.message = message;
+ this.params = params;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String[] getParams() {
+ return params;
+ }
+
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/package-info.java b/jhipster/src/main/java/com/baeldung/web/rest/package-info.java
new file mode 100644
index 000000000000..0a74f6e90c48
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Spring MVC REST controllers.
+ */
+package com.baeldung.web.rest;
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/util/HeaderUtil.java b/jhipster/src/main/java/com/baeldung/web/rest/util/HeaderUtil.java
new file mode 100644
index 000000000000..7c643e9323b8
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/util/HeaderUtil.java
@@ -0,0 +1,45 @@
+package com.baeldung.web.rest.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+
+/**
+ * Utility class for HTTP headers creation.
+ */
+public final class HeaderUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(HeaderUtil.class);
+
+ private static final String APPLICATION_NAME = "baeldungApp";
+
+ private HeaderUtil() {
+ }
+
+ public static HttpHeaders createAlert(String message, String param) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("X-baeldungApp-alert", message);
+ headers.add("X-baeldungApp-params", param);
+ return headers;
+ }
+
+ public static HttpHeaders createEntityCreationAlert(String entityName, String param) {
+ return createAlert(APPLICATION_NAME + "." + entityName + ".created", param);
+ }
+
+ public static HttpHeaders createEntityUpdateAlert(String entityName, String param) {
+ return createAlert(APPLICATION_NAME + "." + entityName + ".updated", param);
+ }
+
+ public static HttpHeaders createEntityDeletionAlert(String entityName, String param) {
+ return createAlert(APPLICATION_NAME + "." + entityName + ".deleted", param);
+ }
+
+ public static HttpHeaders createFailureAlert(String entityName, String errorKey, String defaultMessage) {
+ log.error("Entity creation failed, {}", defaultMessage);
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("X-baeldungApp-error", "error." + errorKey);
+ headers.add("X-baeldungApp-params", entityName);
+ return headers;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/util/PaginationUtil.java b/jhipster/src/main/java/com/baeldung/web/rest/util/PaginationUtil.java
new file mode 100644
index 000000000000..affcb7842c48
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/util/PaginationUtil.java
@@ -0,0 +1,47 @@
+package com.baeldung.web.rest.util;
+
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.net.URISyntaxException;
+
+/**
+ * Utility class for handling pagination.
+ *
+ *
+ * Pagination uses the same principles as the Github API ,
+ * and follow RFC 5988 (Link header) .
+ */
+public final class PaginationUtil {
+
+ private PaginationUtil() {
+ }
+
+ public static HttpHeaders generatePaginationHttpHeaders(Page page, String baseUrl) {
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("X-Total-Count", "" + Long.toString(page.getTotalElements()));
+ String link = "";
+ if ((page.getNumber() + 1) < page.getTotalPages()) {
+ link = "<" + generateUri(baseUrl, page.getNumber() + 1, page.getSize()) + ">; rel=\"next\",";
+ }
+ // prev link
+ if ((page.getNumber()) > 0) {
+ link += "<" + generateUri(baseUrl, page.getNumber() - 1, page.getSize()) + ">; rel=\"prev\",";
+ }
+ // last and first link
+ int lastPage = 0;
+ if (page.getTotalPages() > 0) {
+ lastPage = page.getTotalPages() - 1;
+ }
+ link += "<" + generateUri(baseUrl, lastPage, page.getSize()) + ">; rel=\"last\",";
+ link += "<" + generateUri(baseUrl, 0, page.getSize()) + ">; rel=\"first\"";
+ headers.add(HttpHeaders.LINK, link);
+ return headers;
+ }
+
+ private static String generateUri(String baseUrl, int page, int size) {
+ return UriComponentsBuilder.fromUriString(baseUrl).queryParam("page", page).queryParam("size", size).toUriString();
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/KeyAndPasswordVM.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/KeyAndPasswordVM.java
new file mode 100644
index 000000000000..94465f309fa9
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/KeyAndPasswordVM.java
@@ -0,0 +1,27 @@
+package com.baeldung.web.rest.vm;
+
+/**
+ * View Model object for storing the user's key and password.
+ */
+public class KeyAndPasswordVM {
+
+ private String key;
+
+ private String newPassword;
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getNewPassword() {
+ return newPassword;
+ }
+
+ public void setNewPassword(String newPassword) {
+ this.newPassword = newPassword;
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/LoggerVM.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/LoggerVM.java
new file mode 100644
index 000000000000..56f77aec85ab
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/LoggerVM.java
@@ -0,0 +1,48 @@
+package com.baeldung.web.rest.vm;
+
+import ch.qos.logback.classic.Logger;
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+/**
+ * View Model object for storing a Logback logger.
+ */
+public class LoggerVM {
+
+ private String name;
+
+ private String level;
+
+ public LoggerVM(Logger logger) {
+ this.name = logger.getName();
+ this.level = logger.getEffectiveLevel().toString();
+ }
+
+ @JsonCreator
+ public LoggerVM() {
+ // Empty public constructor used by Jackson.
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getLevel() {
+ return level;
+ }
+
+ public void setLevel(String level) {
+ this.level = level;
+ }
+
+ @Override
+ public String toString() {
+ return "LoggerVM{" +
+ "name='" + name + '\'' +
+ ", level='" + level + '\'' +
+ '}';
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/LoginVM.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/LoginVM.java
new file mode 100644
index 000000000000..a910d0d59c85
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/LoginVM.java
@@ -0,0 +1,55 @@
+package com.baeldung.web.rest.vm;
+
+import com.baeldung.config.Constants;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * View Model object for storing a user's credentials.
+ */
+public class LoginVM {
+
+ @Pattern(regexp = Constants.LOGIN_REGEX)
+ @NotNull
+ @Size(min = 1, max = 50)
+ private String username;
+
+ @NotNull
+ @Size(min = ManagedUserVM.PASSWORD_MIN_LENGTH, max = ManagedUserVM.PASSWORD_MAX_LENGTH)
+ private String password;
+
+ private Boolean rememberMe;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Boolean isRememberMe() {
+ return rememberMe;
+ }
+
+ public void setRememberMe(Boolean rememberMe) {
+ this.rememberMe = rememberMe;
+ }
+
+ @Override
+ public String toString() {
+ return "LoginVM{" +
+ "username='" + username + '\'' +
+ ", rememberMe=" + rememberMe +
+ '}';
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/ManagedUserVM.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/ManagedUserVM.java
new file mode 100644
index 000000000000..0899f0d0aa6a
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/ManagedUserVM.java
@@ -0,0 +1,45 @@
+package com.baeldung.web.rest.vm;
+
+import com.baeldung.service.dto.UserDTO;
+import javax.validation.constraints.Size;
+
+import java.time.ZonedDateTime;
+import java.util.Set;
+
+/**
+ * View Model extending the UserDTO, which is meant to be used in the user management UI.
+ */
+public class ManagedUserVM extends UserDTO {
+
+ public static final int PASSWORD_MIN_LENGTH = 4;
+
+ public static final int PASSWORD_MAX_LENGTH = 100;
+
+ @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH)
+ private String password;
+
+ public ManagedUserVM() {
+ // Empty constructor needed for Jackson.
+ }
+
+ public ManagedUserVM(Long id, String login, String password, String firstName, String lastName,
+ String email, boolean activated, String imageUrl, String langKey,
+ String createdBy, ZonedDateTime createdDate, String lastModifiedBy, ZonedDateTime lastModifiedDate,
+ Set authorities) {
+
+ super(id, login, firstName, lastName, email, activated, imageUrl, langKey,
+ createdBy, createdDate, lastModifiedBy, lastModifiedDate, authorities);
+
+ this.password = password;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String toString() {
+ return "ManagedUserVM{" +
+ "} " + super.toString();
+ }
+}
diff --git a/jhipster/src/main/java/com/baeldung/web/rest/vm/package-info.java b/jhipster/src/main/java/com/baeldung/web/rest/vm/package-info.java
new file mode 100644
index 000000000000..8d56157b6ac6
--- /dev/null
+++ b/jhipster/src/main/java/com/baeldung/web/rest/vm/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * View Models used by Spring MVC REST controllers.
+ */
+package com.baeldung.web.rest.vm;
diff --git a/jhipster/src/main/resources/.h2.server.properties b/jhipster/src/main/resources/.h2.server.properties
new file mode 100644
index 000000000000..f8b442990280
--- /dev/null
+++ b/jhipster/src/main/resources/.h2.server.properties
@@ -0,0 +1,5 @@
+#H2 Server Properties
+0=JHipster H2 (Disk)|org.h2.Driver|jdbc\:h2\:file\:./target/h2db/db/baeldung|baeldung
+webAllowOthers=true
+webPort=8082
+webSSL=false
diff --git a/jhipster/src/main/resources/banner.txt b/jhipster/src/main/resources/banner.txt
new file mode 100644
index 000000000000..c3d8cf725ddc
--- /dev/null
+++ b/jhipster/src/main/resources/banner.txt
@@ -0,0 +1,10 @@
+
+ ${AnsiColor.GREEN} ██╗${AnsiColor.RED} ██╗ ██╗ ████████╗ ███████╗ ██████╗ ████████╗ ████████╗ ███████╗
+ ${AnsiColor.GREEN} ██║${AnsiColor.RED} ██║ ██║ ╚══██╔══╝ ██╔═══██╗ ██╔════╝ ╚══██╔══╝ ██╔═════╝ ██╔═══██╗
+ ${AnsiColor.GREEN} ██║${AnsiColor.RED} ████████║ ██║ ███████╔╝ ╚█████╗ ██║ ██████╗ ███████╔╝
+ ${AnsiColor.GREEN}██╗ ██║${AnsiColor.RED} ██╔═══██║ ██║ ██╔════╝ ╚═══██╗ ██║ ██╔═══╝ ██╔══██║
+ ${AnsiColor.GREEN}╚██████╔╝${AnsiColor.RED} ██║ ██║ ████████╗ ██║ ██████╔╝ ██║ ████████╗ ██║ ╚██╗
+ ${AnsiColor.GREEN} ╚═════╝ ${AnsiColor.RED} ╚═╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═╝
+
+${AnsiColor.BRIGHT_BLUE}:: JHipster 🤓 :: Running Spring Boot ${spring-boot.version} ::
+:: http://jhipster.github.io ::${AnsiColor.DEFAULT}
diff --git a/jhipster/src/main/resources/config/application-dev.yml b/jhipster/src/main/resources/config/application-dev.yml
new file mode 100644
index 000000000000..d75c2549c872
--- /dev/null
+++ b/jhipster/src/main/resources/config/application-dev.yml
@@ -0,0 +1,130 @@
+# ===================================================================
+# Spring Boot configuration for the "dev" profile.
+#
+# This configuration overrides the application.yml file.
+#
+# More information on profiles: https://jhipster.github.io/profiles/
+# More information on configuration properties: https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+# ===================================================================
+# Standard Spring Boot properties.
+# Full reference is available at:
+# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+# ===================================================================
+
+spring:
+ profiles:
+ active: dev
+ include: swagger
+ devtools:
+ restart:
+ enabled: true
+ livereload:
+ enabled: false # we use gulp + BrowserSync for livereload
+ jackson:
+ serialization.indent_output: true
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ url: jdbc:h2:file:./target/h2db/db/baeldung;DB_CLOSE_DELAY=-1
+ username: baeldung
+ password:
+ h2:
+ console:
+ enabled: false
+ jpa:
+ database-platform: io.github.jhipster.domain.util.FixedH2Dialect
+ database: H2
+ show-sql: true
+ properties:
+ hibernate.id.new_generator_mappings: true
+ hibernate.cache.use_second_level_cache: true
+ hibernate.cache.use_query_cache: false
+ hibernate.generate_statistics: true
+ hibernate.cache.region.factory_class: io.github.jhipster.config.jcache.NoDefaultJCacheRegionFactory
+ mail:
+ host: localhost
+ port: 25
+ username:
+ password:
+ messages:
+ cache-seconds: 1
+ thymeleaf:
+ cache: false
+
+liquibase:
+ contexts: dev
+
+# ===================================================================
+# To enable SSL, generate a certificate using:
+# keytool -genkey -alias baeldung -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
+#
+# You can also use Let's Encrypt:
+# https://maximilian-boehm.com/hp2121/Create-a-Java-Keystore-JKS-from-Let-s-Encrypt-Certificates.htm
+#
+# Then, modify the server.ssl properties so your "server" configuration looks like:
+#
+# server:
+# port: 8443
+# ssl:
+# key-store: keystore.p12
+# key-store-password:
+# keyStoreType: PKCS12
+# keyAlias: baeldung
+# ===================================================================
+server:
+ port: 8080
+
+# ===================================================================
+# JHipster specific properties
+#
+# Full reference is available at: https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+jhipster:
+ http:
+ version: V_1_1 # To use HTTP/2 you will need SSL support (see above the "server.ssl" configuration)
+ cache: # Cache configuration
+ ehcache: # Ehcache configuration
+ time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache
+ max-entries: 100 # Number of objects in each cache entry
+ security:
+ authentication:
+ jwt:
+ secret: my-secret-token-to-change-in-production
+ # Token is valid 24 hours
+ token-validity-in-seconds: 86400
+ token-validity-in-seconds-for-remember-me: 2592000
+ mail: # specific JHipster mail property, for standard properties see MailProperties
+ from: baeldung@localhost
+ base-url: http://127.0.0.1:8080
+ metrics: # DropWizard Metrics configuration, used by MetricsConfiguration
+ jmx.enabled: true
+ graphite: # Use the "graphite" Maven profile to have the Graphite dependencies
+ enabled: false
+ host: localhost
+ port: 2003
+ prefix: baeldung
+ prometheus: # Use the "prometheus" Maven profile to have the Prometheus dependencies
+ enabled: false
+ endpoint: /prometheusMetrics
+ logs: # Reports Dropwizard metrics in the logs
+ enabled: false
+ reportFrequency: 60 # in seconds
+ logging:
+ logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration
+ enabled: false
+ host: localhost
+ port: 5000
+ queue-size: 512
+
+# ===================================================================
+# Application specific properties
+# Add your own application properties here, see the ApplicationProperties class
+# to have type-safe configuration, like in the JHipsterProperties above
+#
+# More documentation is available at:
+# https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+application:
diff --git a/jhipster/src/main/resources/config/application-prod.yml b/jhipster/src/main/resources/config/application-prod.yml
new file mode 100644
index 000000000000..a46fbd1c7365
--- /dev/null
+++ b/jhipster/src/main/resources/config/application-prod.yml
@@ -0,0 +1,132 @@
+# ===================================================================
+# Spring Boot configuration for the "prod" profile.
+#
+# This configuration overrides the application.yml file.
+#
+# More information on profiles: https://jhipster.github.io/profiles/
+# More information on configuration properties: https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+# ===================================================================
+# Standard Spring Boot properties.
+# Full reference is available at:
+# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+# ===================================================================
+
+spring:
+ devtools:
+ restart:
+ enabled: false
+ livereload:
+ enabled: false
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ url: jdbc:mysql://localhost:3306/baeldung?useUnicode=true&characterEncoding=utf8&useSSL=false
+ username: root
+ password:
+ hikari:
+ data-source-properties:
+ cachePrepStmts: true
+ prepStmtCacheSize: 250
+ prepStmtCacheSqlLimit: 2048
+ useServerPrepStmts: true
+ jpa:
+ database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
+ database: MYSQL
+ show-sql: false
+ properties:
+ hibernate.id.new_generator_mappings: true
+ hibernate.cache.use_second_level_cache: true
+ hibernate.cache.use_query_cache: false
+ hibernate.generate_statistics: false
+ hibernate.cache.region.factory_class: io.github.jhipster.config.jcache.NoDefaultJCacheRegionFactory
+ mail:
+ host: localhost
+ port: 25
+ username:
+ password:
+ thymeleaf:
+ cache: true
+
+liquibase:
+ contexts: prod
+
+# ===================================================================
+# To enable SSL, generate a certificate using:
+# keytool -genkey -alias baeldung -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
+#
+# You can also use Let's Encrypt:
+# https://maximilian-boehm.com/hp2121/Create-a-Java-Keystore-JKS-from-Let-s-Encrypt-Certificates.htm
+#
+# Then, modify the server.ssl properties so your "server" configuration looks like:
+#
+# server:
+# port: 443
+# ssl:
+# key-store: keystore.p12
+# key-store-password:
+# keyStoreType: PKCS12
+# keyAlias: baeldung
+# ===================================================================
+server:
+ port: 8080
+ compression:
+ enabled: true
+ mime-types: text/html,text/xml,text/plain,text/css, application/javascript, application/json
+ min-response-size: 1024
+
+# ===================================================================
+# JHipster specific properties
+#
+# Full reference is available at: https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+jhipster:
+ http:
+ version: V_1_1 # To use HTTP/2 you will need SSL support (see above the "server.ssl" configuration)
+ cache: # Used by the CachingHttpHeadersFilter
+ timeToLiveInDays: 1461
+ cache: # Cache configuration
+ ehcache: # Ehcache configuration
+ time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache
+ max-entries: 1000 # Number of objects in each cache entry
+ security:
+ authentication:
+ jwt:
+ secret: e1d4b69d3f953e3fa622121e882e6f459ca20ca4
+ # Token is valid 24 hours
+ token-validity-in-seconds: 86400
+ token-validity-in-seconds-for-remember-me: 2592000
+ mail: # specific JHipster mail property, for standard properties see MailProperties
+ from: baeldung@localhost
+ base-url: http://my-server-url-to-change # Modify according to your server's URL
+ metrics: # DropWizard Metrics configuration, used by MetricsConfiguration
+ jmx.enabled: true
+ graphite:
+ enabled: false
+ host: localhost
+ port: 2003
+ prefix: baeldung
+ prometheus:
+ enabled: false
+ endpoint: /prometheusMetrics
+ logs: # Reports Dropwizard metrics in the logs
+ enabled: false
+ reportFrequency: 60 # in seconds
+ logging:
+ logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration
+ enabled: false
+ host: localhost
+ port: 5000
+ queue-size: 512
+
+# ===================================================================
+# Application specific properties
+# Add your own application properties here, see the ApplicationProperties class
+# to have type-safe configuration, like in the JHipsterProperties above
+#
+# More documentation is available at:
+# https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+application:
diff --git a/jhipster/src/main/resources/config/application.yml b/jhipster/src/main/resources/config/application.yml
new file mode 100644
index 000000000000..ed48baf853dd
--- /dev/null
+++ b/jhipster/src/main/resources/config/application.yml
@@ -0,0 +1,106 @@
+# ===================================================================
+# Spring Boot configuration.
+#
+# This configuration will be overriden by the Spring profile you use,
+# for example application-dev.yml if you use the "dev" profile.
+#
+# More information on profiles: https://jhipster.github.io/profiles/
+# More information on configuration properties: https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+# ===================================================================
+# Standard Spring Boot properties.
+# Full reference is available at:
+# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+# ===================================================================
+
+management:
+ security:
+ roles: ADMIN
+ context-path: /management
+ health:
+ mail:
+ enabled: false # When using the MailService, configure an SMTP server and set this to true
+spring:
+ application:
+ name: baeldung
+ profiles:
+ # The commented value for `active` can be replaced with valid Spring profiles to load.
+ # Otherwise, it will be filled in by maven when building the WAR file
+ # Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS`
+ active: #spring.profiles.active#
+ jackson:
+ serialization.write_dates_as_timestamps: false
+ jpa:
+ open-in-view: false
+ hibernate:
+ ddl-auto: none
+ naming:
+ physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
+ implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+ messages:
+ basename: i18n/messages
+ mvc:
+ favicon:
+ enabled: false
+ thymeleaf:
+ mode: XHTML
+
+security:
+ basic:
+ enabled: false
+
+server:
+ session:
+ cookie:
+ http-only: true
+
+info:
+ project:
+ version: #project.version#
+
+# ===================================================================
+# JHipster specific properties
+#
+# Full reference is available at: https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+jhipster:
+ async:
+ core-pool-size: 2
+ max-pool-size: 50
+ queue-capacity: 10000
+ # By default CORS is disabled. Uncomment to enable.
+ #cors:
+ #allowed-origins: "*"
+ #allowed-methods: GET, PUT, POST, DELETE, OPTIONS
+ #allowed-headers: "*"
+ #exposed-headers:
+ #allow-credentials: true
+ #max-age: 1800
+ mail:
+ from: baeldung@localhost
+ swagger:
+ default-include-pattern: /api/.*
+ title: baeldung API
+ description: baeldung API documentation
+ version: 0.0.1
+ terms-of-service-url:
+ contact-name:
+ contact-url:
+ contact-email:
+ license:
+ license-url:
+ ribbon:
+ display-on-active-profiles: dev
+
+# ===================================================================
+# Application specific properties
+# Add your own application properties here, see the ApplicationProperties class
+# to have type-safe configuration, like in the JHipsterProperties above
+#
+# More documentation is available at:
+# https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+application:
diff --git a/jhipster/src/main/resources/config/liquibase/authorities.csv b/jhipster/src/main/resources/config/liquibase/authorities.csv
new file mode 100644
index 000000000000..af5c6dfa186d
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/authorities.csv
@@ -0,0 +1,3 @@
+name
+ROLE_ADMIN
+ROLE_USER
diff --git a/jhipster/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/jhipster/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml
new file mode 100644
index 000000000000..98ac548808ef
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_Post.xml b/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_Post.xml
new file mode 100644
index 000000000000..ce4773262c6e
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_Post.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_constraints_Post.xml b/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_constraints_Post.xml
new file mode 100644
index 000000000000..204098037184
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/changelog/20170316223211_added_entity_constraints_Post.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_Comment.xml b/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_Comment.xml
new file mode 100644
index 000000000000..d0b26503e113
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_Comment.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_constraints_Comment.xml b/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_constraints_Comment.xml
new file mode 100644
index 000000000000..b7670e747cc2
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/changelog/20170316224021_added_entity_constraints_Comment.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/resources/config/liquibase/master.xml b/jhipster/src/main/resources/config/liquibase/master.xml
new file mode 100644
index 000000000000..32eb4799897d
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/master.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/resources/config/liquibase/users.csv b/jhipster/src/main/resources/config/liquibase/users.csv
new file mode 100644
index 000000000000..b25922b69906
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/users.csv
@@ -0,0 +1,5 @@
+id;login;password_hash;first_name;last_name;email;image_url;activated;lang_key;created_by;last_modified_by
+1;system;$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG;System;System;system@localhost;;true;en;system;system
+2;anonymoususer;$2a$10$j8S5d7Sr7.8VTOYNviDPOeWX8KcYILUVJBsYV83Y5NtECayypx9lO;Anonymous;User;anonymous@localhost;;true;en;system;system
+3;admin;$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC;Administrator;Administrator;admin@localhost;;true;en;system;system
+4;user;$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K;User;User;user@localhost;;true;en;system;system
diff --git a/jhipster/src/main/resources/config/liquibase/users_authorities.csv b/jhipster/src/main/resources/config/liquibase/users_authorities.csv
new file mode 100644
index 000000000000..06c5feeeeace
--- /dev/null
+++ b/jhipster/src/main/resources/config/liquibase/users_authorities.csv
@@ -0,0 +1,6 @@
+user_id;authority_name
+1;ROLE_ADMIN
+1;ROLE_USER
+3;ROLE_ADMIN
+3;ROLE_USER
+4;ROLE_USER
diff --git a/jhipster/src/main/resources/i18n/messages.properties b/jhipster/src/main/resources/i18n/messages.properties
new file mode 100644
index 000000000000..1c28002acd9d
--- /dev/null
+++ b/jhipster/src/main/resources/i18n/messages.properties
@@ -0,0 +1,22 @@
+# Error page
+error.title=Your request cannot be processed
+error.subtitle=Sorry, an error has occurred.
+error.status=Status:
+error.message=Message:
+
+# Activation e-mail
+email.activation.title=baeldung account activation
+email.activation.greeting=Dear {0}
+email.activation.text1=Your baeldung account has been created, please click on the URL below to activate it:
+email.activation.text2=Regards,
+email.signature=baeldung Team.
+
+# Creation email
+email.creation.text1=Your baeldung account has been created, please click on the URL below to access it:
+
+# Reset e-mail
+email.reset.title=baeldung password reset
+email.reset.greeting=Dear {0}
+email.reset.text1=For your baeldung account a password reset was requested, please click on the URL below to reset it:
+email.reset.text2=Regards,
+
diff --git a/jhipster/src/main/resources/i18n/messages_en.properties b/jhipster/src/main/resources/i18n/messages_en.properties
new file mode 100644
index 000000000000..1c28002acd9d
--- /dev/null
+++ b/jhipster/src/main/resources/i18n/messages_en.properties
@@ -0,0 +1,22 @@
+# Error page
+error.title=Your request cannot be processed
+error.subtitle=Sorry, an error has occurred.
+error.status=Status:
+error.message=Message:
+
+# Activation e-mail
+email.activation.title=baeldung account activation
+email.activation.greeting=Dear {0}
+email.activation.text1=Your baeldung account has been created, please click on the URL below to activate it:
+email.activation.text2=Regards,
+email.signature=baeldung Team.
+
+# Creation email
+email.creation.text1=Your baeldung account has been created, please click on the URL below to access it:
+
+# Reset e-mail
+email.reset.title=baeldung password reset
+email.reset.greeting=Dear {0}
+email.reset.text1=For your baeldung account a password reset was requested, please click on the URL below to reset it:
+email.reset.text2=Regards,
+
diff --git a/jhipster/src/main/resources/logback-spring.xml b/jhipster/src/main/resources/logback-spring.xml
new file mode 100644
index 000000000000..3c62a70c310e
--- /dev/null
+++ b/jhipster/src/main/resources/logback-spring.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/resources/mails/activationEmail.html b/jhipster/src/main/resources/mails/activationEmail.html
new file mode 100644
index 000000000000..9fb22a779685
--- /dev/null
+++ b/jhipster/src/main/resources/mails/activationEmail.html
@@ -0,0 +1,24 @@
+
+
+
+ JHipster activation
+
+
+
+
+ Dear
+
+
+ Your JHipster account has been created, please click on the URL below to activate it:
+
+
+ Activation Link
+
+
+ Regards,
+
+ JHipster.
+
+
+
diff --git a/jhipster/src/main/resources/mails/creationEmail.html b/jhipster/src/main/resources/mails/creationEmail.html
new file mode 100644
index 000000000000..a59d91da0c4b
--- /dev/null
+++ b/jhipster/src/main/resources/mails/creationEmail.html
@@ -0,0 +1,24 @@
+
+
+
+ JHipster creation
+
+
+
+
+ Dear
+
+
+ Your JHipster account has been created, please click on the URL below to access it:
+
+
+ login
+
+
+ Regards,
+
+ JHipster.
+
+
+
diff --git a/jhipster/src/main/resources/mails/passwordResetEmail.html b/jhipster/src/main/resources/mails/passwordResetEmail.html
new file mode 100644
index 000000000000..30d5f62c60cb
--- /dev/null
+++ b/jhipster/src/main/resources/mails/passwordResetEmail.html
@@ -0,0 +1,24 @@
+
+
+
+ JHipster password reset
+
+
+
+
+ Dear
+
+
+ For your JHipster account a password reset was requested, please click on the URL below to reset it:
+
+
+ Reset Link
+
+
+ Regards,
+
+ JHipster.
+
+
+
diff --git a/jhipster/src/main/resources/templates/error.html b/jhipster/src/main/resources/templates/error.html
new file mode 100644
index 000000000000..774b080d6c43
--- /dev/null
+++ b/jhipster/src/main/resources/templates/error.html
@@ -0,0 +1,162 @@
+
+
+
+
+ Your request cannot be processed
+
+
+
+
+
Your request cannot be processed :(
+
+
Sorry, an error has occurred.
+
+
Status: (
)
+
+ Message:
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/404.html b/jhipster/src/main/webapp/404.html
new file mode 100644
index 000000000000..8d7925a892c6
--- /dev/null
+++ b/jhipster/src/main/webapp/404.html
@@ -0,0 +1,60 @@
+
+
+
+
+ Page Not Found
+
+
+
+
+ Page Not Found
+ Sorry, but the page you were trying to view does not exist.
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/account.module.ts b/jhipster/src/main/webapp/app/account/account.module.ts
new file mode 100644
index 000000000000..09b19a7555c6
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/account.module.ts
@@ -0,0 +1,45 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterModule } from '@angular/router';
+
+import { BaeldungSharedModule } from '../shared';
+
+import {
+ Register,
+ Activate,
+ Password,
+ PasswordResetInit,
+ PasswordResetFinish,
+ PasswordStrengthBarComponent,
+ RegisterComponent,
+ ActivateComponent,
+ PasswordComponent,
+ PasswordResetInitComponent,
+ PasswordResetFinishComponent,
+ SettingsComponent,
+ accountState
+} from './';
+
+@NgModule({
+ imports: [
+ BaeldungSharedModule,
+ RouterModule.forRoot(accountState, { useHash: true })
+ ],
+ declarations: [
+ ActivateComponent,
+ RegisterComponent,
+ PasswordComponent,
+ PasswordStrengthBarComponent,
+ PasswordResetInitComponent,
+ PasswordResetFinishComponent,
+ SettingsComponent
+ ],
+ providers: [
+ Register,
+ Activate,
+ Password,
+ PasswordResetInit,
+ PasswordResetFinish
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class BaeldungAccountModule {}
diff --git a/jhipster/src/main/webapp/app/account/account.route.ts b/jhipster/src/main/webapp/app/account/account.route.ts
new file mode 100644
index 000000000000..4715216cf3d4
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/account.route.ts
@@ -0,0 +1,26 @@
+import { Routes, CanActivate } from '@angular/router';
+
+import { UserRouteAccessService } from '../shared';
+
+import {
+ activateRoute,
+ passwordRoute,
+ passwordResetFinishRoute,
+ passwordResetInitRoute,
+ registerRoute,
+ settingsRoute
+} from './';
+
+let ACCOUNT_ROUTES = [
+ activateRoute,
+ passwordRoute,
+ passwordResetFinishRoute,
+ passwordResetInitRoute,
+ registerRoute,
+ settingsRoute
+];
+
+export const accountState: Routes = [{
+ path: '',
+ children: ACCOUNT_ROUTES
+}];
diff --git a/jhipster/src/main/webapp/app/account/activate/activate.component.html b/jhipster/src/main/webapp/app/account/activate/activate.component.html
new file mode 100644
index 000000000000..25e3f2341797
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/activate/activate.component.html
@@ -0,0 +1,19 @@
+
+
+
+
Activation
+
+
+
+ Your user has been activated. Please
+ sign in .
+
+
+
+
+ Your user could not be activated. Please use the registration form to sign up.
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/activate/activate.component.ts b/jhipster/src/main/webapp/app/account/activate/activate.component.ts
new file mode 100644
index 000000000000..dbaaa7d676f6
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/activate/activate.component.ts
@@ -0,0 +1,42 @@
+import { Component, OnInit } from '@angular/core';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { ActivatedRoute } from '@angular/router';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { Activate } from './activate.service';
+import { LoginModalService } from '../../shared';
+
+@Component({
+ selector: 'jhi-activate',
+ templateUrl: './activate.component.html'
+})
+export class ActivateComponent implements OnInit {
+ error: string;
+ success: string;
+ modalRef: NgbModalRef;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private activate: Activate,
+ private loginModalService: LoginModalService,
+ private route: ActivatedRoute
+ ) {
+ this.jhiLanguageService.setLocations(['activate']);
+ }
+
+ ngOnInit () {
+ this.route.queryParams.subscribe(params => {
+ this.activate.get(params['key']).subscribe(() => {
+ this.error = null;
+ this.success = 'OK';
+ }, () => {
+ this.success = null;
+ this.error = 'ERROR';
+ });
+ });
+ }
+
+ login() {
+ this.modalRef = this.loginModalService.open();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/activate/activate.route.ts b/jhipster/src/main/webapp/app/account/activate/activate.route.ts
new file mode 100644
index 000000000000..8f1bb32b5850
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/activate/activate.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { ActivateComponent } from './activate.component';
+
+export const activateRoute: Route = {
+ path: 'activate',
+ component: ActivateComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'activate.title'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster/src/main/webapp/app/account/activate/activate.service.ts b/jhipster/src/main/webapp/app/account/activate/activate.service.ts
new file mode 100644
index 000000000000..f877a4d50e03
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/activate/activate.service.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+import { Http, Response, URLSearchParams } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class Activate {
+
+ constructor (private http: Http) {}
+
+ get(key: string): Observable {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('key', key);
+
+ return this.http.get('api/activate', {
+ search: params
+ }).map((res: Response) => res);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/index.ts b/jhipster/src/main/webapp/app/account/index.ts
new file mode 100644
index 000000000000..aeada0551ce1
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/index.ts
@@ -0,0 +1,19 @@
+export * from './activate/activate.component';
+export * from './activate/activate.service';
+export * from './activate/activate.route';
+export * from './password/password.component';
+export * from './password/password-strength-bar.component';
+export * from './password/password.service';
+export * from './password/password.route';
+export * from './password-reset/finish/password-reset-finish.component';
+export * from './password-reset/finish/password-reset-finish.service';
+export * from './password-reset/finish/password-reset-finish.route';
+export * from './password-reset/init/password-reset-init.component';
+export * from './password-reset/init/password-reset-init.service';
+export * from './password-reset/init/password-reset-init.route';
+export * from './register/register.component';
+export * from './register/register.service';
+export * from './register/register.route';
+export * from './settings/settings.component';
+export * from './settings/settings.route';
+export * from './account.route';
diff --git a/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html
new file mode 100644
index 000000000000..a1dfde055ca7
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html
@@ -0,0 +1,77 @@
+
+
+
+
Reset password
+
+
+ The password reset key is missing.
+
+
+
+
Choose a new password
+
+
+
+
Your password couldn't be reset. Remember a password request is only valid for 24 hours.
+
+
+
+ Your password has been reset. Please
+ sign in .
+
+
+
+ The password and its confirmation do not match!
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts
new file mode 100644
index 000000000000..f1889920bd55
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts
@@ -0,0 +1,65 @@
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { ActivatedRoute } from '@angular/router';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { PasswordResetFinish } from './password-reset-finish.service';
+import { LoginModalService } from '../../../shared';
+
+@Component({
+ selector: 'jhi-password-reset-finish',
+ templateUrl: './password-reset-finish.component.html'
+})
+export class PasswordResetFinishComponent implements OnInit, AfterViewInit {
+ confirmPassword: string;
+ doNotMatch: string;
+ error: string;
+ keyMissing: boolean;
+ resetAccount: any;
+ success: string;
+ modalRef: NgbModalRef;
+ key: string;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private passwordResetFinish: PasswordResetFinish,
+ private loginModalService: LoginModalService,
+ private route: ActivatedRoute,
+ private elementRef: ElementRef, private renderer: Renderer
+ ) {
+ this.jhiLanguageService.setLocations(['reset']);
+ }
+
+ ngOnInit() {
+ this.route.queryParams.subscribe(params => {
+ this.key = params['key'];
+ });
+ this.resetAccount = {};
+ this.keyMissing = !this.key;
+ }
+
+ ngAfterViewInit() {
+ if (this.elementRef.nativeElement.querySelector('#password') != null) {
+ this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#password'), 'focus', []);
+ }
+ }
+
+ finishReset() {
+ this.doNotMatch = null;
+ this.error = null;
+ if (this.resetAccount.password !== this.confirmPassword) {
+ this.doNotMatch = 'ERROR';
+ } else {
+ this.passwordResetFinish.save({key: this.key, newPassword: this.resetAccount.password}).subscribe(() => {
+ this.success = 'OK';
+ }, () => {
+ this.success = null;
+ this.error = 'ERROR';
+ });
+ }
+ }
+
+ login() {
+ this.modalRef = this.loginModalService.open();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts
new file mode 100644
index 000000000000..7b02653ed567
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../../shared';
+import { PasswordResetFinishComponent } from './password-reset-finish.component';
+
+export const passwordResetFinishRoute: Route = {
+ path: 'reset/finish',
+ component: PasswordResetFinishComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'global.menu.account.password'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts
new file mode 100644
index 000000000000..abd81374b065
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { Http } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class PasswordResetFinish {
+
+ constructor (private http: Http) {}
+
+ save(keyAndPassword: any): Observable {
+ return this.http.post('api/account/reset_password/finish', keyAndPassword);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html
new file mode 100644
index 000000000000..2503433275e0
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html
@@ -0,0 +1,47 @@
+
+
+
+
Reset your password
+
+
+ E-Mail address isn't registered! Please check and try again.
+
+
+
+
Enter the e-mail address you used to register.
+
+
+
+
Check your e-mails for details on how to reset your password.
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts
new file mode 100644
index 000000000000..eba521dc050b
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts
@@ -0,0 +1,49 @@
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { PasswordResetInit } from './password-reset-init.service';
+
+@Component({
+ selector: 'jhi-password-reset-init',
+ templateUrl: './password-reset-init.component.html'
+})
+export class PasswordResetInitComponent implements OnInit, AfterViewInit {
+ error: string;
+ errorEmailNotExists: string;
+ resetAccount: any;
+ success: string;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private passwordResetInit: PasswordResetInit,
+ private elementRef: ElementRef,
+ private renderer: Renderer
+ ) {
+ this.jhiLanguageService.setLocations(['reset']);
+ }
+
+ ngOnInit() {
+ this.resetAccount = {};
+ }
+
+ ngAfterViewInit() {
+ this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#email'), 'focus', []);
+ }
+
+ requestReset () {
+
+ this.error = null;
+ this.errorEmailNotExists = null;
+
+ this.passwordResetInit.save(this.resetAccount.email).subscribe(() => {
+ this.success = 'OK';
+ }, (response) => {
+ this.success = null;
+ if (response.status === 400 && response.data === 'e-mail address not registered') {
+ this.errorEmailNotExists = 'ERROR';
+ } else {
+ this.error = 'ERROR';
+ }
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts
new file mode 100644
index 000000000000..37f7e8102dd7
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../../shared';
+import { PasswordResetInitComponent } from './password-reset-init.component';
+
+export const passwordResetInitRoute: Route = {
+ path: 'reset/request',
+ component: PasswordResetInitComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'global.menu.account.password'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts
new file mode 100644
index 000000000000..fa466cd6cc38
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { Http } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class PasswordResetInit {
+
+ constructor (private http: Http) {}
+
+ save(mail: string): Observable {
+ return this.http.post('api/account/reset_password/init', mail);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/password/password-strength-bar.component.ts b/jhipster/src/main/webapp/app/account/password/password-strength-bar.component.ts
new file mode 100644
index 000000000000..2a61d133d65c
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password/password-strength-bar.component.ts
@@ -0,0 +1,89 @@
+import { Component, ElementRef, Input, Renderer } from '@angular/core';
+
+@Component({
+ selector: 'jhi-password-strength-bar',
+ template: `
+ `,
+ styleUrls: [
+ 'password-strength-bar.scss'
+ ]
+})
+export class PasswordStrengthBarComponent {
+
+ colors = ['#F00', '#F90', '#FF0', '#9F0', '#0F0'];
+
+ constructor(private renderer: Renderer, private elementRef: ElementRef) { }
+
+ measureStrength(p: string): number {
+
+ let force = 0;
+ let regex = /[$-/:-?{-~!"^_`\[\]]/g; // "
+
+ let lowerLetters = /[a-z]+/.test(p);
+ let upperLetters = /[A-Z]+/.test(p);
+ let numbers = /[0-9]+/.test(p);
+ let symbols = regex.test(p);
+
+ let flags = [lowerLetters, upperLetters, numbers, symbols];
+ let passedMatches = flags.filter( (isMatchedFlag: boolean) => {
+ return isMatchedFlag === true;
+ }).length;
+
+ force += 2 * p.length + ((p.length >= 10) ? 1 : 0);
+ force += passedMatches * 10;
+
+ // penality (short password)
+ force = (p.length <= 6) ? Math.min(force, 10) : force;
+
+ // penality (poor variety of characters)
+ force = (passedMatches === 1) ? Math.min(force, 10) : force;
+ force = (passedMatches === 2) ? Math.min(force, 20) : force;
+ force = (passedMatches === 3) ? Math.min(force, 40) : force;
+
+ return force;
+ };
+
+ getColor(s: number): any {
+ let idx = 0;
+ if (s <= 10) {
+ idx = 0;
+ } else if (s <= 20) {
+ idx = 1;
+ } else if (s <= 30) {
+ idx = 2;
+ } else if (s <= 40) {
+ idx = 3;
+ } else {
+ idx = 4;
+ }
+ return {idx: idx + 1, col: this.colors[idx]};
+ };
+
+ @Input()
+ set passwordToCheck(password: string) {
+ if (password) {
+ let c = this.getColor(this.measureStrength(password));
+ let element = this.elementRef.nativeElement;
+ if ( element.className ) {
+ this.renderer.setElementClass(element, element.className , false);
+ }
+ let lis = element.getElementsByTagName('li');
+ for (let i = 0; i < lis.length; i++) {
+ if (i < c.idx) {
+ this.renderer.setElementStyle(lis[i], 'backgroundColor', c.col);
+ } else {
+ this.renderer.setElementStyle(lis[i], 'backgroundColor', '#DDD');
+ }
+ }
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/password/password-strength-bar.scss b/jhipster/src/main/webapp/app/account/password/password-strength-bar.scss
new file mode 100644
index 000000000000..8f8effcb60d7
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password/password-strength-bar.scss
@@ -0,0 +1,23 @@
+/* ==========================================================================
+start Password strength bar style
+========================================================================== */
+ul#strength {
+ display:inline;
+ list-style:none;
+ margin:0;
+ margin-left:15px;
+ padding:0;
+ vertical-align:2px;
+}
+
+.point {
+ background:#DDD;
+ border-radius:2px;
+ display:inline-block;
+ height:5px;
+ margin-right:1px;
+ width:20px;
+ &:last {
+ margin:0 !important;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/password/password.component.html b/jhipster/src/main/webapp/app/account/password/password.component.html
new file mode 100644
index 000000000000..2534bccdfca9
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password/password.component.html
@@ -0,0 +1,65 @@
+
+
+
+
Password for [{{account.login}} ]
+
+
+ Password changed!
+
+
+ An error has occurred! The password could not be changed.
+
+
+
+ The password and its confirmation do not match!
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/password/password.component.ts b/jhipster/src/main/webapp/app/account/password/password.component.ts
new file mode 100644
index 000000000000..f34eae423cf3
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password/password.component.ts
@@ -0,0 +1,49 @@
+import { Component, OnInit } from '@angular/core';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { Principal } from '../../shared';
+import { Password } from './password.service';
+
+@Component({
+ selector: 'jhi-password',
+ templateUrl: './password.component.html'
+})
+export class PasswordComponent implements OnInit {
+ doNotMatch: string;
+ error: string;
+ success: string;
+ account: any;
+ password: string;
+ confirmPassword: string;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private passwordService: Password,
+ private principal: Principal
+ ) {
+ this.jhiLanguageService.setLocations(['password']);
+ }
+
+ ngOnInit () {
+ this.principal.identity().then((account) => {
+ this.account = account;
+ });
+ }
+
+ changePassword () {
+ if (this.password !== this.confirmPassword) {
+ this.error = null;
+ this.success = null;
+ this.doNotMatch = 'ERROR';
+ } else {
+ this.doNotMatch = null;
+ this.passwordService.save(this.password).subscribe(() => {
+ this.error = null;
+ this.success = 'OK';
+ }, () => {
+ this.success = null;
+ this.error = 'ERROR';
+ });
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/password/password.route.ts b/jhipster/src/main/webapp/app/account/password/password.route.ts
new file mode 100644
index 000000000000..d5a71184584f
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password/password.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { PasswordComponent } from './password.component';
+
+export const passwordRoute: Route = {
+ path: 'password',
+ component: PasswordComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'global.menu.account.password'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster/src/main/webapp/app/account/password/password.service.ts b/jhipster/src/main/webapp/app/account/password/password.service.ts
new file mode 100644
index 000000000000..0c220d8816d2
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/password/password.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { Http } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class Password {
+
+ constructor (private http: Http) {}
+
+ save(newPassword: string): Observable {
+ return this.http.post('api/account/change_password', newPassword);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/register/register.component.html b/jhipster/src/main/webapp/app/account/register/register.component.html
new file mode 100644
index 000000000000..14a0c851e252
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/register/register.component.html
@@ -0,0 +1,122 @@
+
+
+
+
Registration
+
+
+ Registration saved! Please check your email for confirmation.
+
+
+
+ Registration failed! Please try again later.
+
+
+
+ Login name already registered! Please choose another one.
+
+
+
+ E-mail is already in use! Please choose another one.
+
+
+
+ The password and its confirmation do not match!
+
+
+
+
+
+
+
If you want to
+
sign in , you can try the default accounts: - Administrator (login="admin" and password="admin") - User (login="user" and password="user").
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/register/register.component.ts b/jhipster/src/main/webapp/app/account/register/register.component.ts
new file mode 100644
index 000000000000..dc5c4ef2f4f8
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/register/register.component.ts
@@ -0,0 +1,73 @@
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { Register } from './register.service';
+import { LoginModalService } from '../../shared';
+
+@Component({
+ selector: 'jhi-register',
+ templateUrl: './register.component.html'
+})
+export class RegisterComponent implements OnInit, AfterViewInit {
+
+ confirmPassword: string;
+ doNotMatch: string;
+ error: string;
+ errorEmailExists: string;
+ errorUserExists: string;
+ registerAccount: any;
+ success: boolean;
+ modalRef: NgbModalRef;
+
+ constructor(
+ private languageService: JhiLanguageService,
+ private loginModalService: LoginModalService,
+ private registerService: Register,
+ private elementRef: ElementRef,
+ private renderer: Renderer
+ ) {
+ this.languageService.setLocations(['register']);
+ }
+
+ ngOnInit() {
+ this.success = false;
+ this.registerAccount = {};
+ }
+
+ ngAfterViewInit() {
+ this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#login'), 'focus', []);
+ }
+
+ register() {
+ if (this.registerAccount.password !== this.confirmPassword) {
+ this.doNotMatch = 'ERROR';
+ } else {
+ this.doNotMatch = null;
+ this.error = null;
+ this.errorUserExists = null;
+ this.errorEmailExists = null;
+ this.languageService.getCurrent().then(key => {
+ this.registerAccount.langKey = key;
+ this.registerService.save(this.registerAccount).subscribe(() => {
+ this.success = true;
+ }, (response) => this.processError(response));
+ });
+ }
+ }
+
+ openLogin() {
+ this.modalRef = this.loginModalService.open();
+ }
+
+ private processError(response) {
+ this.success = null;
+ if (response.status === 400 && response._body === 'login already in use') {
+ this.errorUserExists = 'ERROR';
+ } else if (response.status === 400 && response._body === 'e-mail address already in use') {
+ this.errorEmailExists = 'ERROR';
+ } else {
+ this.error = 'ERROR';
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/register/register.route.ts b/jhipster/src/main/webapp/app/account/register/register.route.ts
new file mode 100644
index 000000000000..3dc47f1e5896
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/register/register.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { RegisterComponent } from './register.component';
+
+export const registerRoute: Route = {
+ path: 'register',
+ component: RegisterComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'register.title'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster/src/main/webapp/app/account/register/register.service.ts b/jhipster/src/main/webapp/app/account/register/register.service.ts
new file mode 100644
index 000000000000..87bbe9d37b9e
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/register/register.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { Http } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class Register {
+
+ constructor (private http: Http) {}
+
+ save(account: any): Observable {
+ return this.http.post('api/register', account);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/settings/settings.component.html b/jhipster/src/main/webapp/app/account/settings/settings.component.html
new file mode 100644
index 000000000000..82fca9e80479
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/settings/settings.component.html
@@ -0,0 +1,86 @@
+
+
+
+
User settings for [{{settingsAccount.login}} ]
+
+
+ Settings saved!
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/account/settings/settings.component.ts b/jhipster/src/main/webapp/app/account/settings/settings.component.ts
new file mode 100644
index 000000000000..37f4c70e3b9d
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/settings/settings.component.ts
@@ -0,0 +1,63 @@
+import { Component, OnInit } from '@angular/core';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { Principal, AccountService, JhiLanguageHelper } from '../../shared';
+
+@Component({
+ selector: 'jhi-settings',
+ templateUrl: './settings.component.html'
+})
+export class SettingsComponent implements OnInit {
+ error: string;
+ success: string;
+ settingsAccount: any;
+ languages: any[];
+
+ constructor(
+ private account: AccountService,
+ private principal: Principal,
+ private languageService: JhiLanguageService,
+ private languageHelper: JhiLanguageHelper
+ ) {
+ this.languageService.setLocations(['settings']);
+ }
+
+ ngOnInit () {
+ this.principal.identity().then((account) => {
+ this.settingsAccount = this.copyAccount(account);
+ });
+ this.languageHelper.getAll().then((languages) => {
+ this.languages = languages;
+ });
+ }
+
+ save () {
+ this.account.save(this.settingsAccount).subscribe(() => {
+ this.error = null;
+ this.success = 'OK';
+ this.principal.identity(true).then((account) => {
+ this.settingsAccount = this.copyAccount(account);
+ });
+ this.languageService.getCurrent().then((current) => {
+ if (this.settingsAccount.langKey !== current) {
+ this.languageService.changeLanguage(this.settingsAccount.langKey);
+ }
+ });
+ }, () => {
+ this.success = null;
+ this.error = 'ERROR';
+ });
+ }
+
+ copyAccount (account) {
+ return {
+ activated: account.activated,
+ email: account.email,
+ firstName: account.firstName,
+ langKey: account.langKey,
+ lastName: account.lastName,
+ login: account.login,
+ imageUrl: account.imageUrl
+ };
+ }
+}
diff --git a/jhipster/src/main/webapp/app/account/settings/settings.route.ts b/jhipster/src/main/webapp/app/account/settings/settings.route.ts
new file mode 100644
index 000000000000..9c9a852c7bbd
--- /dev/null
+++ b/jhipster/src/main/webapp/app/account/settings/settings.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { SettingsComponent } from './settings.component';
+
+export const settingsRoute: Route = {
+ path: 'settings',
+ component: SettingsComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'global.menu.account.settings'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster/src/main/webapp/app/admin/admin.module.ts b/jhipster/src/main/webapp/app/admin/admin.module.ts
new file mode 100644
index 000000000000..4ac749bc78ce
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/admin.module.ts
@@ -0,0 +1,73 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { ParseLinks } from 'ng-jhipster';
+
+import { BaeldungSharedModule } from '../shared';
+
+import {
+ adminState,
+ AuditsComponent,
+ UserMgmtComponent,
+ UserDialogComponent,
+ UserDeleteDialogComponent,
+ UserMgmtDetailComponent,
+ UserMgmtDialogComponent,
+ UserMgmtDeleteDialogComponent,
+ LogsComponent,
+ JhiMetricsMonitoringModalComponent,
+ JhiMetricsMonitoringComponent,
+ JhiHealthModalComponent,
+ JhiHealthCheckComponent,
+ JhiConfigurationComponent,
+ JhiDocsComponent,
+ AuditsService,
+ JhiConfigurationService,
+ JhiHealthService,
+ JhiMetricsService,
+ LogsService,
+ UserResolvePagingParams,
+ UserResolve,
+ UserModalService
+} from './';
+
+
+@NgModule({
+ imports: [
+ BaeldungSharedModule,
+ RouterModule.forRoot(adminState, { useHash: true })
+ ],
+ declarations: [
+ AuditsComponent,
+ UserMgmtComponent,
+ UserDialogComponent,
+ UserDeleteDialogComponent,
+ UserMgmtDetailComponent,
+ UserMgmtDialogComponent,
+ UserMgmtDeleteDialogComponent,
+ LogsComponent,
+ JhiConfigurationComponent,
+ JhiHealthCheckComponent,
+ JhiHealthModalComponent,
+ JhiDocsComponent,
+ JhiMetricsMonitoringComponent,
+ JhiMetricsMonitoringModalComponent
+ ],
+ entryComponents: [
+ UserMgmtDialogComponent,
+ UserMgmtDeleteDialogComponent,
+ JhiHealthModalComponent,
+ JhiMetricsMonitoringModalComponent,
+ ],
+ providers: [
+ AuditsService,
+ JhiConfigurationService,
+ JhiHealthService,
+ JhiMetricsService,
+ LogsService,
+ UserResolvePagingParams,
+ UserResolve,
+ UserModalService
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class BaeldungAdminModule {}
diff --git a/jhipster/src/main/webapp/app/admin/admin.route.ts b/jhipster/src/main/webapp/app/admin/admin.route.ts
new file mode 100644
index 000000000000..8e64d2d5c0ae
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/admin.route.ts
@@ -0,0 +1,36 @@
+import { Routes, CanActivate } from '@angular/router';
+
+import {
+ auditsRoute,
+ configurationRoute,
+ docsRoute,
+ healthRoute,
+ logsRoute,
+ metricsRoute,
+ userMgmtRoute,
+ userDialogRoute
+} from './';
+
+import { UserRouteAccessService } from '../shared';
+
+let ADMIN_ROUTES = [
+ auditsRoute,
+ configurationRoute,
+ docsRoute,
+ healthRoute,
+ logsRoute,
+ ...userMgmtRoute,
+ metricsRoute
+];
+
+
+export const adminState: Routes = [{
+ path: '',
+ data: {
+ authorities: ['ROLE_ADMIN']
+ },
+ canActivate: [UserRouteAccessService],
+ children: ADMIN_ROUTES
+},
+ ...userDialogRoute
+];
diff --git a/jhipster/src/main/webapp/app/admin/audits/audit-data.model.ts b/jhipster/src/main/webapp/app/admin/audits/audit-data.model.ts
new file mode 100644
index 000000000000..55a5e9c819b3
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/audits/audit-data.model.ts
@@ -0,0 +1,6 @@
+export class AuditData {
+ constructor(
+ public remoteAddress: string,
+ public sessionId: string
+ ) { }
+}
diff --git a/jhipster/src/main/webapp/app/admin/audits/audit.model.ts b/jhipster/src/main/webapp/app/admin/audits/audit.model.ts
new file mode 100644
index 000000000000..9ca753795c88
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/audits/audit.model.ts
@@ -0,0 +1,10 @@
+import { AuditData } from './audit-data.model';
+
+export class Audit {
+ constructor(
+ public data: AuditData,
+ public principal: string,
+ public timestamp: string,
+ public type: string
+ ) { }
+}
diff --git a/jhipster/src/main/webapp/app/admin/audits/audits.component.html b/jhipster/src/main/webapp/app/admin/audits/audits.component.html
new file mode 100644
index 000000000000..9fcbaf7ecdcd
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/audits/audits.component.html
@@ -0,0 +1,45 @@
+
+
Audits
+
+
+
+
+
+
+
+ Date
+ User
+ State
+ Extra data
+
+
+
+ {{audit.timestamp| date:'medium'}}
+ {{audit.principal}}
+ {{audit.type}}
+
+ {{audit.data.message}}
+ Remote Address {{audit.data.remoteAddress}}
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/admin/audits/audits.component.ts b/jhipster/src/main/webapp/app/admin/audits/audits.component.ts
new file mode 100644
index 000000000000..84e6b40fe808
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/audits/audits.component.ts
@@ -0,0 +1,99 @@
+import { Component, OnInit, Inject } from '@angular/core';
+import { DatePipe } from '@angular/common';
+import { ParseLinks, JhiLanguageService} from 'ng-jhipster';
+
+import { Audit } from './audit.model';
+import { AuditsService } from './audits.service';
+import { ITEMS_PER_PAGE } from '../../shared';
+import { PaginationConfig } from '../../blocks/config/uib-pagination.config';
+
+@Component({
+ selector: 'jhi-audit',
+ templateUrl: './audits.component.html'
+})
+export class AuditsComponent implements OnInit {
+ audits: Audit[];
+ fromDate: string;
+ itemsPerPage: any;
+ links: any;
+ page: number;
+ orderProp: string;
+ reverse: boolean;
+ toDate: string;
+ totalItems: number;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private auditsService: AuditsService,
+ private parseLinks: ParseLinks,
+ private paginationConfig: PaginationConfig,
+ private datePipe: DatePipe
+ ) {
+ this.jhiLanguageService.setLocations(['audits']);
+ this.itemsPerPage = ITEMS_PER_PAGE;
+ this.page = 1;
+ this.reverse = false;
+ this.orderProp = 'timestamp';
+ }
+
+ getAudits() {
+ return this.sortAudits(this.audits);
+ }
+
+ loadPage(page: number) {
+ this.page = page;
+ this.onChangeDate();
+ }
+
+ ngOnInit() {
+ this.today();
+ this.previousMonth();
+ this.onChangeDate();
+ }
+
+ onChangeDate() {
+ this.auditsService.query({page: this.page - 1, size: this.itemsPerPage,
+ fromDate: this.fromDate, toDate: this.toDate}).subscribe(res => {
+
+ this.audits = res.json();
+ this.links = this.parseLinks.parse(res.headers.get('link'));
+ this.totalItems = + res.headers.get('X-Total-Count');
+ });
+ }
+
+ previousMonth() {
+ let dateFormat = 'yyyy-MM-dd';
+ let fromDate: Date = new Date();
+
+ if (fromDate.getMonth() === 0) {
+ fromDate = new Date(fromDate.getFullYear() - 1, 11, fromDate.getDate());
+ } else {
+ fromDate = new Date(fromDate.getFullYear(), fromDate.getMonth() - 1, fromDate.getDate());
+ }
+
+ this.fromDate = this.datePipe.transform(fromDate, dateFormat);
+ }
+
+ today() {
+ let dateFormat = 'yyyy-MM-dd';
+ // Today + 1 day - needed if the current day must be included
+ let today: Date = new Date();
+
+ let date = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
+ this.toDate = this.datePipe.transform(date, dateFormat);
+ }
+
+ private sortAudits(audits: Audit[]) {
+ audits = audits.slice(0).sort((a, b) => {
+ if (a[this.orderProp] < b[this.orderProp]) {
+ return -1;
+ } else if ([b[this.orderProp] < a[this.orderProp]]) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+
+ return this.reverse ? audits.reverse() : audits;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/audits/audits.route.ts b/jhipster/src/main/webapp/app/admin/audits/audits.route.ts
new file mode 100644
index 000000000000..f0e0fa7be99c
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/audits/audits.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { AuditsComponent } from './audits.component';
+
+export const auditsRoute: Route = {
+ path: 'audits',
+ component: AuditsComponent,
+ data: {
+ pageTitle: 'audits.title'
+ }
+};
diff --git a/jhipster/src/main/webapp/app/admin/audits/audits.service.ts b/jhipster/src/main/webapp/app/admin/audits/audits.service.ts
new file mode 100644
index 000000000000..b373b8915cbe
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/audits/audits.service.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core';
+import { Http, Response, URLSearchParams } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class AuditsService {
+ constructor(private http: Http) { }
+
+ query(req: any): Observable {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('fromDate', req.fromDate);
+ params.set('toDate', req.toDate);
+ params.set('page', req.page);
+ params.set('size', req.size);
+ params.set('sort', req.sort);
+
+ let options = {
+ search: params
+ };
+
+ return this.http.get('management/audits', options);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/configuration/configuration.component.html b/jhipster/src/main/webapp/app/admin/configuration/configuration.component.html
new file mode 100644
index 000000000000..1d64b41c2955
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/configuration/configuration.component.html
@@ -0,0 +1,46 @@
+
+
Configuration
+
+
Filter (by prefix)
+
Spring configuration
+
+
+
+ Prefix
+ Properties
+
+
+
+
+ {{entry.prefix}}
+
+
+
{{key}}
+
+ {{entry.properties[key]}}
+
+
+
+
+
+
+
+
{{key}}
+
+
+
+ Property
+ Value
+
+
+
+
+ {{item.key}}
+
+ {{item.val}}
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/admin/configuration/configuration.component.ts b/jhipster/src/main/webapp/app/admin/configuration/configuration.component.ts
new file mode 100644
index 000000000000..88fbc32250e6
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/configuration/configuration.component.ts
@@ -0,0 +1,48 @@
+import { Component, OnInit } from '@angular/core';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { JhiConfigurationService } from './configuration.service';
+
+@Component({
+ selector: 'jhi-configuration',
+ templateUrl: './configuration.component.html'
+})
+export class JhiConfigurationComponent implements OnInit {
+ allConfiguration: any = null;
+ configuration: any = null;
+ configKeys: any[];
+ filter: string;
+ orderProp: string;
+ reverse: boolean;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private configurationService: JhiConfigurationService
+ ) {
+ this.jhiLanguageService.setLocations(['configuration']);
+ this.configKeys = [];
+ this.filter = '';
+ this.orderProp = 'prefix';
+ this.reverse = false;
+ }
+
+ keys(dict): Array {
+ return (dict === undefined) ? [] : Object.keys(dict);
+ }
+
+ ngOnInit() {
+ this.configurationService.get().subscribe((configuration) => {
+ this.configuration = configuration;
+
+ for (let config of configuration) {
+ if (config.properties !== undefined) {
+ this.configKeys.push(Object.keys(config.properties));
+ }
+ }
+ });
+
+ this.configurationService.getEnv().subscribe((configuration) => {
+ this.allConfiguration = configuration;
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/configuration/configuration.route.ts b/jhipster/src/main/webapp/app/admin/configuration/configuration.route.ts
new file mode 100644
index 000000000000..06866237b039
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/configuration/configuration.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { JhiConfigurationComponent } from './configuration.component';
+
+export const configurationRoute: Route = {
+ path: 'jhi-configuration',
+ component: JhiConfigurationComponent,
+ data: {
+ pageTitle: 'configuration.title'
+ }
+};
diff --git a/jhipster/src/main/webapp/app/admin/configuration/configuration.service.ts b/jhipster/src/main/webapp/app/admin/configuration/configuration.service.ts
new file mode 100644
index 000000000000..e239ed309748
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/configuration/configuration.service.ts
@@ -0,0 +1,53 @@
+import { Injectable } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class JhiConfigurationService {
+
+ constructor(private http: Http) {
+ }
+
+ get(): Observable {
+ return this.http.get('management/configprops').map((res: Response) => {
+ let properties: any[] = [];
+
+ const propertiesObject = res.json();
+
+ for (let key in propertiesObject) {
+ if (propertiesObject.hasOwnProperty(key)) {
+ properties.push(propertiesObject[key]);
+ }
+ }
+
+ return properties.sort((propertyA, propertyB) => {
+ return (propertyA.prefix === propertyB.prefix) ? 0 :
+ (propertyA.prefix < propertyB.prefix) ? -1 : 1;
+ });
+ });
+ }
+
+ getEnv(): Observable {
+ return this.http.get('management/env').map((res: Response) => {
+ let properties: any = {};
+
+ const propertiesObject = res.json();
+
+ for (let key in propertiesObject) {
+ if (propertiesObject.hasOwnProperty(key)) {
+ let valsObject = propertiesObject[key];
+ let vals: any[] = [];
+
+ for (let valKey in valsObject) {
+ if (valsObject.hasOwnProperty(valKey)) {
+ vals.push({key: valKey, val: valsObject[valKey]});
+ }
+ }
+ properties[key] = vals;
+ }
+ }
+
+ return properties;
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/docs/docs.component.html b/jhipster/src/main/webapp/app/admin/docs/docs.component.html
new file mode 100644
index 000000000000..30efbbb93ee5
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/docs/docs.component.html
@@ -0,0 +1,2 @@
+
diff --git a/jhipster/src/main/webapp/app/admin/docs/docs.component.ts b/jhipster/src/main/webapp/app/admin/docs/docs.component.ts
new file mode 100644
index 000000000000..4bd5bf232962
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/docs/docs.component.ts
@@ -0,0 +1,14 @@
+import { Component } from '@angular/core';
+import { JhiLanguageService } from 'ng-jhipster';
+
+@Component({
+ selector: 'jhi-docs',
+ templateUrl: './docs.component.html'
+})
+export class JhiDocsComponent {
+ constructor (
+ private jhiLanguageService: JhiLanguageService
+ ) {
+ this.jhiLanguageService.setLocations(['global']);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/docs/docs.route.ts b/jhipster/src/main/webapp/app/admin/docs/docs.route.ts
new file mode 100644
index 000000000000..6e99618f1def
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/docs/docs.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { JhiDocsComponent } from './docs.component';
+
+export const docsRoute: Route = {
+ path: 'docs',
+ component: JhiDocsComponent,
+ data: {
+ pageTitle: 'global.menu.admin.apidocs'
+ }
+};
diff --git a/jhipster/src/main/webapp/app/admin/health/health-modal.component.html b/jhipster/src/main/webapp/app/admin/health/health-modal.component.html
new file mode 100644
index 000000000000..1be6271f3ba3
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/health/health-modal.component.html
@@ -0,0 +1,36 @@
+
+
+
+
Properties
+
+
+
+
+ Name
+ Value
+
+
+
+
+ {{entry.key}}
+ {{readableValue(entry.value)}}
+
+
+
+
+
+
+
Error
+
{{currentHealth.error}}
+
+
+
diff --git a/jhipster/src/main/webapp/app/admin/health/health-modal.component.ts b/jhipster/src/main/webapp/app/admin/health/health-modal.component.ts
new file mode 100644
index 000000000000..1486f20dbf5e
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/health/health-modal.component.ts
@@ -0,0 +1,37 @@
+import { Component } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+
+import { JhiHealthService } from './health.service';
+
+@Component({
+ selector: 'jhi-health-modal',
+ templateUrl: './health-modal.component.html'
+})
+export class JhiHealthModalComponent {
+
+ currentHealth: any;
+
+ constructor(private healthService: JhiHealthService, public activeModal: NgbActiveModal) {}
+
+ baseName(name) {
+ return this.healthService.getBaseName(name);
+ }
+
+ subSystemName(name) {
+ return this.healthService.getSubSystemName(name);
+ }
+
+ readableValue(value: number) {
+ if (this.currentHealth.name !== 'diskSpace') {
+ return value.toString();
+ }
+
+ // Should display storage space in an human readable unit
+ let val = value / 1073741824;
+ if (val > 1) { // Value
+ return val.toFixed(2) + ' GB';
+ } else {
+ return (value / 1048576).toFixed(2) + ' MB';
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/health/health.component.html b/jhipster/src/main/webapp/app/admin/health/health.component.html
new file mode 100644
index 000000000000..6fea72af966e
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/health/health.component.html
@@ -0,0 +1,34 @@
+
+
+ Health Checks
+
+ Refresh
+
+
+
+
+
+
+ Service Name
+ Status
+ Details
+
+
+
+
+ {{'health.indicator.' + baseName(health.name) | translate}} {{subSystemName(health.name)}}
+
+
+ {{health.status}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/admin/health/health.component.ts b/jhipster/src/main/webapp/app/admin/health/health.component.ts
new file mode 100644
index 000000000000..ab1be4271f2b
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/health/health.component.ts
@@ -0,0 +1,69 @@
+import { Component, OnInit } from '@angular/core';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { JhiHealthService } from './health.service';
+import { JhiHealthModalComponent } from './health-modal.component';
+
+@Component({
+ selector: 'jhi-health',
+ templateUrl: './health.component.html',
+})
+export class JhiHealthCheckComponent implements OnInit {
+ healthData: any;
+ updatingHealth: boolean;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private modalService: NgbModal,
+ private healthService: JhiHealthService
+ ) {
+ this.jhiLanguageService.setLocations(['health']);
+
+ }
+
+ ngOnInit() {
+ this.refresh();
+ }
+
+ baseName(name: string) {
+ return this.healthService.getBaseName(name);
+ }
+
+ getBadgeClass(statusState) {
+ if (statusState === 'UP') {
+ return 'badge-success';
+ } else {
+ return 'badge-danger';
+ }
+ }
+
+ refresh() {
+ this.updatingHealth = true;
+
+ this.healthService.checkHealth().subscribe(health => {
+ this.healthData = this.healthService.transformHealthData(health);
+ this.updatingHealth = false;
+ }, error => {
+ if (error.status === 503) {
+ this.healthData = this.healthService.transformHealthData(error.json());
+ this.updatingHealth = false;
+ }
+ });
+ }
+
+ showHealth(health: any) {
+ const modalRef = this.modalService.open(JhiHealthModalComponent);
+ modalRef.componentInstance.currentHealth = health;
+ modalRef.result.then((result) => {
+ // Left blank intentionally, nothing to do here
+ }, (reason) => {
+ // Left blank intentionally, nothing to do here
+ });
+ }
+
+ subSystemName(name: string) {
+ return this.healthService.getSubSystemName(name);
+ }
+
+}
diff --git a/jhipster/src/main/webapp/app/admin/health/health.route.ts b/jhipster/src/main/webapp/app/admin/health/health.route.ts
new file mode 100644
index 000000000000..b47d07a0c0c4
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/health/health.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { JhiHealthCheckComponent } from './health.component';
+
+export const healthRoute: Route = {
+ path: 'jhi-health',
+ component: JhiHealthCheckComponent,
+ data: {
+ pageTitle: 'health.title'
+ }
+};
diff --git a/jhipster/src/main/webapp/app/admin/health/health.service.ts b/jhipster/src/main/webapp/app/admin/health/health.service.ts
new file mode 100644
index 000000000000..1bf00dab085c
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/health/health.service.ts
@@ -0,0 +1,140 @@
+import { Injectable } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class JhiHealthService {
+
+ separator: string;
+
+ constructor (private http: Http) {
+ this.separator = '.';
+ }
+
+ checkHealth(): Observable {
+ return this.http.get('management/health').map((res: Response) => res.json());
+ }
+
+ transformHealthData(data): any {
+ let response = [];
+ this.flattenHealthData(response, null, data);
+ return response;
+ }
+
+ getBaseName(name): string {
+ if (name) {
+ let split = name.split('.');
+ return split[0];
+ }
+ }
+
+ getSubSystemName(name): string {
+ if (name) {
+ let split = name.split('.');
+ split.splice(0, 1);
+ let remainder = split.join('.');
+ return remainder ? ' - ' + remainder : '';
+ }
+ }
+
+ /* private methods */
+ private addHealthObject(result, isLeaf, healthObject, name): any {
+
+ let status: any;
+ let error: any;
+ let healthData: any = {
+ 'name': name,
+ 'error': error,
+ 'status': status
+ };
+
+ let details = {};
+ let hasDetails = false;
+
+ for (let key in healthObject) {
+ if (healthObject.hasOwnProperty(key)) {
+ let value = healthObject[key];
+ if (key === 'status' || key === 'error') {
+ healthData[key] = value;
+ } else {
+ if (!this.isHealthObject(value)) {
+ details[key] = value;
+ hasDetails = true;
+ }
+ }
+ }
+ }
+
+ // Add the details
+ if (hasDetails) {
+ healthData.details = details;
+ }
+
+ // Only add nodes if they provide additional information
+ if (isLeaf || hasDetails || healthData.error) {
+ result.push(healthData);
+ }
+ return healthData;
+ }
+
+ private flattenHealthData (result, path, data): any {
+ for (let key in data) {
+ if (data.hasOwnProperty(key)) {
+ let value = data[key];
+ if (this.isHealthObject(value)) {
+ if (this.hasSubSystem(value)) {
+ this.addHealthObject(result, false, value, this.getModuleName(path, key));
+ this.flattenHealthData(result, this.getModuleName(path, key), value);
+ } else {
+ this.addHealthObject(result, true, value, this.getModuleName(path, key));
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private getModuleName (path, name): string {
+ let result;
+ if (path && name) {
+ result = path + this.separator + name;
+ } else if (path) {
+ result = path;
+ } else if (name) {
+ result = name;
+ } else {
+ result = '';
+ }
+ return result;
+ }
+
+ private hasSubSystem (healthObject): boolean {
+ let result = false;
+
+ for (let key in healthObject) {
+ if (healthObject.hasOwnProperty(key)) {
+ let value = healthObject[key];
+ if (value && value.status) {
+ result = true;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private isHealthObject (healthObject): boolean {
+ let result = false;
+
+ for (let key in healthObject) {
+ if (healthObject.hasOwnProperty(key)) {
+ if (key === 'status') {
+ result = true;
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/index.ts b/jhipster/src/main/webapp/app/admin/index.ts
new file mode 100644
index 000000000000..2273c8af127a
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/index.ts
@@ -0,0 +1,29 @@
+export * from './audits/audits.component';
+export * from './audits/audits.service';
+export * from './audits/audits.route';
+export * from './audits/audit.model';
+export * from './audits/audit-data.model';
+export * from './configuration/configuration.component';
+export * from './configuration/configuration.service';
+export * from './configuration/configuration.route';
+export * from './docs/docs.component';
+export * from './docs/docs.route';
+export * from './health/health.component';
+export * from './health/health-modal.component';
+export * from './health/health.service';
+export * from './health/health.route';
+export * from './logs/logs.component';
+export * from './logs/logs.service';
+export * from './logs/logs.route';
+export * from './logs/log.model';
+export * from './metrics/metrics.component';
+export * from './metrics/metrics-modal.component';
+export * from './metrics/metrics.service';
+export * from './metrics/metrics.route';
+export * from './user-management/user-management-dialog.component';
+export * from './user-management/user-management-delete-dialog.component';
+export * from './user-management/user-management-detail.component';
+export * from './user-management/user-management.component';
+export * from './user-management/user-management.route';
+export * from './user-management/user-modal.service';
+export * from './admin.route';
diff --git a/jhipster/src/main/webapp/app/admin/logs/log.model.ts b/jhipster/src/main/webapp/app/admin/logs/log.model.ts
new file mode 100644
index 000000000000..79cbd3480884
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/logs/log.model.ts
@@ -0,0 +1,6 @@
+export class Log {
+ constructor(
+ public name: string,
+ public level: string
+ ) { }
+}
diff --git a/jhipster/src/main/webapp/app/admin/logs/logs.component.html b/jhipster/src/main/webapp/app/admin/logs/logs.component.html
new file mode 100644
index 000000000000..242ef5563a9c
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/logs/logs.component.html
@@ -0,0 +1,27 @@
+
+
Logs
+
+
There are {{ loggers.length }} loggers.
+
+
Filter
+
+
+
+
+ Name
+ Level
+
+
+
+
+ {{logger.name | slice:0:140}}
+
+ TRACE
+ DEBUG
+ INFO
+ WARN
+ ERROR
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/admin/logs/logs.component.ts b/jhipster/src/main/webapp/app/admin/logs/logs.component.ts
new file mode 100644
index 000000000000..7655749018bc
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/logs/logs.component.ts
@@ -0,0 +1,38 @@
+import { Component, OnInit } from '@angular/core';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { Log } from './log.model';
+import { LogsService } from './logs.service';
+
+@Component({
+ selector: 'jhi-logs',
+ templateUrl: './logs.component.html',
+})
+export class LogsComponent implements OnInit {
+
+ loggers: Log[];
+ filter: string;
+ orderProp: string;
+ reverse: boolean;
+
+ constructor (
+ private jhiLanguageService: JhiLanguageService,
+ private logsService: LogsService
+ ) {
+ this.filter = '';
+ this.orderProp = 'name';
+ this.reverse = false;
+ this.jhiLanguageService.setLocations(['logs']);
+ }
+
+ ngOnInit() {
+ this.logsService.findAll().subscribe(loggers => this.loggers = loggers);
+ }
+
+ changeLevel (name: string, level: string) {
+ let log = new Log(name, level);
+ this.logsService.changeLevel(log).subscribe(() => {
+ this.logsService.findAll().subscribe(loggers => this.loggers = loggers);
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/logs/logs.route.ts b/jhipster/src/main/webapp/app/admin/logs/logs.route.ts
new file mode 100644
index 000000000000..1077f5da32a3
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/logs/logs.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { LogsComponent } from './logs.component';
+
+export const logsRoute: Route = {
+ path: 'logs',
+ component: LogsComponent,
+ data: {
+ pageTitle: 'logs.title'
+ }
+};
diff --git a/jhipster/src/main/webapp/app/admin/logs/logs.service.ts b/jhipster/src/main/webapp/app/admin/logs/logs.service.ts
new file mode 100644
index 000000000000..c937c10a42e3
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/logs/logs.service.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+import { Log } from './log.model';
+
+@Injectable()
+export class LogsService {
+ constructor(private http: Http) { }
+
+ changeLevel(log: Log): Observable {
+ return this.http.put('management/logs', log);
+ }
+
+ findAll(): Observable {
+ return this.http.get('management/logs').map((res: Response) => res.json());
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.html b/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.html
new file mode 100644
index 000000000000..b8f0391acf1a
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.html
@@ -0,0 +1,56 @@
+
+
+
+
All {{threadDumpAll}}
+
Runnable {{threadDumpRunnable}}
+
Waiting {{threadDumpWaiting}}
+
Timed Waiting {{threadDumpTimedWaiting}}
+
Blocked {{threadDumpBlocked}}
+
+ Filter
+
+
+
+ {{entry.value.threadState}} {{entry.value.threadName}} (ID {{entry.value.threadId}})
+
+ Show StackTrace
+ Hide StackTrace
+
+
+
+
+
+ {{st.value.className}}.{{st.value.methodName}}({{st.value.fileName}}:{{st.value.lineNumber}})
+
+
+
+
+
+
+
+ Blocked Time
+ Blocked Count
+ Waited Time
+ Waited Count
+ Lock Name
+
+
+
+
+ {{entry.value.blockedTime}}
+ {{entry.value.blockedCount}}
+ {{entry.value.waitedTime}}
+ {{entry.value.waitedCount}}
+ {{entry.value.lockName}}
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.ts b/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.ts
new file mode 100644
index 000000000000..4a3034dd94b5
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/metrics/metrics-modal.component.ts
@@ -0,0 +1,48 @@
+import { Component, OnInit } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ selector: 'jhi-metrics-modal',
+ templateUrl: './metrics-modal.component.html'
+})
+export class JhiMetricsMonitoringModalComponent implements OnInit {
+
+ threadDumpFilter: any;
+ threadDump: any;
+ threadDumpAll = 0;
+ threadDumpBlocked = 0;
+ threadDumpRunnable = 0;
+ threadDumpTimedWaiting = 0;
+ threadDumpWaiting = 0;
+
+ constructor(public activeModal: NgbActiveModal) {}
+
+ ngOnInit() {
+ this.threadDump.forEach((value) => {
+ if (value.threadState === 'RUNNABLE') {
+ this.threadDumpRunnable += 1;
+ } else if (value.threadState === 'WAITING') {
+ this.threadDumpWaiting += 1;
+ } else if (value.threadState === 'TIMED_WAITING') {
+ this.threadDumpTimedWaiting += 1;
+ } else if (value.threadState === 'BLOCKED') {
+ this.threadDumpBlocked += 1;
+ }
+ });
+
+ this.threadDumpAll = this.threadDumpRunnable + this.threadDumpWaiting +
+ this.threadDumpTimedWaiting + this.threadDumpBlocked;
+ }
+
+ getBadgeClass (threadState) {
+ if (threadState === 'RUNNABLE') {
+ return 'badge-success';
+ } else if (threadState === 'WAITING') {
+ return 'badge-info';
+ } else if (threadState === 'TIMED_WAITING') {
+ return 'badge-warning';
+ } else if (threadState === 'BLOCKED') {
+ return 'badge-danger';
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics.component.html b/jhipster/src/main/webapp/app/admin/metrics/metrics.component.html
new file mode 100644
index 000000000000..800ca21ef947
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/metrics/metrics.component.html
@@ -0,0 +1,253 @@
+
+
+ Application Metrics
+
+ Refresh
+
+
+
+
JVM Metrics
+
+
+
Memory
+
Total Memory ({{metrics.gauges['jvm.memory.total.used'].value / 1000000 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.total.max'].value / 1000000 | number:'1.0-0'}}M)
+
+ {{metrics.gauges['jvm.memory.total.used'].value * 100 / metrics.gauges['jvm.memory.total.max'].value | number:'1.0-0'}}%
+
+
Heap Memory ({{metrics.gauges['jvm.memory.heap.used'].value / 1000000 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.heap.max'].value / 1000000 | number:'1.0-0'}}M)
+
+ {{metrics.gauges['jvm.memory.heap.used'].value * 100 / metrics.gauges['jvm.memory.heap.max'].value | number:'1.0-0'}}%
+
+
Non-Heap Memory ({{metrics.gauges['jvm.memory.non-heap.used'].value / 1000000 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.non-heap.committed'].value / 1000000 | number:'1.0-0'}}M)
+
+ {{metrics.gauges['jvm.memory.non-heap.used'].value * 100 / metrics.gauges['jvm.memory.non-heap.committed'].value | number:'1.0-0'}}%
+
+
+
+
Threads (Total: {{metrics.gauges['jvm.threads.count'].value}})
+
Runnable {{metrics.gauges['jvm.threads.runnable.count'].value}}
+
+ {{metrics.gauges['jvm.threads.runnable.count'].value * 100 / metrics.gauges['jvm.threads.count'].value | number:'1.0-0'}}%
+
+
Timed Waiting ({{metrics.gauges['jvm.threads.timed_waiting.count'].value}})
+
+ {{metrics.gauges['jvm.threads.timed_waiting.count'].value * 100 / metrics.gauges['jvm.threads.count'].value | number:'1.0-0'}}%
+
+
Waiting ({{metrics.gauges['jvm.threads.waiting.count'].value}})
+
+ {{metrics.gauges['jvm.threads.waiting.count'].value * 100 / metrics.gauges['jvm.threads.count'].value | number:'1.0-0'}}%
+
+
Blocked ({{metrics.gauges['jvm.threads.blocked.count'].value}})
+
+ {{metrics.gauges['jvm.threads.blocked.count'].value * 100 / metrics.gauges['jvm.threads.count'].value | number:'1.0-0'}}%
+
+
+
+
Garbage collections
+
+
Mark Sweep count
+
{{metrics.gauges['jvm.garbage.PS-MarkSweep.count'].value}}
+
+
+
Mark Sweep time
+
{{metrics.gauges['jvm.garbage.PS-MarkSweep.time'].value}}ms
+
+
+
Scavenge count
+
{{metrics.gauges['jvm.garbage.PS-Scavenge.count'].value}}
+
+
+
Scavenge time
+
{{metrics.gauges['jvm.garbage.PS-Scavenge.time'].value}}ms
+
+
+
+
Updating...
+
+
HTTP requests (events per second)
+
+ Active requests {{metrics.counters['com.codahale.metrics.servlet.InstrumentedFilter.activeRequests'].count | number:'1.0-0'}} - Total requests {{metrics.timers['com.codahale.metrics.servlet.InstrumentedFilter.requests'].count | number:'1.0-0'}}
+
+
+
+
+
+ Code
+ Count
+ Mean
+ Average (1 min)
+ Average (5 min)
+ Average (15 min)
+
+
+
+
+ OK
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].count}}
+
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].mean_rate | number:'1.0-2'}}
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m1_rate | number:'1.0-2'}}
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m5_rate | number:'1.0-2'}}
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m15_rate | number:'1.0-2'}}
+
+
+
+ Not Found
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].count}}
+
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].mean_rate | number:'1.0-2'}}
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m1_rate | number:'1.0-2'}}
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m5_rate | number:'1.0-2'}}
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m15_rate | number:'1.0-2'}}
+
+
+
+ Server error
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].count}}
+
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].mean_rate | number:'1.0-2'}}
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m1_rate | number:'1.0-2'}}
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m5_rate | number:'1.0-2'}}
+
+
+ {{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m15_rate | number:'1.0-2'}}
+
+
+
+
+
+
+
Services statistics (time in millisecond)
+
+
+
+
+ Service name
+ Count
+ Mean
+ Min
+ p50
+ p75
+ p95
+ p99
+ Max
+
+
+
+
+ {{entry.key}}
+ {{entry.value.count}}
+ {{entry.value.mean * 1000 | number:'1.0-0'}}
+ {{entry.value.min * 1000 | number:'1.0-0'}}
+ {{entry.value.p50 * 1000 | number:'1.0-0'}}
+ {{entry.value.p75 * 1000 | number:'1.0-0'}}
+ {{entry.value.p95 * 1000 | number:'1.0-0'}}
+ {{entry.value.p99 * 1000 | number:'1.0-0'}}
+ {{entry.value.max * 1000 | number:'1.0-0'}}
+
+
+
+
+
+
Cache statistics
+
+
+
+
+ Cache name
+ Cache Hits
+ Cache Misses
+ Cache Gets
+ Cache Puts
+ Cache Removals
+ Cache Evictions
+ Cache Hit %
+ Cache Miss %
+ Average get time (µs)
+ Average put time (µs)
+ Average remove time (µs)
+
+
+
+
+ {{entry.key}}
+ {{metrics.gauges[entry.key + '.cache-hits'].value}}
+ {{metrics.gauges[entry.key + '.cache-misses'].value}}
+ {{metrics.gauges[entry.key + '.cache-gets'].value}}
+ {{metrics.gauges[entry.key + '.cache-puts'].value}}
+ {{metrics.gauges[entry.key + '.cache-removals'].value}}
+ {{metrics.gauges[entry.key + '.cache-evictions'].value}}
+ {{metrics.gauges[entry.key + '.cache-hit-percentage'].value}}
+ {{metrics.gauges[entry.key + '.cache-miss-percentage'].value }}
+ {{metrics.gauges[entry.key + '.average-get-time'].value | number : '1.2-2' }}
+ {{metrics.gauges[entry.key + '.average-put-time'].value | number : '1.2-2'}}
+ {{metrics.gauges[entry.key + '.average-remove-time'].value | number : '1.2-2' }}
+
+
+
+
+
+
0">DataSource statistics (time in millisecond)
+
0">
+
+
+
+ Usage ({{metrics.gauges['HikariPool-1.pool.ActiveConnections'].value}} / {{metrics.gauges['HikariPool-1.pool.TotalConnections'].value}})
+ Count
+ Mean
+ Min
+ p50
+ p75
+ p95
+ p99
+ Max
+
+
+
+
+
+
+
+ {{metrics.gauges['HikariPool-1.pool.ActiveConnections'].value * 100 / metrics.gauges['HikariPool-1.pool.TotalConnections'].value | number:'1.0-0'}}%
+
+
+
+ {{metrics.histograms['HikariPool-1.pool.Usage'].count}}
+ {{metrics.histograms['HikariPool-1.pool.Usage'].mean | number:'1.0-2'}}
+ {{metrics.histograms['HikariPool-1.pool.Usage'].min | number:'1.0-2'}}
+ {{metrics.histograms['HikariPool-1.pool.Usage'].p50 | number:'1.0-2'}}
+ {{metrics.histograms['HikariPool-1.pool.Usage'].p75 | number:'1.0-2'}}
+ {{metrics.histograms['HikariPool-1.pool.Usage'].p95 | number:'1.0-2'}}
+ {{metrics.histograms['HikariPool-1.pool.Usage'].p99 | number:'1.0-2'}}
+ {{metrics.histograms['HikariPool-1.pool.Usage'].max | number:'1.0-2'}}
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics.component.ts b/jhipster/src/main/webapp/app/admin/metrics/metrics.component.ts
new file mode 100644
index 000000000000..21c39a3debcf
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/metrics/metrics.component.ts
@@ -0,0 +1,74 @@
+import { Component, OnInit } from '@angular/core';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { JhiMetricsMonitoringModalComponent } from './metrics-modal.component';
+import { JhiMetricsService } from './metrics.service';
+
+@Component({
+ selector: 'jhi-metrics',
+ templateUrl: './metrics.component.html',
+})
+export class JhiMetricsMonitoringComponent implements OnInit {
+ metrics: any = {};
+ cachesStats: any = {};
+ servicesStats: any = {};
+ updatingMetrics = true;
+ JCACHE_KEY: string ;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private modalService: NgbModal,
+ private metricsService: JhiMetricsService
+ ) {
+ this.JCACHE_KEY = 'jcache.statistics';
+ this.jhiLanguageService.setLocations(['metrics']);
+ }
+
+ ngOnInit() {
+ this.refresh();
+ }
+
+ refresh () {
+ this.updatingMetrics = true;
+ this.metricsService.getMetrics().subscribe((metrics) => {
+ this.metrics = metrics;
+ this.updatingMetrics = false;
+ this.servicesStats = {};
+ this.cachesStats = {};
+ Object.keys(metrics.timers).forEach((key) => {
+ let value = metrics.timers[key];
+ if (key.indexOf('web.rest') !== -1 || key.indexOf('service') !== -1) {
+ this.servicesStats[key] = value;
+ }
+ });
+ Object.keys(metrics.gauges).forEach((key) => {
+ if (key.indexOf('jcache.statistics') !== -1) {
+ let value = metrics.gauges[key].value;
+ // remove gets or puts
+ let index = key.lastIndexOf('.');
+ let newKey = key.substr(0, index);
+
+ // Keep the name of the domain
+ this.cachesStats[newKey] = {
+ 'name': this.JCACHE_KEY.length,
+ 'value': value
+ };
+ }
+ });
+ });
+ }
+
+ refreshThreadDumpData () {
+ this.metricsService.threadDump().subscribe((data) => {
+ const modalRef = this.modalService.open(JhiMetricsMonitoringModalComponent, { size: 'lg'});
+ modalRef.componentInstance.threadDump = data;
+ modalRef.result.then((result) => {
+ // Left blank intentionally, nothing to do here
+ }, (reason) => {
+ // Left blank intentionally, nothing to do here
+ });
+ });
+ }
+
+}
diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics.route.ts b/jhipster/src/main/webapp/app/admin/metrics/metrics.route.ts
new file mode 100644
index 000000000000..cc13284dc8e2
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/metrics/metrics.route.ts
@@ -0,0 +1,12 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { JhiMetricsMonitoringComponent } from './metrics.component';
+
+export const metricsRoute: Route = {
+ path: 'jhi-metrics',
+ component: JhiMetricsMonitoringComponent,
+ data: {
+ pageTitle: 'metrics.title'
+ }
+};
diff --git a/jhipster/src/main/webapp/app/admin/metrics/metrics.service.ts b/jhipster/src/main/webapp/app/admin/metrics/metrics.service.ts
new file mode 100644
index 000000000000..2b3194733902
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/metrics/metrics.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class JhiMetricsService {
+
+ constructor (private http: Http) {}
+
+ getMetrics(): Observable {
+ return this.http.get('management/metrics').map((res: Response) => res.json());
+ }
+
+ threadDump(): Observable {
+ return this.http.get('management/dump').map((res: Response) => res.json());
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html b/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html
new file mode 100644
index 000000000000..452f1612c8d0
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.html
@@ -0,0 +1,19 @@
+
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts
new file mode 100644
index 000000000000..2c5c2a6a0135
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-delete-dialog.component.ts
@@ -0,0 +1,63 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { EventManager, JhiLanguageService } from 'ng-jhipster';
+
+import { User, UserService } from '../../shared';
+import { UserModalService } from './user-modal.service';
+
+@Component({
+ selector: 'jhi-user-mgmt-delete-dialog',
+ templateUrl: './user-management-delete-dialog.component.html'
+})
+export class UserMgmtDeleteDialogComponent {
+
+ user: User;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private userService: UserService,
+ public activeModal: NgbActiveModal,
+ private eventManager: EventManager
+ ) {
+ this.jhiLanguageService.setLocations(['user-management']);
+ }
+
+ clear () {
+ this.activeModal.dismiss('cancel');
+ }
+
+ confirmDelete (login) {
+ this.userService.delete(login).subscribe(response => {
+ this.eventManager.broadcast({ name: 'userListModification',
+ content: 'Deleted a user'});
+ this.activeModal.dismiss(true);
+ });
+ }
+
+}
+
+@Component({
+ selector: 'jhi-user-delete-dialog',
+ template: ''
+})
+export class UserDeleteDialogComponent implements OnInit, OnDestroy {
+
+ modalRef: NgbModalRef;
+ routeSub: any;
+
+ constructor (
+ private route: ActivatedRoute,
+ private userModalService: UserModalService
+ ) {}
+
+ ngOnInit() {
+ this.routeSub = this.route.params.subscribe(params => {
+ this.modalRef = this.userModalService.open(UserMgmtDeleteDialogComponent, params['login']);
+ });
+ }
+
+ ngOnDestroy() {
+ this.routeSub.unsubscribe();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.html b/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.html
new file mode 100644
index 000000000000..d5deb8fa24ea
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.html
@@ -0,0 +1,44 @@
+
+
+ User [{{user.login}} ]
+
+
+ Login
+
+ {{user.login}}
+ Deactivated
+ Activated
+
+ First Name
+ {{user.firstName}}
+ Last Name
+ {{user.lastName}}
+ Email
+ {{user.email}}
+ Lang Key
+ {{user.langKey}}
+ Created By
+ {{user.createdBy}}
+ Created Date
+ {{user.createdDate | date:'dd/MM/yy HH:mm' }}
+ Last Modified By
+ {{user.lastModifiedBy}}
+ Last Modified Date
+ {{user.lastModifiedDate | date:'dd/MM/yy HH:mm'}}
+ Profiles
+
+
+
+
+
+ Back
+
+
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.ts
new file mode 100644
index 000000000000..564b1daf3d7c
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-detail.component.ts
@@ -0,0 +1,40 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { User, UserService } from '../../shared';
+
+@Component({
+ selector: 'jhi-user-mgmt-detail',
+ templateUrl: './user-management-detail.component.html'
+})
+export class UserMgmtDetailComponent implements OnInit, OnDestroy {
+
+ user: User;
+ private subscription: any;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private userService: UserService,
+ private route: ActivatedRoute
+ ) {
+ this.jhiLanguageService.setLocations(['user-management']);
+ }
+
+ ngOnInit() {
+ this.subscription = this.route.params.subscribe(params => {
+ this.load(params['login']);
+ });
+ }
+
+ load (login) {
+ this.userService.find(login).subscribe(user => {
+ this.user = user;
+ });
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+
+}
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.html b/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.html
new file mode 100644
index 000000000000..6359527df74e
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.html
@@ -0,0 +1,112 @@
+
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.ts
new file mode 100644
index 000000000000..ebbf073df48d
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management-dialog.component.ts
@@ -0,0 +1,89 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+
+import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { EventManager, JhiLanguageService } from 'ng-jhipster';
+
+import { UserModalService } from './user-modal.service';
+import { JhiLanguageHelper, User, UserService } from '../../shared';
+
+@Component({
+ selector: 'jhi-user-mgmt-dialog',
+ templateUrl: './user-management-dialog.component.html'
+})
+export class UserMgmtDialogComponent implements OnInit {
+
+ user: User;
+ languages: any[];
+ authorities: any[];
+ isSaving: Boolean;
+
+ constructor (
+ public activeModal: NgbActiveModal,
+ private languageHelper: JhiLanguageHelper,
+ private jhiLanguageService: JhiLanguageService,
+ private userService: UserService,
+ private eventManager: EventManager
+ ) {}
+
+ ngOnInit() {
+ this.isSaving = false;
+ this.authorities = ['ROLE_USER', 'ROLE_ADMIN'];
+ this.languageHelper.getAll().then((languages) => {
+ this.languages = languages;
+ });
+ this.jhiLanguageService.setLocations(['user-management']);
+ }
+
+ clear() {
+ this.activeModal.dismiss('cancel');
+ }
+
+ save() {
+ this.isSaving = true;
+ if (this.user.id !== null) {
+ this.userService.update(this.user).subscribe(response => this.onSaveSuccess(response), () => this.onSaveError());
+ } else {
+ this.userService.create(this.user).subscribe(response => this.onSaveSuccess(response), () => this.onSaveError());
+ }
+ }
+
+ private onSaveSuccess(result) {
+ this.eventManager.broadcast({ name: 'userListModification', content: 'OK' });
+ this.isSaving = false;
+ this.activeModal.dismiss(result);
+ }
+
+ private onSaveError() {
+ this.isSaving = false;
+ }
+}
+
+@Component({
+ selector: 'jhi-user-dialog',
+ template: ''
+})
+export class UserDialogComponent implements OnInit, OnDestroy {
+
+ modalRef: NgbModalRef;
+ routeSub: any;
+
+ constructor (
+ private route: ActivatedRoute,
+ private userModalService: UserModalService
+ ) {}
+
+ ngOnInit() {
+ this.routeSub = this.route.params.subscribe(params => {
+ if ( params['login'] ) {
+ this.modalRef = this.userModalService.open(UserMgmtDialogComponent, params['login']);
+ } else {
+ this.modalRef = this.userModalService.open(UserMgmtDialogComponent);
+ }
+ });
+ }
+
+ ngOnDestroy() {
+ this.routeSub.unsubscribe();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management.component.html b/jhipster/src/main/webapp/app/admin/user-management/user-management.component.html
new file mode 100644
index 000000000000..73fc18469aca
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management.component.html
@@ -0,0 +1,81 @@
+
+
+ Users
+
+ Create a new User
+
+
+
+
+
+
+
+ ID
+ Login
+ Email
+
+ Lang Key
+ Profiles
+ Created Date
+ Last Modified By
+ Last Modified Date
+
+
+
+
+
+ {{user.id}}
+ {{user.login}}
+ {{user.email}}
+
+ Deactivated
+ Activated
+
+ {{user.langKey}}
+
+
+ {{ authority }}
+
+
+ {{user.createdDate | date:'dd/MM/yy HH:mm'}}
+ {{user.lastModifiedBy}}
+ {{user.lastModifiedDate | date:'dd/MM/yy HH:mm'}}
+
+
+
+
+ View
+
+
+
+ Edit
+
+
+
+ Delete
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management.component.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management.component.ts
new file mode 100644
index 000000000000..213bb24923ce
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management.component.ts
@@ -0,0 +1,131 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Response } from '@angular/http';
+import { ActivatedRoute, Router } from '@angular/router';
+import { EventManager, PaginationUtil, ParseLinks, AlertService, JhiLanguageService } from 'ng-jhipster';
+
+import { ITEMS_PER_PAGE, Principal, User, UserService } from '../../shared';
+import { PaginationConfig } from '../../blocks/config/uib-pagination.config';
+
+@Component({
+ selector: 'jhi-user-mgmt',
+ templateUrl: './user-management.component.html'
+})
+export class UserMgmtComponent implements OnInit, OnDestroy {
+
+ currentAccount: any;
+ users: User[];
+ error: any;
+ success: any;
+ routeData: any;
+ links: any;
+ totalItems: any;
+ queryCount: any;
+ itemsPerPage: any;
+ page: any;
+ predicate: any;
+ previousPage: any;
+ reverse: any;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private userService: UserService,
+ private parseLinks: ParseLinks,
+ private alertService: AlertService,
+ private principal: Principal,
+ private eventManager: EventManager, private paginationUtil: PaginationUtil,
+ private paginationConfig: PaginationConfig,
+ private activatedRoute: ActivatedRoute,
+ private router: Router
+ ) {
+ this.itemsPerPage = ITEMS_PER_PAGE;
+ this.routeData = this.activatedRoute.data.subscribe(data => {
+ this.page = data['pagingParams'].page;
+ this.previousPage = data['pagingParams'].page;
+ this.reverse = data['pagingParams'].ascending;
+ this.predicate = data['pagingParams'].predicate;
+ });
+ this.jhiLanguageService.setLocations(['user-management']);
+ }
+
+ ngOnInit() {
+ this.principal.identity().then((account) => {
+ this.currentAccount = account;
+ this.loadAll();
+ this.registerChangeInUsers();
+ });
+ }
+
+ ngOnDestroy() {
+ this.routeData.unsubscribe();
+ }
+
+ registerChangeInUsers() {
+ this.eventManager.subscribe('userListModification', (response) => this.loadAll());
+ }
+
+ setActive (user, isActivated) {
+ user.activated = isActivated;
+
+ this.userService.update(user).subscribe(
+ response => {
+ if (response.status === 200) {
+ this.error = null;
+ this.success = 'OK';
+ this.loadAll();
+ } else {
+ this.success = null;
+ this.error = 'ERROR';
+ }
+ });
+ }
+
+ loadAll () {
+ this.userService.query({
+ page: this.page - 1,
+ size: this.itemsPerPage,
+ sort: this.sort()}).subscribe(
+ (res: Response) => this.onSuccess(res.json(), res.headers),
+ (res: Response) => this.onError(res.json())
+ );
+ }
+
+ trackIdentity (index, item: User) {
+ return item.id;
+ }
+
+ sort () {
+ let result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')];
+ if (this.predicate !== 'id') {
+ result.push('id');
+ }
+ return result;
+ }
+
+ loadPage (page: number) {
+ if (page !== this.previousPage) {
+ this.previousPage = page;
+ this.transition();
+ }
+ }
+
+ transition () {
+ this.router.navigate(['/user-management'], { queryParams:
+ {
+ page: this.page,
+ sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc')
+ }
+ });
+ this.loadAll();
+ }
+
+ private onSuccess(data, headers) {
+ this.links = this.parseLinks.parse(headers.get('link'));
+ this.totalItems = headers.get('X-Total-Count');
+ this.queryCount = this.totalItems;
+ this.users = data;
+ }
+
+ private onError(error) {
+ this.alertService.error(error.error, error.message, null);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-management.route.ts b/jhipster/src/main/webapp/app/admin/user-management/user-management.route.ts
new file mode 100644
index 000000000000..6aca0cdeb1c2
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-management.route.ts
@@ -0,0 +1,77 @@
+import { Injectable } from '@angular/core';
+import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Routes, CanActivate } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { PaginationUtil } from 'ng-jhipster';
+
+import { UserMgmtComponent } from './user-management.component';
+import { UserMgmtDetailComponent } from './user-management-detail.component';
+import { UserDialogComponent } from './user-management-dialog.component';
+import { UserDeleteDialogComponent } from './user-management-delete-dialog.component';
+
+import { Principal } from '../../shared';
+
+
+@Injectable()
+export class UserResolve implements CanActivate {
+
+ constructor(private principal: Principal) { }
+
+ canActivate() {
+ return this.principal.identity().then(account => this.principal.hasAnyAuthority(['ROLE_ADMIN']));
+ }
+}
+
+@Injectable()
+export class UserResolvePagingParams implements Resolve {
+
+ constructor(private paginationUtil: PaginationUtil) {}
+
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+ let page = route.queryParams['page'] ? route.queryParams['page'] : '1';
+ let sort = route.queryParams['sort'] ? route.queryParams['sort'] : 'id,asc';
+ return {
+ page: this.paginationUtil.parsePage(page),
+ predicate: this.paginationUtil.parsePredicate(sort),
+ ascending: this.paginationUtil.parseAscending(sort)
+ };
+ }
+}
+
+export const userMgmtRoute: Routes = [
+ {
+ path: 'user-management',
+ component: UserMgmtComponent,
+ resolve: {
+ 'pagingParams': UserResolvePagingParams
+ },
+ data: {
+ pageTitle: 'userManagement.home.title'
+ }
+ },
+ {
+ path: 'user-management/:login',
+ component: UserMgmtDetailComponent,
+ data: {
+ pageTitle: 'userManagement.home.title'
+ }
+ }
+];
+
+export const userDialogRoute: Routes = [
+ {
+ path: 'user-management-new',
+ component: UserDialogComponent,
+ outlet: 'popup'
+ },
+ {
+ path: 'user-management/:login/edit',
+ component: UserDialogComponent,
+ outlet: 'popup'
+ },
+ {
+ path: 'user-management/:login/delete',
+ component: UserDeleteDialogComponent,
+ outlet: 'popup'
+ }
+];
diff --git a/jhipster/src/main/webapp/app/admin/user-management/user-modal.service.ts b/jhipster/src/main/webapp/app/admin/user-management/user-modal.service.ts
new file mode 100644
index 000000000000..006bc2a53c1d
--- /dev/null
+++ b/jhipster/src/main/webapp/app/admin/user-management/user-modal.service.ts
@@ -0,0 +1,42 @@
+import { Injectable, Component } from '@angular/core';
+import { Router } from '@angular/router';
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+
+import { UserMgmtDialogComponent } from './user-management-dialog.component';
+import { User, UserService } from '../../shared';
+
+@Injectable()
+export class UserModalService {
+ private isOpen = false;
+ constructor (
+ private modalService: NgbModal,
+ private router: Router,
+ private userService: UserService
+ ) {}
+
+ open (component: Component, login?: string): NgbModalRef {
+ if (this.isOpen) {
+ return;
+ }
+ this.isOpen = true;
+
+ if (login) {
+ this.userService.find(login).subscribe(user => this.userModalRef(component, user));
+ } else {
+ return this.userModalRef(component, new User());
+ }
+ }
+
+ userModalRef(component: Component, user: User): NgbModalRef {
+ let modalRef = this.modalService.open(component, { size: 'lg', backdrop: 'static'});
+ modalRef.componentInstance.user = user;
+ modalRef.result.then(result => {
+ this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true });
+ this.isOpen = false;
+ }, (reason) => {
+ this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true });
+ this.isOpen = false;
+ });
+ return modalRef;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/app.constants.ts b/jhipster/src/main/webapp/app/app.constants.ts
new file mode 100644
index 000000000000..8f53b6f2e830
--- /dev/null
+++ b/jhipster/src/main/webapp/app/app.constants.ts
@@ -0,0 +1,7 @@
+// DO NOT EDIT THIS FILE, EDIT THE WEBPACK COMMON CONFIG INSTEAD, WHICH WILL MODIFY THIS FILE
+let _VERSION = '0.0.0'; // This value will be overwritten by webpack
+let _DEBUG_INFO_ENABLED = true; // This value will be overwritten by webpack
+/* @toreplace VERSION */
+/* @toreplace DEBUG_INFO_ENABLED */
+export const VERSION = _VERSION;
+export const DEBUG_INFO_ENABLED = _DEBUG_INFO_ENABLED;
diff --git a/jhipster/src/main/webapp/app/app.main.ts b/jhipster/src/main/webapp/app/app.main.ts
new file mode 100644
index 000000000000..301e5ffb81f3
--- /dev/null
+++ b/jhipster/src/main/webapp/app/app.main.ts
@@ -0,0 +1,11 @@
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { ProdConfig } from './blocks/config/prod.config';
+import { BaeldungAppModule } from './app.module';
+
+ProdConfig();
+
+if (module['hot']) {
+ module['hot'].accept();
+}
+
+platformBrowserDynamic().bootstrapModule(BaeldungAppModule);
diff --git a/jhipster/src/main/webapp/app/app.module.ts b/jhipster/src/main/webapp/app/app.module.ts
new file mode 100644
index 000000000000..dc82c6cdce88
--- /dev/null
+++ b/jhipster/src/main/webapp/app/app.module.ts
@@ -0,0 +1,58 @@
+import './vendor.ts';
+
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { BrowserModule } from '@angular/platform-browser';
+import { Ng2Webstorage } from 'ng2-webstorage';
+
+import { BaeldungSharedModule, UserRouteAccessService } from './shared';
+import { BaeldungHomeModule } from './home/home.module';
+import { BaeldungAdminModule } from './admin/admin.module';
+import { BaeldungAccountModule } from './account/account.module';
+import { BaeldungEntityModule } from './entities/entity.module';
+
+import { LayoutRoutingModule } from './layouts';
+import { customHttpProvider } from './blocks/interceptor/http.provider';
+import { PaginationConfig } from './blocks/config/uib-pagination.config';
+
+import {
+ JhiMainComponent,
+ NavbarComponent,
+ FooterComponent,
+ ProfileService,
+ PageRibbonComponent,
+ ActiveMenuDirective,
+ ErrorComponent
+} from './layouts';
+
+
+@NgModule({
+ imports: [
+ BrowserModule,
+ LayoutRoutingModule,
+ Ng2Webstorage.forRoot({ prefix: 'jhi', separator: '-'}),
+ BaeldungSharedModule,
+ BaeldungHomeModule,
+ BaeldungAdminModule,
+ BaeldungAccountModule,
+ BaeldungEntityModule
+ ],
+ declarations: [
+ JhiMainComponent,
+ NavbarComponent,
+ ErrorComponent,
+ PageRibbonComponent,
+ ActiveMenuDirective,
+ FooterComponent
+ ],
+ providers: [
+ ProfileService,
+ { provide: Window, useValue: window },
+ { provide: Document, useValue: document },
+ customHttpProvider(),
+ PaginationConfig,
+ UserRouteAccessService
+ ],
+ bootstrap: [ JhiMainComponent ]
+})
+export class BaeldungAppModule {}
diff --git a/jhipster/src/main/webapp/app/app.route.ts b/jhipster/src/main/webapp/app/app.route.ts
new file mode 100644
index 000000000000..bfddd9bb7e3b
--- /dev/null
+++ b/jhipster/src/main/webapp/app/app.route.ts
@@ -0,0 +1,9 @@
+import { Route } from '@angular/router';
+
+import { NavbarComponent } from './layouts';
+
+export const navbarRoute: Route = {
+ path: '',
+ component: NavbarComponent,
+ outlet: 'navbar'
+ };
diff --git a/jhipster/src/main/webapp/app/blocks/config/prod.config.ts b/jhipster/src/main/webapp/app/blocks/config/prod.config.ts
new file mode 100644
index 000000000000..cc0050de2790
--- /dev/null
+++ b/jhipster/src/main/webapp/app/blocks/config/prod.config.ts
@@ -0,0 +1,9 @@
+import { enableProdMode } from '@angular/core';
+import { DEBUG_INFO_ENABLED } from '../../app.constants';
+
+export function ProdConfig() {
+ // disable debug data on prod profile to improve performance
+ if (!DEBUG_INFO_ENABLED) {
+ enableProdMode();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/blocks/config/uib-pagination.config.ts b/jhipster/src/main/webapp/app/blocks/config/uib-pagination.config.ts
new file mode 100644
index 000000000000..245e1f6bf141
--- /dev/null
+++ b/jhipster/src/main/webapp/app/blocks/config/uib-pagination.config.ts
@@ -0,0 +1,13 @@
+import { ITEMS_PER_PAGE } from '../../shared';
+import { Injectable } from '@angular/core';
+import { NgbPaginationConfig} from '@ng-bootstrap/ng-bootstrap';
+
+@Injectable()
+export class PaginationConfig {
+ constructor(private config: NgbPaginationConfig) {
+ config.boundaryLinks = true;
+ config.maxSize = 5;
+ config.pageSize = ITEMS_PER_PAGE;
+ config.size = 'sm';
+ }
+}
diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts b/jhipster/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts
new file mode 100644
index 000000000000..49f517148cb0
--- /dev/null
+++ b/jhipster/src/main/webapp/app/blocks/interceptor/auth-expired.interceptor.ts
@@ -0,0 +1,34 @@
+import { HttpInterceptor } from 'ng-jhipster';
+import { RequestOptionsArgs, Response } from '@angular/http';
+import { Observable } from 'rxjs/Observable';
+import { Injector } from '@angular/core';
+import { AuthService } from '../../shared/auth/auth.service';
+import { Principal } from '../../shared/auth/principal.service';
+import { AuthServerProvider } from '../../shared/auth/auth-jwt.service';
+
+export class AuthExpiredInterceptor extends HttpInterceptor {
+
+ constructor(private injector: Injector) {
+ super();
+ }
+
+ requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs {
+ return options;
+ }
+
+ responseIntercept(observable: Observable): Observable {
+ let self = this;
+
+ return > observable.catch((error, source) => {
+ if (error.status === 401) {
+ let principal: Principal = self.injector.get(Principal);
+
+ if (principal.isAuthenticated()) {
+ let auth: AuthService = self.injector.get(AuthService);
+ auth.authorize(true);
+ }
+ }
+ return Observable.throw(error);
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts b/jhipster/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts
new file mode 100644
index 000000000000..cf9028411602
--- /dev/null
+++ b/jhipster/src/main/webapp/app/blocks/interceptor/auth.interceptor.ts
@@ -0,0 +1,27 @@
+import { Observable } from 'rxjs/Observable';
+import { RequestOptionsArgs, Response } from '@angular/http';
+import { LocalStorageService, SessionStorageService } from 'ng2-webstorage';
+import { HttpInterceptor } from 'ng-jhipster';
+
+export class AuthInterceptor extends HttpInterceptor {
+
+ constructor(
+ private localStorage: LocalStorageService,
+ private sessionStorage: SessionStorageService
+ ) {
+ super();
+ }
+
+ requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs {
+ let token = this.localStorage.retrieve('authenticationToken') || this.sessionStorage.retrieve('authenticationToken');
+ if (!!token) {
+ options.headers.append('Authorization', 'Bearer ' + token);
+ }
+ return options;
+ }
+
+ responseIntercept(observable: Observable): Observable {
+ return observable; // by pass
+ }
+
+}
diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts b/jhipster/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts
new file mode 100644
index 000000000000..6e22557d051b
--- /dev/null
+++ b/jhipster/src/main/webapp/app/blocks/interceptor/errorhandler.interceptor.ts
@@ -0,0 +1,24 @@
+import { HttpInterceptor, EventManager } from 'ng-jhipster';
+import { RequestOptionsArgs, Response } from '@angular/http';
+import { Observable } from 'rxjs/Observable';
+
+export class ErrorHandlerInterceptor extends HttpInterceptor {
+
+ constructor(private eventManager: EventManager) {
+ super();
+ }
+
+ requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs {
+ return options;
+ }
+
+ responseIntercept(observable: Observable): Observable {
+ return > observable.catch(error => {
+ if (!(error.status === 401 && (error.text() === '' ||
+ (error.json().path && error.json().path.indexOf('/api/account') === 0 )))) {
+ this.eventManager.broadcast( {name: 'baeldungApp.httpError', content: error});
+ }
+ return Observable.throw(error);
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/http.provider.ts b/jhipster/src/main/webapp/app/blocks/interceptor/http.provider.ts
new file mode 100644
index 000000000000..a9689662a355
--- /dev/null
+++ b/jhipster/src/main/webapp/app/blocks/interceptor/http.provider.ts
@@ -0,0 +1,45 @@
+import { Injector } from '@angular/core';
+import { Http, XHRBackend, RequestOptions } from '@angular/http';
+import { EventManager, InterceptableHttp } from 'ng-jhipster';
+
+import { AuthInterceptor } from './auth.interceptor';
+import { LocalStorageService, SessionStorageService } from 'ng2-webstorage';
+import { AuthExpiredInterceptor } from './auth-expired.interceptor';
+import { ErrorHandlerInterceptor } from './errorhandler.interceptor';
+import { NotificationInterceptor } from './notification.interceptor';
+
+export function interceptableFactory(
+ backend: XHRBackend,
+ defaultOptions: RequestOptions,
+ localStorage: LocalStorageService,
+ sessionStorage: SessionStorageService,
+ injector: Injector,
+ eventManager: EventManager
+) {
+ return new InterceptableHttp(
+ backend,
+ defaultOptions,
+ [
+ new AuthInterceptor(localStorage, sessionStorage),
+ new AuthExpiredInterceptor(injector),
+ // Other interceptors can be added here
+ new ErrorHandlerInterceptor(eventManager),
+ new NotificationInterceptor()
+ ]
+ );
+};
+
+export function customHttpProvider() {
+ return {
+ provide: Http,
+ useFactory: interceptableFactory,
+ deps: [
+ XHRBackend,
+ RequestOptions,
+ LocalStorageService,
+ SessionStorageService,
+ Injector,
+ EventManager
+ ]
+ };
+};
diff --git a/jhipster/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts b/jhipster/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts
new file mode 100644
index 000000000000..ebee7688c54d
--- /dev/null
+++ b/jhipster/src/main/webapp/app/blocks/interceptor/notification.interceptor.ts
@@ -0,0 +1,33 @@
+import { HttpInterceptor } from 'ng-jhipster';
+import { RequestOptionsArgs, Response } from '@angular/http';
+import { Observable } from 'rxjs/Observable';
+
+export class NotificationInterceptor extends HttpInterceptor {
+
+ constructor() {
+ super();
+ }
+
+ requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs {
+ return options;
+ }
+
+ responseIntercept(observable: Observable): Observable {
+ return > observable.catch((error) => {
+ let arr = Array.from(error.headers._headers);
+ let headers = [];
+ let i;
+ for (i = 0; i < arr.length; i++) {
+ if (arr[i][0].endsWith('app-alert') || arr[i][0].endsWith('app-params')) {
+ headers.push(arr[i][0]);
+ }
+ }
+ headers.sort();
+ let alertKey = headers.length >= 1 ? error.headers.get(headers[0]) : null;
+ if (typeof alertKey === 'string') {
+ // AlertService.success(alertKey, { param: response.headers(headers[1])});
+ }
+ return Observable.throw(error);
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.html b/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.html
new file mode 100644
index 000000000000..63540ec6bdf9
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.html
@@ -0,0 +1,19 @@
+
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.ts b/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.ts
new file mode 100644
index 000000000000..29ef818ddfd5
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment-delete-dialog.component.ts
@@ -0,0 +1,67 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+
+import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { EventManager, JhiLanguageService } from 'ng-jhipster';
+
+import { Comment } from './comment.model';
+import { CommentPopupService } from './comment-popup.service';
+import { CommentService } from './comment.service';
+
+@Component({
+ selector: 'jhi-comment-delete-dialog',
+ templateUrl: './comment-delete-dialog.component.html'
+})
+export class CommentDeleteDialogComponent {
+
+ comment: Comment;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private commentService: CommentService,
+ public activeModal: NgbActiveModal,
+ private eventManager: EventManager
+ ) {
+ this.jhiLanguageService.setLocations(['comment']);
+ }
+
+ clear () {
+ this.activeModal.dismiss('cancel');
+ }
+
+ confirmDelete (id: number) {
+ this.commentService.delete(id).subscribe(response => {
+ this.eventManager.broadcast({
+ name: 'commentListModification',
+ content: 'Deleted an comment'
+ });
+ this.activeModal.dismiss(true);
+ });
+ }
+}
+
+@Component({
+ selector: 'jhi-comment-delete-popup',
+ template: ''
+})
+export class CommentDeletePopupComponent implements OnInit, OnDestroy {
+
+ modalRef: NgbModalRef;
+ routeSub: any;
+
+ constructor (
+ private route: ActivatedRoute,
+ private commentPopupService: CommentPopupService
+ ) {}
+
+ ngOnInit() {
+ this.routeSub = this.route.params.subscribe(params => {
+ this.modalRef = this.commentPopupService
+ .open(CommentDeleteDialogComponent, params['id']);
+ });
+ }
+
+ ngOnDestroy() {
+ this.routeSub.unsubscribe();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.html b/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.html
new file mode 100644
index 000000000000..add5b9e20830
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.html
@@ -0,0 +1,35 @@
+
+
+
Comment {{comment.id}}
+
+
+
+ Text
+
+ {{comment.text}}
+
+ Creation Date
+
+ {{comment.creationDate | date:'mediumDate'}}
+
+ Post
+
+
+
+
+
+
+ Back
+
+
+
+ Edit
+
+
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.ts b/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.ts
new file mode 100644
index 000000000000..c2bf7fce0fd6
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment-detail.component.ts
@@ -0,0 +1,43 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { JhiLanguageService } from 'ng-jhipster';
+import { Comment } from './comment.model';
+import { CommentService } from './comment.service';
+
+@Component({
+ selector: 'jhi-comment-detail',
+ templateUrl: './comment-detail.component.html'
+})
+export class CommentDetailComponent implements OnInit, OnDestroy {
+
+ comment: Comment;
+ private subscription: any;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private commentService: CommentService,
+ private route: ActivatedRoute
+ ) {
+ this.jhiLanguageService.setLocations(['comment']);
+ }
+
+ ngOnInit() {
+ this.subscription = this.route.params.subscribe(params => {
+ this.load(params['id']);
+ });
+ }
+
+ load (id) {
+ this.commentService.find(id).subscribe(comment => {
+ this.comment = comment;
+ });
+ }
+ previousState() {
+ window.history.back();
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+
+}
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.html b/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.html
new file mode 100644
index 000000000000..7e13c3317721
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.html
@@ -0,0 +1,76 @@
+
+
+
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.ts b/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.ts
new file mode 100644
index 000000000000..1a30305c965a
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment-dialog.component.ts
@@ -0,0 +1,107 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Response } from '@angular/http';
+
+import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { EventManager, AlertService, JhiLanguageService } from 'ng-jhipster';
+
+import { Comment } from './comment.model';
+import { CommentPopupService } from './comment-popup.service';
+import { CommentService } from './comment.service';
+import { Post, PostService } from '../post';
+@Component({
+ selector: 'jhi-comment-dialog',
+ templateUrl: './comment-dialog.component.html'
+})
+export class CommentDialogComponent implements OnInit {
+
+ comment: Comment;
+ authorities: any[];
+ isSaving: boolean;
+
+ posts: Post[];
+ constructor(
+ public activeModal: NgbActiveModal,
+ private jhiLanguageService: JhiLanguageService,
+ private alertService: AlertService,
+ private commentService: CommentService,
+ private postService: PostService,
+ private eventManager: EventManager
+ ) {
+ this.jhiLanguageService.setLocations(['comment']);
+ }
+
+ ngOnInit() {
+ this.isSaving = false;
+ this.authorities = ['ROLE_USER', 'ROLE_ADMIN'];
+ this.postService.query().subscribe(
+ (res: Response) => { this.posts = res.json(); }, (res: Response) => this.onError(res.json()));
+ }
+ clear () {
+ this.activeModal.dismiss('cancel');
+ }
+
+ save () {
+ this.isSaving = true;
+ if (this.comment.id !== undefined) {
+ this.commentService.update(this.comment)
+ .subscribe((res: Comment) =>
+ this.onSaveSuccess(res), (res: Response) => this.onSaveError(res.json()));
+ } else {
+ this.commentService.create(this.comment)
+ .subscribe((res: Comment) =>
+ this.onSaveSuccess(res), (res: Response) => this.onSaveError(res.json()));
+ }
+ }
+
+ private onSaveSuccess (result: Comment) {
+ this.eventManager.broadcast({ name: 'commentListModification', content: 'OK'});
+ this.isSaving = false;
+ this.activeModal.dismiss(result);
+ }
+
+ private onSaveError (error) {
+ this.isSaving = false;
+ this.onError(error);
+ }
+
+ private onError (error) {
+ this.alertService.error(error.message, null, null);
+ }
+
+ trackPostById(index: number, item: Post) {
+ return item.id;
+ }
+}
+
+@Component({
+ selector: 'jhi-comment-popup',
+ template: ''
+})
+export class CommentPopupComponent implements OnInit, OnDestroy {
+
+ modalRef: NgbModalRef;
+ routeSub: any;
+
+ constructor (
+ private route: ActivatedRoute,
+ private commentPopupService: CommentPopupService
+ ) {}
+
+ ngOnInit() {
+ this.routeSub = this.route.params.subscribe(params => {
+ if ( params['id'] ) {
+ this.modalRef = this.commentPopupService
+ .open(CommentDialogComponent, params['id']);
+ } else {
+ this.modalRef = this.commentPopupService
+ .open(CommentDialogComponent);
+ }
+
+ });
+ }
+
+ ngOnDestroy() {
+ this.routeSub.unsubscribe();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment-popup.service.ts b/jhipster/src/main/webapp/app/entities/comment/comment-popup.service.ts
new file mode 100644
index 000000000000..a1c0ce11bf30
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment-popup.service.ts
@@ -0,0 +1,50 @@
+import { Injectable, Component } from '@angular/core';
+import { Router } from '@angular/router';
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { Comment } from './comment.model';
+import { CommentService } from './comment.service';
+@Injectable()
+export class CommentPopupService {
+ private isOpen = false;
+ constructor (
+ private modalService: NgbModal,
+ private router: Router,
+ private commentService: CommentService
+
+ ) {}
+
+ open (component: Component, id?: number | any): NgbModalRef {
+ if (this.isOpen) {
+ return;
+ }
+ this.isOpen = true;
+
+ if (id) {
+ this.commentService.find(id).subscribe(comment => {
+ if (comment.creationDate) {
+ comment.creationDate = {
+ year: comment.creationDate.getFullYear(),
+ month: comment.creationDate.getMonth() + 1,
+ day: comment.creationDate.getDate()
+ };
+ }
+ this.commentModalRef(component, comment);
+ });
+ } else {
+ return this.commentModalRef(component, new Comment());
+ }
+ }
+
+ commentModalRef(component: Component, comment: Comment): NgbModalRef {
+ let modalRef = this.modalService.open(component, { size: 'lg', backdrop: 'static'});
+ modalRef.componentInstance.comment = comment;
+ modalRef.result.then(result => {
+ this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true });
+ this.isOpen = false;
+ }, (reason) => {
+ this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true });
+ this.isOpen = false;
+ });
+ return modalRef;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.component.html b/jhipster/src/main/webapp/app/entities/comment/comment.component.html
new file mode 100644
index 000000000000..1efd1ce37991
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment.component.html
@@ -0,0 +1,64 @@
+
+
+ Comments
+
+
+
+
+
+
+
+
+
+
+ ID
+ Text
+ Creation Date
+ Post
+
+
+
+ = links['last']" [infiniteScrollDistance]="0">
+
+ {{comment.id}}
+ {{comment.text}}
+ {{comment.creationDate | date:'mediumDate'}}
+
+
+
+
+
+
+
+ View
+
+
+
+ Edit
+
+
+
+ Delete
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.component.ts b/jhipster/src/main/webapp/app/entities/comment/comment.component.ts
new file mode 100644
index 000000000000..4e009d477480
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment.component.ts
@@ -0,0 +1,110 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Response } from '@angular/http';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Subscription } from 'rxjs/Rx';
+import { EventManager, ParseLinks, PaginationUtil, JhiLanguageService, AlertService } from 'ng-jhipster';
+
+import { Comment } from './comment.model';
+import { CommentService } from './comment.service';
+import { ITEMS_PER_PAGE, Principal } from '../../shared';
+import { PaginationConfig } from '../../blocks/config/uib-pagination.config';
+
+@Component({
+ selector: 'jhi-comment',
+ templateUrl: './comment.component.html'
+})
+export class CommentComponent implements OnInit, OnDestroy {
+
+ comments: Comment[];
+ currentAccount: any;
+ eventSubscriber: Subscription;
+ itemsPerPage: number;
+ links: any;
+ page: any;
+ predicate: any;
+ queryCount: any;
+ reverse: any;
+ totalItems: number;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private commentService: CommentService,
+ private alertService: AlertService,
+ private eventManager: EventManager,
+ private parseLinks: ParseLinks,
+ private principal: Principal
+ ) {
+ this.comments = [];
+ this.itemsPerPage = ITEMS_PER_PAGE;
+ this.page = 0;
+ this.links = {
+ last: 0
+ };
+ this.predicate = 'id';
+ this.reverse = true;
+ this.jhiLanguageService.setLocations(['comment']);
+ }
+
+ loadAll () {
+ this.commentService.query({
+ page: this.page,
+ size: this.itemsPerPage,
+ sort: this.sort()
+ }).subscribe(
+ (res: Response) => this.onSuccess(res.json(), res.headers),
+ (res: Response) => this.onError(res.json())
+ );
+ }
+
+ reset () {
+ this.page = 0;
+ this.comments = [];
+ this.loadAll();
+ }
+
+ loadPage(page) {
+ this.page = page;
+ this.loadAll();
+ }
+ ngOnInit() {
+ this.loadAll();
+ this.principal.identity().then((account) => {
+ this.currentAccount = account;
+ });
+ this.registerChangeInComments();
+ }
+
+ ngOnDestroy() {
+ this.eventManager.destroy(this.eventSubscriber);
+ }
+
+ trackId (index: number, item: Comment) {
+ return item.id;
+ }
+
+
+
+ registerChangeInComments() {
+ this.eventSubscriber = this.eventManager.subscribe('commentListModification', (response) => this.reset());
+ }
+
+ sort () {
+ let result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')];
+ if (this.predicate !== 'id') {
+ result.push('id');
+ }
+ return result;
+ }
+
+ private onSuccess(data, headers) {
+ this.links = this.parseLinks.parse(headers.get('link'));
+ this.totalItems = headers.get('X-Total-Count');
+ for (let i = 0; i < data.length; i++) {
+ this.comments.push(data[i]);
+ }
+ }
+
+ private onError (error) {
+ this.alertService.error(error.message, null, null);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.model.ts b/jhipster/src/main/webapp/app/entities/comment/comment.model.ts
new file mode 100644
index 000000000000..66df7d0b34e9
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment.model.ts
@@ -0,0 +1,10 @@
+import { Post } from '../post';
+export class Comment {
+ constructor(
+ public id?: number,
+ public text?: string,
+ public creationDate?: any,
+ public post?: Post,
+ ) {
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.module.ts b/jhipster/src/main/webapp/app/entities/comment/comment.module.ts
new file mode 100644
index 000000000000..1f3167274e07
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment.module.ts
@@ -0,0 +1,50 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterModule } from '@angular/router';
+
+import { BaeldungSharedModule } from '../../shared';
+
+import {
+ CommentService,
+ CommentPopupService,
+ CommentComponent,
+ CommentDetailComponent,
+ CommentDialogComponent,
+ CommentPopupComponent,
+ CommentDeletePopupComponent,
+ CommentDeleteDialogComponent,
+ commentRoute,
+ commentPopupRoute,
+} from './';
+
+let ENTITY_STATES = [
+ ...commentRoute,
+ ...commentPopupRoute,
+];
+
+@NgModule({
+ imports: [
+ BaeldungSharedModule,
+ RouterModule.forRoot(ENTITY_STATES, { useHash: true })
+ ],
+ declarations: [
+ CommentComponent,
+ CommentDetailComponent,
+ CommentDialogComponent,
+ CommentDeleteDialogComponent,
+ CommentPopupComponent,
+ CommentDeletePopupComponent,
+ ],
+ entryComponents: [
+ CommentComponent,
+ CommentDialogComponent,
+ CommentPopupComponent,
+ CommentDeleteDialogComponent,
+ CommentDeletePopupComponent,
+ ],
+ providers: [
+ CommentService,
+ CommentPopupService,
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class BaeldungCommentModule {}
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.route.ts b/jhipster/src/main/webapp/app/entities/comment/comment.route.ts
new file mode 100644
index 000000000000..bb0e5e414165
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment.route.ts
@@ -0,0 +1,61 @@
+import { Injectable } from '@angular/core';
+import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Routes, CanActivate } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { PaginationUtil } from 'ng-jhipster';
+
+import { CommentComponent } from './comment.component';
+import { CommentDetailComponent } from './comment-detail.component';
+import { CommentPopupComponent } from './comment-dialog.component';
+import { CommentDeletePopupComponent } from './comment-delete-dialog.component';
+
+import { Principal } from '../../shared';
+
+
+export const commentRoute: Routes = [
+ {
+ path: 'comment',
+ component: CommentComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.comment.home.title'
+ }
+ }, {
+ path: 'comment/:id',
+ component: CommentDetailComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.comment.home.title'
+ }
+ }
+];
+
+export const commentPopupRoute: Routes = [
+ {
+ path: 'comment-new',
+ component: CommentPopupComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.comment.home.title'
+ },
+ outlet: 'popup'
+ },
+ {
+ path: 'comment/:id/edit',
+ component: CommentPopupComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.comment.home.title'
+ },
+ outlet: 'popup'
+ },
+ {
+ path: 'comment/:id/delete',
+ component: CommentDeletePopupComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.comment.home.title'
+ },
+ outlet: 'popup'
+ }
+];
diff --git a/jhipster/src/main/webapp/app/entities/comment/comment.service.ts b/jhipster/src/main/webapp/app/entities/comment/comment.service.ts
new file mode 100644
index 000000000000..b6121b3b1033
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/comment.service.ts
@@ -0,0 +1,78 @@
+import { Injectable } from '@angular/core';
+import { Http, Response, URLSearchParams, BaseRequestOptions } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+import { Comment } from './comment.model';
+import { DateUtils } from 'ng-jhipster';
+@Injectable()
+export class CommentService {
+
+ private resourceUrl = 'api/comments';
+
+ constructor(private http: Http, private dateUtils: DateUtils) { }
+
+ create(comment: Comment): Observable {
+ let copy: Comment = Object.assign({}, comment);
+ copy.creationDate = this.dateUtils
+ .convertLocalDateToServer(comment.creationDate);
+ return this.http.post(this.resourceUrl, copy).map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ update(comment: Comment): Observable {
+ let copy: Comment = Object.assign({}, comment);
+ copy.creationDate = this.dateUtils
+ .convertLocalDateToServer(comment.creationDate);
+ return this.http.put(this.resourceUrl, copy).map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ find(id: number): Observable {
+ return this.http.get(`${this.resourceUrl}/${id}`).map((res: Response) => {
+ let jsonResponse = res.json();
+ jsonResponse.creationDate = this.dateUtils
+ .convertLocalDateFromServer(jsonResponse.creationDate);
+ return jsonResponse;
+ });
+ }
+
+ query(req?: any): Observable {
+ let options = this.createRequestOption(req);
+ return this.http.get(this.resourceUrl, options)
+ .map((res: any) => this.convertResponse(res))
+ ;
+ }
+
+ delete(id: number): Observable {
+ return this.http.delete(`${this.resourceUrl}/${id}`);
+ }
+
+
+ private convertResponse(res: any): any {
+ let jsonResponse = res.json();
+ for (let i = 0; i < jsonResponse.length; i++) {
+ jsonResponse[i].creationDate = this.dateUtils
+ .convertLocalDateFromServer(jsonResponse[i].creationDate);
+ }
+ res._body = jsonResponse;
+ return res;
+ }
+
+ private createRequestOption(req?: any): BaseRequestOptions {
+ let options: BaseRequestOptions = new BaseRequestOptions();
+ if (req) {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('page', req.page);
+ params.set('size', req.size);
+ if (req.sort) {
+ params.paramsMap.set('sort', req.sort);
+ }
+ params.set('query', req.query);
+
+ options.search = params;
+ }
+ return options;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/comment/index.ts b/jhipster/src/main/webapp/app/entities/comment/index.ts
new file mode 100644
index 000000000000..5e54fb6099a5
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/comment/index.ts
@@ -0,0 +1,8 @@
+export * from './comment.model';
+export * from './comment-popup.service';
+export * from './comment.service';
+export * from './comment-dialog.component';
+export * from './comment-delete-dialog.component';
+export * from './comment-detail.component';
+export * from './comment.component';
+export * from './comment.route';
diff --git a/jhipster/src/main/webapp/app/entities/entity.module.ts b/jhipster/src/main/webapp/app/entities/entity.module.ts
new file mode 100644
index 000000000000..ba044e1902fc
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/entity.module.ts
@@ -0,0 +1,18 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+
+import { BaeldungPostModule } from './post/post.module';
+import { BaeldungCommentModule } from './comment/comment.module';
+/* jhipster-needle-add-entity-module-import - JHipster will add entity modules imports here */
+
+@NgModule({
+ imports: [
+ BaeldungPostModule,
+ BaeldungCommentModule,
+ /* jhipster-needle-add-entity-module - JHipster will add entity modules here */
+ ],
+ declarations: [],
+ entryComponents: [],
+ providers: [],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class BaeldungEntityModule {}
diff --git a/jhipster/src/main/webapp/app/entities/post/index.ts b/jhipster/src/main/webapp/app/entities/post/index.ts
new file mode 100644
index 000000000000..9375e11bd8bb
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/index.ts
@@ -0,0 +1,8 @@
+export * from './post.model';
+export * from './post-popup.service';
+export * from './post.service';
+export * from './post-dialog.component';
+export * from './post-delete-dialog.component';
+export * from './post-detail.component';
+export * from './post.component';
+export * from './post.route';
diff --git a/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.html b/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.html
new file mode 100644
index 000000000000..901fc1968eef
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.html
@@ -0,0 +1,19 @@
+
diff --git a/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.ts b/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.ts
new file mode 100644
index 000000000000..5e1413fd95c5
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post-delete-dialog.component.ts
@@ -0,0 +1,67 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+
+import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { EventManager, JhiLanguageService } from 'ng-jhipster';
+
+import { Post } from './post.model';
+import { PostPopupService } from './post-popup.service';
+import { PostService } from './post.service';
+
+@Component({
+ selector: 'jhi-post-delete-dialog',
+ templateUrl: './post-delete-dialog.component.html'
+})
+export class PostDeleteDialogComponent {
+
+ post: Post;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private postService: PostService,
+ public activeModal: NgbActiveModal,
+ private eventManager: EventManager
+ ) {
+ this.jhiLanguageService.setLocations(['post']);
+ }
+
+ clear () {
+ this.activeModal.dismiss('cancel');
+ }
+
+ confirmDelete (id: number) {
+ this.postService.delete(id).subscribe(response => {
+ this.eventManager.broadcast({
+ name: 'postListModification',
+ content: 'Deleted an post'
+ });
+ this.activeModal.dismiss(true);
+ });
+ }
+}
+
+@Component({
+ selector: 'jhi-post-delete-popup',
+ template: ''
+})
+export class PostDeletePopupComponent implements OnInit, OnDestroy {
+
+ modalRef: NgbModalRef;
+ routeSub: any;
+
+ constructor (
+ private route: ActivatedRoute,
+ private postPopupService: PostPopupService
+ ) {}
+
+ ngOnInit() {
+ this.routeSub = this.route.params.subscribe(params => {
+ this.modalRef = this.postPopupService
+ .open(PostDeleteDialogComponent, params['id']);
+ });
+ }
+
+ ngOnDestroy() {
+ this.routeSub.unsubscribe();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/post/post-detail.component.html b/jhipster/src/main/webapp/app/entities/post/post-detail.component.html
new file mode 100644
index 000000000000..8edcbee37584
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post-detail.component.html
@@ -0,0 +1,37 @@
+
+
+
Post {{post.id}}
+
+
+
+ Title
+
+ {{post.title}}
+
+ Content
+
+ {{post.content}}
+
+ Creation Date
+
+ {{post.creationDate | date:'mediumDate'}}
+
+ Creator
+
+ {{post.creator?.login}}
+
+
+
+
+ Back
+
+
+
+ Edit
+
+
diff --git a/jhipster/src/main/webapp/app/entities/post/post-detail.component.ts b/jhipster/src/main/webapp/app/entities/post/post-detail.component.ts
new file mode 100644
index 000000000000..679592f3b85b
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post-detail.component.ts
@@ -0,0 +1,43 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { JhiLanguageService } from 'ng-jhipster';
+import { Post } from './post.model';
+import { PostService } from './post.service';
+
+@Component({
+ selector: 'jhi-post-detail',
+ templateUrl: './post-detail.component.html'
+})
+export class PostDetailComponent implements OnInit, OnDestroy {
+
+ post: Post;
+ private subscription: any;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private postService: PostService,
+ private route: ActivatedRoute
+ ) {
+ this.jhiLanguageService.setLocations(['post']);
+ }
+
+ ngOnInit() {
+ this.subscription = this.route.params.subscribe(params => {
+ this.load(params['id']);
+ });
+ }
+
+ load (id) {
+ this.postService.find(id).subscribe(post => {
+ this.post = post;
+ });
+ }
+ previousState() {
+ window.history.back();
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+
+}
diff --git a/jhipster/src/main/webapp/app/entities/post/post-dialog.component.html b/jhipster/src/main/webapp/app/entities/post/post-dialog.component.html
new file mode 100644
index 000000000000..2216dcd451da
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post-dialog.component.html
@@ -0,0 +1,96 @@
+
+
+
diff --git a/jhipster/src/main/webapp/app/entities/post/post-dialog.component.ts b/jhipster/src/main/webapp/app/entities/post/post-dialog.component.ts
new file mode 100644
index 000000000000..803b76fc7839
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post-dialog.component.ts
@@ -0,0 +1,107 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Response } from '@angular/http';
+
+import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { EventManager, AlertService, JhiLanguageService } from 'ng-jhipster';
+
+import { Post } from './post.model';
+import { PostPopupService } from './post-popup.service';
+import { PostService } from './post.service';
+import { User, UserService } from '../../shared';
+@Component({
+ selector: 'jhi-post-dialog',
+ templateUrl: './post-dialog.component.html'
+})
+export class PostDialogComponent implements OnInit {
+
+ post: Post;
+ authorities: any[];
+ isSaving: boolean;
+
+ users: User[];
+ constructor(
+ public activeModal: NgbActiveModal,
+ private jhiLanguageService: JhiLanguageService,
+ private alertService: AlertService,
+ private postService: PostService,
+ private userService: UserService,
+ private eventManager: EventManager
+ ) {
+ this.jhiLanguageService.setLocations(['post']);
+ }
+
+ ngOnInit() {
+ this.isSaving = false;
+ this.authorities = ['ROLE_USER', 'ROLE_ADMIN'];
+ this.userService.query().subscribe(
+ (res: Response) => { this.users = res.json(); }, (res: Response) => this.onError(res.json()));
+ }
+ clear () {
+ this.activeModal.dismiss('cancel');
+ }
+
+ save () {
+ this.isSaving = true;
+ if (this.post.id !== undefined) {
+ this.postService.update(this.post)
+ .subscribe((res: Post) =>
+ this.onSaveSuccess(res), (res: Response) => this.onSaveError(res.json()));
+ } else {
+ this.postService.create(this.post)
+ .subscribe((res: Post) =>
+ this.onSaveSuccess(res), (res: Response) => this.onSaveError(res.json()));
+ }
+ }
+
+ private onSaveSuccess (result: Post) {
+ this.eventManager.broadcast({ name: 'postListModification', content: 'OK'});
+ this.isSaving = false;
+ this.activeModal.dismiss(result);
+ }
+
+ private onSaveError (error) {
+ this.isSaving = false;
+ this.onError(error);
+ }
+
+ private onError (error) {
+ this.alertService.error(error.message, null, null);
+ }
+
+ trackUserById(index: number, item: User) {
+ return item.id;
+ }
+}
+
+@Component({
+ selector: 'jhi-post-popup',
+ template: ''
+})
+export class PostPopupComponent implements OnInit, OnDestroy {
+
+ modalRef: NgbModalRef;
+ routeSub: any;
+
+ constructor (
+ private route: ActivatedRoute,
+ private postPopupService: PostPopupService
+ ) {}
+
+ ngOnInit() {
+ this.routeSub = this.route.params.subscribe(params => {
+ if ( params['id'] ) {
+ this.modalRef = this.postPopupService
+ .open(PostDialogComponent, params['id']);
+ } else {
+ this.modalRef = this.postPopupService
+ .open(PostDialogComponent);
+ }
+
+ });
+ }
+
+ ngOnDestroy() {
+ this.routeSub.unsubscribe();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/post/post-popup.service.ts b/jhipster/src/main/webapp/app/entities/post/post-popup.service.ts
new file mode 100644
index 000000000000..42ea731e07d6
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post-popup.service.ts
@@ -0,0 +1,50 @@
+import { Injectable, Component } from '@angular/core';
+import { Router } from '@angular/router';
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { Post } from './post.model';
+import { PostService } from './post.service';
+@Injectable()
+export class PostPopupService {
+ private isOpen = false;
+ constructor (
+ private modalService: NgbModal,
+ private router: Router,
+ private postService: PostService
+
+ ) {}
+
+ open (component: Component, id?: number | any): NgbModalRef {
+ if (this.isOpen) {
+ return;
+ }
+ this.isOpen = true;
+
+ if (id) {
+ this.postService.find(id).subscribe(post => {
+ if (post.creationDate) {
+ post.creationDate = {
+ year: post.creationDate.getFullYear(),
+ month: post.creationDate.getMonth() + 1,
+ day: post.creationDate.getDate()
+ };
+ }
+ this.postModalRef(component, post);
+ });
+ } else {
+ return this.postModalRef(component, new Post());
+ }
+ }
+
+ postModalRef(component: Component, post: Post): NgbModalRef {
+ let modalRef = this.modalService.open(component, { size: 'lg', backdrop: 'static'});
+ modalRef.componentInstance.post = post;
+ modalRef.result.then(result => {
+ this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true });
+ this.isOpen = false;
+ }, (reason) => {
+ this.router.navigate([{ outlets: { popup: null }}], { replaceUrl: true });
+ this.isOpen = false;
+ });
+ return modalRef;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/post/post.component.html b/jhipster/src/main/webapp/app/entities/post/post.component.html
new file mode 100644
index 000000000000..cf17c4e6dc40
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post.component.html
@@ -0,0 +1,64 @@
+
+
+ Posts
+
+
+
+ Create new Post
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ Title
+ Content
+ Creation Date
+ Creator
+
+
+
+ = links['last']" [infiniteScrollDistance]="0">
+
+ {{post.id}}
+ {{post.title}}
+ {{post.content}}
+ {{post.creationDate | date:'mediumDate'}}
+
+ {{post.creator?.login}}
+
+
+
+
+
+ View
+
+
+
+ Edit
+
+
+
+ Delete
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/entities/post/post.component.ts b/jhipster/src/main/webapp/app/entities/post/post.component.ts
new file mode 100644
index 000000000000..e8401a48c716
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post.component.ts
@@ -0,0 +1,110 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Response } from '@angular/http';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Subscription } from 'rxjs/Rx';
+import { EventManager, ParseLinks, PaginationUtil, JhiLanguageService, AlertService } from 'ng-jhipster';
+
+import { Post } from './post.model';
+import { PostService } from './post.service';
+import { ITEMS_PER_PAGE, Principal } from '../../shared';
+import { PaginationConfig } from '../../blocks/config/uib-pagination.config';
+
+@Component({
+ selector: 'jhi-post',
+ templateUrl: './post.component.html'
+})
+export class PostComponent implements OnInit, OnDestroy {
+
+ posts: Post[];
+ currentAccount: any;
+ eventSubscriber: Subscription;
+ itemsPerPage: number;
+ links: any;
+ page: any;
+ predicate: any;
+ queryCount: any;
+ reverse: any;
+ totalItems: number;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private postService: PostService,
+ private alertService: AlertService,
+ private eventManager: EventManager,
+ private parseLinks: ParseLinks,
+ private principal: Principal
+ ) {
+ this.posts = [];
+ this.itemsPerPage = ITEMS_PER_PAGE;
+ this.page = 0;
+ this.links = {
+ last: 0
+ };
+ this.predicate = 'id';
+ this.reverse = true;
+ this.jhiLanguageService.setLocations(['post']);
+ }
+
+ loadAll () {
+ this.postService.query({
+ page: this.page,
+ size: this.itemsPerPage,
+ sort: this.sort()
+ }).subscribe(
+ (res: Response) => this.onSuccess(res.json(), res.headers),
+ (res: Response) => this.onError(res.json())
+ );
+ }
+
+ reset () {
+ this.page = 0;
+ this.posts = [];
+ this.loadAll();
+ }
+
+ loadPage(page) {
+ this.page = page;
+ this.loadAll();
+ }
+ ngOnInit() {
+ this.loadAll();
+ this.principal.identity().then((account) => {
+ this.currentAccount = account;
+ });
+ this.registerChangeInPosts();
+ }
+
+ ngOnDestroy() {
+ this.eventManager.destroy(this.eventSubscriber);
+ }
+
+ trackId (index: number, item: Post) {
+ return item.id;
+ }
+
+
+
+ registerChangeInPosts() {
+ this.eventSubscriber = this.eventManager.subscribe('postListModification', (response) => this.reset());
+ }
+
+ sort () {
+ let result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')];
+ if (this.predicate !== 'id') {
+ result.push('id');
+ }
+ return result;
+ }
+
+ private onSuccess(data, headers) {
+ this.links = this.parseLinks.parse(headers.get('link'));
+ this.totalItems = headers.get('X-Total-Count');
+ for (let i = 0; i < data.length; i++) {
+ this.posts.push(data[i]);
+ }
+ }
+
+ private onError (error) {
+ this.alertService.error(error.message, null, null);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/post/post.model.ts b/jhipster/src/main/webapp/app/entities/post/post.model.ts
new file mode 100644
index 000000000000..454f7e75f8b2
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post.model.ts
@@ -0,0 +1,11 @@
+import { User } from '../../shared';
+export class Post {
+ constructor(
+ public id?: number,
+ public title?: string,
+ public content?: string,
+ public creationDate?: any,
+ public creator?: User,
+ ) {
+ }
+}
diff --git a/jhipster/src/main/webapp/app/entities/post/post.module.ts b/jhipster/src/main/webapp/app/entities/post/post.module.ts
new file mode 100644
index 000000000000..53ba982c021a
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post.module.ts
@@ -0,0 +1,52 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterModule } from '@angular/router';
+
+import { BaeldungSharedModule } from '../../shared';
+import { BaeldungAdminModule } from '../../admin/admin.module';
+
+import {
+ PostService,
+ PostPopupService,
+ PostComponent,
+ PostDetailComponent,
+ PostDialogComponent,
+ PostPopupComponent,
+ PostDeletePopupComponent,
+ PostDeleteDialogComponent,
+ postRoute,
+ postPopupRoute,
+} from './';
+
+let ENTITY_STATES = [
+ ...postRoute,
+ ...postPopupRoute,
+];
+
+@NgModule({
+ imports: [
+ BaeldungSharedModule,
+ BaeldungAdminModule,
+ RouterModule.forRoot(ENTITY_STATES, { useHash: true })
+ ],
+ declarations: [
+ PostComponent,
+ PostDetailComponent,
+ PostDialogComponent,
+ PostDeleteDialogComponent,
+ PostPopupComponent,
+ PostDeletePopupComponent,
+ ],
+ entryComponents: [
+ PostComponent,
+ PostDialogComponent,
+ PostPopupComponent,
+ PostDeleteDialogComponent,
+ PostDeletePopupComponent,
+ ],
+ providers: [
+ PostService,
+ PostPopupService,
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class BaeldungPostModule {}
diff --git a/jhipster/src/main/webapp/app/entities/post/post.route.ts b/jhipster/src/main/webapp/app/entities/post/post.route.ts
new file mode 100644
index 000000000000..27b23bdc0d21
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post.route.ts
@@ -0,0 +1,61 @@
+import { Injectable } from '@angular/core';
+import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Routes, CanActivate } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { PaginationUtil } from 'ng-jhipster';
+
+import { PostComponent } from './post.component';
+import { PostDetailComponent } from './post-detail.component';
+import { PostPopupComponent } from './post-dialog.component';
+import { PostDeletePopupComponent } from './post-delete-dialog.component';
+
+import { Principal } from '../../shared';
+
+
+export const postRoute: Routes = [
+ {
+ path: 'post',
+ component: PostComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.post.home.title'
+ }
+ }, {
+ path: 'post/:id',
+ component: PostDetailComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.post.home.title'
+ }
+ }
+];
+
+export const postPopupRoute: Routes = [
+ {
+ path: 'post-new',
+ component: PostPopupComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.post.home.title'
+ },
+ outlet: 'popup'
+ },
+ {
+ path: 'post/:id/edit',
+ component: PostPopupComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.post.home.title'
+ },
+ outlet: 'popup'
+ },
+ {
+ path: 'post/:id/delete',
+ component: PostDeletePopupComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'baeldungApp.post.home.title'
+ },
+ outlet: 'popup'
+ }
+];
diff --git a/jhipster/src/main/webapp/app/entities/post/post.service.ts b/jhipster/src/main/webapp/app/entities/post/post.service.ts
new file mode 100644
index 000000000000..7f7ee969febc
--- /dev/null
+++ b/jhipster/src/main/webapp/app/entities/post/post.service.ts
@@ -0,0 +1,78 @@
+import { Injectable } from '@angular/core';
+import { Http, Response, URLSearchParams, BaseRequestOptions } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+import { Post } from './post.model';
+import { DateUtils } from 'ng-jhipster';
+@Injectable()
+export class PostService {
+
+ private resourceUrl = 'api/posts';
+
+ constructor(private http: Http, private dateUtils: DateUtils) { }
+
+ create(post: Post): Observable {
+ let copy: Post = Object.assign({}, post);
+ copy.creationDate = this.dateUtils
+ .convertLocalDateToServer(post.creationDate);
+ return this.http.post(this.resourceUrl, copy).map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ update(post: Post): Observable {
+ let copy: Post = Object.assign({}, post);
+ copy.creationDate = this.dateUtils
+ .convertLocalDateToServer(post.creationDate);
+ return this.http.put(this.resourceUrl, copy).map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ find(id: number): Observable {
+ return this.http.get(`${this.resourceUrl}/${id}`).map((res: Response) => {
+ let jsonResponse = res.json();
+ jsonResponse.creationDate = this.dateUtils
+ .convertLocalDateFromServer(jsonResponse.creationDate);
+ return jsonResponse;
+ });
+ }
+
+ query(req?: any): Observable {
+ let options = this.createRequestOption(req);
+ return this.http.get(this.resourceUrl, options)
+ .map((res: any) => this.convertResponse(res))
+ ;
+ }
+
+ delete(id: number): Observable {
+ return this.http.delete(`${this.resourceUrl}/${id}`);
+ }
+
+
+ private convertResponse(res: any): any {
+ let jsonResponse = res.json();
+ for (let i = 0; i < jsonResponse.length; i++) {
+ jsonResponse[i].creationDate = this.dateUtils
+ .convertLocalDateFromServer(jsonResponse[i].creationDate);
+ }
+ res._body = jsonResponse;
+ return res;
+ }
+
+ private createRequestOption(req?: any): BaseRequestOptions {
+ let options: BaseRequestOptions = new BaseRequestOptions();
+ if (req) {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('page', req.page);
+ params.set('size', req.size);
+ if (req.sort) {
+ params.paramsMap.set('sort', req.sort);
+ }
+ params.set('query', req.query);
+
+ options.search = params;
+ }
+ return options;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/home/home.component.html b/jhipster/src/main/webapp/app/home/home.component.html
new file mode 100644
index 000000000000..4c0197228c00
--- /dev/null
+++ b/jhipster/src/main/webapp/app/home/home.component.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
Welcome, Java Hipster!
+
This is your homepage
+
+
+
+ You are logged in as user "{{account.login}}".
+
+
+
+
If you want to
+
sign in , you can try the default accounts: - Administrator (login="admin" and password="admin") - User (login="user" and password="user").
+
+
+
+
+
+
+ If you have any question on JHipster:
+
+
+
+
+
+ If you like JHipster, don't forget to give us a star on Github !
+
+
+
diff --git a/jhipster/src/main/webapp/app/home/home.component.ts b/jhipster/src/main/webapp/app/home/home.component.ts
new file mode 100644
index 000000000000..b16838377e29
--- /dev/null
+++ b/jhipster/src/main/webapp/app/home/home.component.ts
@@ -0,0 +1,50 @@
+import { Component, OnInit } from '@angular/core';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { EventManager, JhiLanguageService } from 'ng-jhipster';
+
+import { Account, LoginModalService, Principal } from '../shared';
+
+@Component({
+ selector: 'jhi-home',
+ templateUrl: './home.component.html',
+ styleUrls: [
+ 'home.scss'
+ ]
+
+})
+export class HomeComponent implements OnInit {
+ account: Account;
+ modalRef: NgbModalRef;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService,
+ private principal: Principal,
+ private loginModalService: LoginModalService,
+ private eventManager: EventManager
+ ) {
+ this.jhiLanguageService.setLocations(['home']);
+ }
+
+ ngOnInit() {
+ this.principal.identity().then((account) => {
+ this.account = account;
+ });
+ this.registerAuthenticationSuccess();
+ }
+
+ registerAuthenticationSuccess() {
+ this.eventManager.subscribe('authenticationSuccess', (message) => {
+ this.principal.identity().then((account) => {
+ this.account = account;
+ });
+ });
+ }
+
+ isAuthenticated() {
+ return this.principal.isAuthenticated();
+ }
+
+ login() {
+ this.modalRef = this.loginModalService.open();
+ }
+}
diff --git a/jhipster/src/main/webapp/app/home/home.module.ts b/jhipster/src/main/webapp/app/home/home.module.ts
new file mode 100644
index 000000000000..172c605249b9
--- /dev/null
+++ b/jhipster/src/main/webapp/app/home/home.module.ts
@@ -0,0 +1,23 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterModule } from '@angular/router';
+
+import { BaeldungSharedModule } from '../shared';
+
+import { HOME_ROUTE, HomeComponent } from './';
+
+
+@NgModule({
+ imports: [
+ BaeldungSharedModule,
+ RouterModule.forRoot([ HOME_ROUTE ], { useHash: true })
+ ],
+ declarations: [
+ HomeComponent,
+ ],
+ entryComponents: [
+ ],
+ providers: [
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class BaeldungHomeModule {}
diff --git a/jhipster/src/main/webapp/app/home/home.route.ts b/jhipster/src/main/webapp/app/home/home.route.ts
new file mode 100644
index 000000000000..cba14bf0c65e
--- /dev/null
+++ b/jhipster/src/main/webapp/app/home/home.route.ts
@@ -0,0 +1,14 @@
+import { Route } from '@angular/router';
+
+import { UserRouteAccessService } from '../shared';
+import { HomeComponent } from './';
+
+export const HOME_ROUTE: Route = {
+ path: '',
+ component: HomeComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'home.title'
+ },
+ canActivate: [UserRouteAccessService]
+};
diff --git a/jhipster/src/main/webapp/app/home/home.scss b/jhipster/src/main/webapp/app/home/home.scss
new file mode 100644
index 000000000000..578787b1c9f7
--- /dev/null
+++ b/jhipster/src/main/webapp/app/home/home.scss
@@ -0,0 +1,26 @@
+
+/* ==========================================================================
+Main page styles
+========================================================================== */
+
+.hipster {
+ display: inline-block;
+ width: 347px;
+ height: 497px;
+ background: url("../../content/images/hipster.png") no-repeat center top;
+ background-size: contain;
+}
+
+/* wait autoprefixer update to allow simple generation of high pixel density media query */
+@media
+only screen and (-webkit-min-device-pixel-ratio: 2),
+only screen and ( min--moz-device-pixel-ratio: 2),
+only screen and ( -o-min-device-pixel-ratio: 2/1),
+only screen and ( min-device-pixel-ratio: 2),
+only screen and ( min-resolution: 192dpi),
+only screen and ( min-resolution: 2dppx) {
+ .hipster {
+ background: url("../../content/images/hipster2x.png") no-repeat center top;
+ background-size: contain;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/home/index.ts b/jhipster/src/main/webapp/app/home/index.ts
new file mode 100644
index 000000000000..d76285b27776
--- /dev/null
+++ b/jhipster/src/main/webapp/app/home/index.ts
@@ -0,0 +1,3 @@
+export * from './home.component';
+export * from './home.route';
+export * from './home.module';
diff --git a/jhipster/src/main/webapp/app/layouts/error/error.component.html b/jhipster/src/main/webapp/app/layouts/error/error.component.html
new file mode 100644
index 000000000000..92fe2f151258
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/error/error.component.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
Error Page!
+
+
+
You are not authorized to access the page.
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/layouts/error/error.component.ts b/jhipster/src/main/webapp/app/layouts/error/error.component.ts
new file mode 100644
index 000000000000..983809f5410a
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/error/error.component.ts
@@ -0,0 +1,20 @@
+import { Component, OnInit } from '@angular/core';
+import { JhiLanguageService } from 'ng-jhipster';
+
+@Component({
+ selector: 'jhi-error',
+ templateUrl: './error.component.html'
+})
+export class ErrorComponent implements OnInit {
+ errorMessage: string;
+ error403: boolean;
+
+ constructor(
+ private jhiLanguageService: JhiLanguageService
+ ) {
+ this.jhiLanguageService.setLocations(['error']);
+ }
+
+ ngOnInit() {
+ }
+}
diff --git a/jhipster/src/main/webapp/app/layouts/error/error.route.ts b/jhipster/src/main/webapp/app/layouts/error/error.route.ts
new file mode 100644
index 000000000000..5f6085076c1f
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/error/error.route.ts
@@ -0,0 +1,25 @@
+import { Routes } from '@angular/router';
+
+import { UserRouteAccessService } from '../../shared';
+import { ErrorComponent } from './error.component';
+
+export const errorRoute: Routes = [
+ {
+ path: 'error',
+ component: ErrorComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'error.title'
+ },
+ canActivate: [UserRouteAccessService]
+ },
+ {
+ path: 'accessdenied',
+ component: ErrorComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'error.title'
+ },
+ canActivate: [UserRouteAccessService]
+ }
+];
diff --git a/jhipster/src/main/webapp/app/layouts/footer/footer.component.html b/jhipster/src/main/webapp/app/layouts/footer/footer.component.html
new file mode 100644
index 000000000000..4e4fda05bf56
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/footer/footer.component.html
@@ -0,0 +1,4 @@
+
+
diff --git a/jhipster/src/main/webapp/app/layouts/footer/footer.component.ts b/jhipster/src/main/webapp/app/layouts/footer/footer.component.ts
new file mode 100644
index 000000000000..37da8bca7544
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/footer/footer.component.ts
@@ -0,0 +1,7 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'jhi-footer',
+ templateUrl: './footer.component.html'
+})
+export class FooterComponent {}
diff --git a/jhipster/src/main/webapp/app/layouts/index.ts b/jhipster/src/main/webapp/app/layouts/index.ts
new file mode 100644
index 000000000000..f25305a0ac06
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/index.ts
@@ -0,0 +1,10 @@
+export * from './error/error.component';
+export * from './error/error.route';
+export * from './main/main.component';
+export * from './footer/footer.component';
+export * from './navbar/navbar.component';
+export * from './navbar/active-menu.directive';
+export * from './profiles/page-ribbon.component';
+export * from './profiles/profile.service';
+export * from './profiles/profile-info.model';
+export * from './layout-routing.module';
diff --git a/jhipster/src/main/webapp/app/layouts/layout-routing.module.ts b/jhipster/src/main/webapp/app/layouts/layout-routing.module.ts
new file mode 100644
index 000000000000..8edbdff26cc3
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/layout-routing.module.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes, Resolve } from '@angular/router';
+
+import { navbarRoute } from '../app.route';
+import { errorRoute } from './';
+
+let LAYOUT_ROUTES = [
+ navbarRoute,
+ ...errorRoute
+];
+
+@NgModule({
+ imports: [
+ RouterModule.forRoot(LAYOUT_ROUTES, { useHash: true })
+ ],
+ exports: [
+ RouterModule
+ ]
+})
+export class LayoutRoutingModule {}
diff --git a/jhipster/src/main/webapp/app/layouts/main/main.component.html b/jhipster/src/main/webapp/app/layouts/main/main.component.html
new file mode 100644
index 000000000000..5bcd12ab0b84
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/main/main.component.html
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/layouts/main/main.component.ts b/jhipster/src/main/webapp/app/layouts/main/main.component.ts
new file mode 100644
index 000000000000..c5982f60caa5
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/main/main.component.ts
@@ -0,0 +1,47 @@
+import { Component, OnInit } from '@angular/core';
+import { Router, ActivatedRouteSnapshot, NavigationEnd, RoutesRecognized } from '@angular/router';
+
+import { JhiLanguageHelper, StateStorageService } from '../../shared';
+
+@Component({
+ selector: 'jhi-main',
+ templateUrl: './main.component.html'
+})
+export class JhiMainComponent implements OnInit {
+
+ constructor(
+ private jhiLanguageHelper: JhiLanguageHelper,
+ private router: Router,
+ private $storageService: StateStorageService,
+ ) {}
+
+ private getPageTitle(routeSnapshot: ActivatedRouteSnapshot) {
+ let title: string = (routeSnapshot.data && routeSnapshot.data['pageTitle']) ? routeSnapshot.data['pageTitle'] : 'baeldungApp';
+ if (routeSnapshot.firstChild) {
+ title = this.getPageTitle(routeSnapshot.firstChild) || title;
+ }
+ return title;
+ }
+
+ ngOnInit() {
+ this.router.events.subscribe((event) => {
+ if (event instanceof NavigationEnd) {
+ this.jhiLanguageHelper.updateTitle(this.getPageTitle(this.router.routerState.snapshot.root));
+ }
+ if (event instanceof RoutesRecognized) {
+ let params = {};
+ let destinationData = {};
+ let destinationName = '';
+ let destinationEvent = event.state.root.firstChild.children[0];
+ if (destinationEvent !== undefined) {
+ params = destinationEvent.params;
+ destinationData = destinationEvent.data;
+ destinationName = destinationEvent.url[0].path;
+ }
+ let from = {name: this.router.url.slice(1)};
+ let destination = {name: destinationName, data: destinationData};
+ this.$storageService.storeDestinationState(destination, params, from);
+ }
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/layouts/navbar/active-menu.directive.ts b/jhipster/src/main/webapp/app/layouts/navbar/active-menu.directive.ts
new file mode 100644
index 000000000000..87275c9d57a9
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/navbar/active-menu.directive.ts
@@ -0,0 +1,26 @@
+import { Directive, OnInit, ElementRef, Renderer, Input} from '@angular/core';
+import { TranslateService, LangChangeEvent } from 'ng2-translate';
+
+@Directive({
+ selector: '[jhiActiveMenu]'
+})
+export class ActiveMenuDirective implements OnInit {
+ @Input() jhiActiveMenu: string;
+
+ constructor(private el: ElementRef, private renderer: Renderer, private translateService: TranslateService) {}
+
+ ngOnInit() {
+ this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
+ this.updateActiveFlag(event.lang);
+ });
+ this.updateActiveFlag(this.translateService.currentLang);
+ }
+
+ updateActiveFlag(selectedLanguage) {
+ if (this.jhiActiveMenu === selectedLanguage) {
+ this.renderer.setElementClass(this.el.nativeElement, 'active', true);
+ } else {
+ this.renderer.setElementClass(this.el.nativeElement, 'active', false);
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.html b/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.html
new file mode 100644
index 000000000000..07b7abb25c22
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.html
@@ -0,0 +1,168 @@
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.ts b/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.ts
new file mode 100644
index 000000000000..8f58bfebd9db
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/navbar/navbar.component.ts
@@ -0,0 +1,81 @@
+import { Component, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { ProfileService } from '../profiles/profile.service'; // FIXME barrel doesnt work here
+import { JhiLanguageHelper, Principal, LoginModalService, LoginService } from '../../shared';
+
+import { VERSION, DEBUG_INFO_ENABLED } from '../../app.constants';
+
+@Component({
+ selector: 'jhi-navbar',
+ templateUrl: './navbar.component.html',
+ styleUrls: [
+ 'navbar.scss'
+ ]
+})
+export class NavbarComponent implements OnInit {
+
+ inProduction: boolean;
+ isNavbarCollapsed: boolean;
+ languages: any[];
+ swaggerEnabled: boolean;
+ modalRef: NgbModalRef;
+ version: string;
+
+ constructor(
+ private loginService: LoginService,
+ private languageHelper: JhiLanguageHelper,
+ private languageService: JhiLanguageService,
+ private principal: Principal,
+ private loginModalService: LoginModalService,
+ private profileService: ProfileService,
+ private router: Router
+ ) {
+ this.version = DEBUG_INFO_ENABLED ? 'v' + VERSION : '';
+ this.isNavbarCollapsed = true;
+ this.languageService.addLocation('home');
+ }
+
+ ngOnInit() {
+ this.languageHelper.getAll().then((languages) => {
+ this.languages = languages;
+ });
+
+ this.profileService.getProfileInfo().subscribe(profileInfo => {
+ this.inProduction = profileInfo.inProduction;
+ this.swaggerEnabled = profileInfo.swaggerEnabled;
+ });
+ }
+
+ changeLanguage(languageKey: string) {
+ this.languageService.changeLanguage(languageKey);
+ }
+
+ collapseNavbar() {
+ this.isNavbarCollapsed = true;
+ }
+
+ isAuthenticated() {
+ return this.principal.isAuthenticated();
+ }
+
+ login() {
+ this.modalRef = this.loginModalService.open();
+ }
+
+ logout() {
+ this.collapseNavbar();
+ this.loginService.logout();
+ this.router.navigate(['']);
+ }
+
+ toggleNavbar() {
+ this.isNavbarCollapsed = !this.isNavbarCollapsed;
+ }
+
+ getImageUrl() {
+ return this.isAuthenticated() ? this.principal.getImageUrl() : null;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/layouts/navbar/navbar.scss b/jhipster/src/main/webapp/app/layouts/navbar/navbar.scss
new file mode 100644
index 000000000000..d48d60954388
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/navbar/navbar.scss
@@ -0,0 +1,70 @@
+
+/* ==========================================================================
+Navbar
+========================================================================== */
+.navbar-version {
+ font-size: 10px;
+ color: #ccc
+}
+
+.jh-navbar {
+ background-color: #353d47;
+ padding: .2em 1em;
+ .profile-image {
+ margin: -10px 0px;
+ height: 40px;
+ width: 40px;
+ border-radius: 50%;
+ }
+ .dropdown-item.active, .dropdown-item.active:focus, .dropdown-item.active:hover {
+ background-color: #353d47;
+ }
+ .dropdown-toggle::after {
+ margin-left: 0.15em;
+ }
+ ul.navbar-nav {
+ padding: 0.5em;
+ .nav-item {
+ margin-left: 1.5rem;
+ }
+ }
+ a.nav-link {
+ font-weight: 400;
+ }
+ .jh-navbar-toggler {
+ color: #ccc;
+ font-size: 1.5em;
+ padding: 10px;
+ &:hover {
+ color: #fff;
+ }
+ }
+}
+
+@media screen and (max-width: 992px) {
+ .jh-logo-container {
+ width: 100%;
+ }
+}
+
+.navbar-title {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+/* ==========================================================================
+Logo styles
+========================================================================== */
+.navbar-brand {
+ &.logo {
+ padding: 5px 15px;
+ .logo-img {
+ height: 45px;
+ width: 70px;
+ display: inline-block;
+ vertical-align: middle;
+ background: url("../../../content/images/logo-jhipster.png") no-repeat center center;
+ background-size: contain;
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts b/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts
new file mode 100644
index 000000000000..f7ba492f6637
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts
@@ -0,0 +1,25 @@
+import { Component, OnInit } from '@angular/core';
+import { ProfileService } from './profile.service';
+import { ProfileInfo } from './profile-info.model';
+
+@Component({
+ selector: 'jhi-page-ribbon',
+ template: ``,
+ styleUrls: [
+ 'page-ribbon.scss'
+ ]
+})
+export class PageRibbonComponent implements OnInit {
+
+ profileInfo: ProfileInfo;
+ ribbonEnv: string;
+
+ constructor(private profileService: ProfileService) {}
+
+ ngOnInit() {
+ this.profileService.getProfileInfo().subscribe(profileInfo => {
+ this.profileInfo = profileInfo;
+ this.ribbonEnv = profileInfo.ribbonEnv;
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.scss b/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.scss
new file mode 100644
index 000000000000..5efd11c03ea4
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/profiles/page-ribbon.scss
@@ -0,0 +1,32 @@
+
+/* ==========================================================================
+Developement Ribbon
+========================================================================== */
+.ribbon {
+ background-color: rgba(170, 0, 0, 0.5);
+ left: -3.5em;
+ moz-transform: rotate(-45deg);
+ ms-transform: rotate(-45deg);
+ o-transform: rotate(-45deg);
+ webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ overflow: hidden;
+ position: absolute;
+ top: 40px;
+ white-space: nowrap;
+ width: 15em;
+ z-index: 9999;
+ pointer-events: none;
+ opacity: 0.75;
+ a {
+ color: #fff;
+ display: block;
+ font-weight: 400;
+ margin: 1px 0;
+ padding: 10px 50px;
+ text-align: center;
+ text-decoration: none;
+ text-shadow: 0 0 5px #444;
+ pointer-events: none;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/layouts/profiles/profile-info.model.ts b/jhipster/src/main/webapp/app/layouts/profiles/profile-info.model.ts
new file mode 100644
index 000000000000..f1adc52c7bf2
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/profiles/profile-info.model.ts
@@ -0,0 +1,6 @@
+export class ProfileInfo {
+ activeProfiles: string[];
+ ribbonEnv: string;
+ inProduction: boolean;
+ swaggerEnabled: boolean;
+}
diff --git a/jhipster/src/main/webapp/app/layouts/profiles/profile.service.ts b/jhipster/src/main/webapp/app/layouts/profiles/profile.service.ts
new file mode 100644
index 000000000000..347b84d8e521
--- /dev/null
+++ b/jhipster/src/main/webapp/app/layouts/profiles/profile.service.ts
@@ -0,0 +1,26 @@
+import { Injectable } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+import { ProfileInfo } from './profile-info.model';
+
+@Injectable()
+export class ProfileService {
+
+ private profileInfoUrl = 'api/profile-info';
+
+ constructor(private http: Http) { }
+
+ getProfileInfo(): Observable {
+ return this.http.get(this.profileInfoUrl)
+ .map((res: Response) => {
+ let data = res.json();
+ let pi = new ProfileInfo();
+ pi.activeProfiles = data.activeProfiles;
+ pi.ribbonEnv = data.ribbonEnv;
+ pi.inProduction = data.activeProfiles.indexOf('prod') !== -1;
+ pi.swaggerEnabled = data.activeProfiles.indexOf('swagger') !== -1;
+ return pi;
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/polyfills.ts b/jhipster/src/main/webapp/app/polyfills.ts
new file mode 100644
index 000000000000..0771ba0b72ec
--- /dev/null
+++ b/jhipster/src/main/webapp/app/polyfills.ts
@@ -0,0 +1,3 @@
+/* tslint:disable */
+import 'reflect-metadata/Reflect';
+import 'zone.js/dist/zone';
diff --git a/jhipster/src/main/webapp/app/shared/alert/alert-error.component.ts b/jhipster/src/main/webapp/app/shared/alert/alert-error.component.ts
new file mode 100644
index 000000000000..e9e3e2b7a021
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/alert/alert-error.component.ts
@@ -0,0 +1,101 @@
+import { Component, OnDestroy } from '@angular/core';
+import { TranslateService } from 'ng2-translate';
+import { EventManager, AlertService } from 'ng-jhipster';
+import { Subscription } from 'rxjs/Rx';
+
+@Component({
+ selector: 'jhi-alert-error',
+ template: `
+ `
+})
+export class JhiAlertErrorComponent implements OnDestroy {
+
+ alerts: any[];
+ cleanHttpErrorListener: Subscription;
+
+ constructor(private alertService: AlertService, private eventManager: EventManager, private translateService: TranslateService) {
+ this.alerts = [];
+
+ this.cleanHttpErrorListener = eventManager.subscribe('baeldungApp.httpError', (response) => {
+ let i;
+ let httpResponse = response.content;
+ switch (httpResponse.status) {
+ // connection refused, server not reachable
+ case 0:
+ this.addErrorAlert('Server not reachable', 'error.server.not.reachable');
+ break;
+
+ case 400:
+ let arr = Array.from(httpResponse.headers._headers);
+ let headers = [];
+ for (i = 0; i < arr.length; i++) {
+ if (arr[i][0].endsWith('app-error') || arr[i][0].endsWith('app-params')) {
+ headers.push(arr[i][0]);
+ }
+ }
+ headers.sort();
+ let errorHeader = httpResponse.headers.get(headers[0]);
+ let entityKey = httpResponse.headers.get(headers[1]);
+ if (errorHeader) {
+ let entityName = translateService.instant('global.menu.entities.' + entityKey);
+ this.addErrorAlert(errorHeader, errorHeader, {entityName: entityName});
+ } else if (httpResponse.text() !== '' && httpResponse.json() && httpResponse.json().fieldErrors) {
+ let fieldErrors = httpResponse.json().fieldErrors;
+ for (i = 0; i < fieldErrors.length; i++) {
+ let fieldError = fieldErrors[i];
+ // convert 'something[14].other[4].id' to 'something[].other[].id' so translations can be written to it
+ let convertedField = fieldError.field.replace(/\[\d*\]/g, '[]');
+ let fieldName = translateService.instant('baeldungApp.' +
+ fieldError.objectName + '.' + convertedField);
+ this.addErrorAlert(
+ 'Field ' + fieldName + ' cannot be empty', 'error.' + fieldError.message, {fieldName: fieldName});
+ }
+ } else if (httpResponse.text() !== '' && httpResponse.json() && httpResponse.json().message) {
+ this.addErrorAlert(httpResponse.json().message, httpResponse.json().message, httpResponse.json());
+ } else {
+ this.addErrorAlert(httpResponse.text());
+ }
+ break;
+
+ case 404:
+ this.addErrorAlert('Not found', 'error.url.not.found');
+ break;
+
+ default:
+ if (httpResponse.text() !== '' && httpResponse.json() && httpResponse.json().message) {
+ this.addErrorAlert(httpResponse.json().message);
+ } else {
+ this.addErrorAlert(JSON.stringify(httpResponse)); // Fixme find a way to parse httpResponse
+ }
+ }
+ });
+ }
+
+ ngOnDestroy() {
+ if (this.cleanHttpErrorListener !== undefined && this.cleanHttpErrorListener !== null) {
+ this.eventManager.destroy(this.cleanHttpErrorListener);
+ this.alerts = [];
+ }
+ }
+
+ addErrorAlert (message, key?, data?) {
+ key = key && key !== null ? key : message;
+ this.alerts.push(
+ this.alertService.addAlert(
+ {
+ type: 'danger',
+ msg: key,
+ params: data,
+ timeout: 5000,
+ toast: this.alertService.isToast(),
+ scoped: true
+ },
+ this.alerts
+ )
+ );
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/alert/alert.component.ts b/jhipster/src/main/webapp/app/shared/alert/alert.component.ts
new file mode 100644
index 000000000000..b8aa418ac581
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/alert/alert.component.ts
@@ -0,0 +1,26 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { AlertService } from 'ng-jhipster';
+
+@Component({
+ selector: 'jhi-alert',
+ template: `
+ `
+})
+export class JhiAlertComponent implements OnInit, OnDestroy {
+ alerts: any[];
+
+ constructor(private alertService: AlertService) { }
+
+ ngOnInit() {
+ this.alerts = this.alertService.get();
+ }
+
+ ngOnDestroy() {
+ this.alerts = [];
+ }
+
+}
diff --git a/jhipster/src/main/webapp/app/shared/auth/account.service.ts b/jhipster/src/main/webapp/app/shared/auth/account.service.ts
new file mode 100644
index 000000000000..6d21943d497f
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/auth/account.service.ts
@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class AccountService {
+ constructor(private http: Http) { }
+
+ get(): Observable {
+ return this.http.get('api/account').map((res: Response) => res.json());
+ }
+
+ save(account: any): Observable {
+ return this.http.post('api/account', account);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/auth/auth-jwt.service.ts b/jhipster/src/main/webapp/app/shared/auth/auth-jwt.service.ts
new file mode 100644
index 000000000000..9be418d1758d
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/auth/auth-jwt.service.ts
@@ -0,0 +1,61 @@
+import { Injectable } from '@angular/core';
+import { Http, Response, Headers, URLSearchParams } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+import { LocalStorageService, SessionStorageService } from 'ng2-webstorage';
+
+@Injectable()
+export class AuthServerProvider {
+ constructor(
+ private http: Http,
+ private $localStorage: LocalStorageService,
+ private $sessionStorage: SessionStorageService
+ ) {}
+
+ getToken () {
+ return this.$localStorage.retrieve('authenticationToken') || this.$sessionStorage.retrieve('authenticationToken');
+ }
+
+ login (credentials): Observable {
+
+ let data = {
+ username: credentials.username,
+ password: credentials.password,
+ rememberMe: credentials.rememberMe
+ };
+ return this.http.post('api/authenticate', data).map(authenticateSuccess.bind(this));
+
+ function authenticateSuccess (resp) {
+ let bearerToken = resp.headers.get('Authorization');
+ if (bearerToken && bearerToken.slice(0, 7) === 'Bearer ') {
+ let jwt = bearerToken.slice(7, bearerToken.length);
+ this.storeAuthenticationToken(jwt, credentials.rememberMe);
+ return jwt;
+ }
+ }
+ }
+
+ loginWithToken(jwt, rememberMe) {
+ if (jwt) {
+ this.storeAuthenticationToken(jwt, rememberMe);
+ return Promise.resolve(jwt);
+ } else {
+ return Promise.reject('auth-jwt-service Promise reject'); // Put appropriate error message here
+ }
+ }
+
+ storeAuthenticationToken(jwt, rememberMe) {
+ if (rememberMe) {
+ this.$localStorage.store('authenticationToken', jwt);
+ } else {
+ this.$sessionStorage.store('authenticationToken', jwt);
+ }
+ }
+
+ logout (): Observable {
+ return new Observable(observer => {
+ this.$localStorage.clear('authenticationToken');
+ this.$sessionStorage.clear('authenticationToken');
+ observer.complete();
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/auth/auth.service.ts b/jhipster/src/main/webapp/app/shared/auth/auth.service.ts
new file mode 100644
index 000000000000..9e21fb673745
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/auth/auth.service.ts
@@ -0,0 +1,65 @@
+import { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { LoginModalService } from '../login/login-modal.service';
+import { Principal } from './principal.service';
+import { StateStorageService } from './state-storage.service';
+
+@Injectable()
+export class AuthService {
+
+ constructor(
+ private principal: Principal,
+ private stateStorageService: StateStorageService,
+ private loginModalService: LoginModalService,
+ private router: Router
+ ) {}
+
+ authorize (force) {
+ let authReturn = this.principal.identity(force).then(authThen.bind(this));
+
+ return authReturn;
+
+ function authThen () {
+ let isAuthenticated = this.principal.isAuthenticated();
+ let toStateInfo = this.stateStorageService.getDestinationState().destination;
+
+ // an authenticated user can't access to login and register pages
+ if (isAuthenticated && (toStateInfo.name === 'register' || toStateInfo.name === 'social-auth')) {
+ this.router.navigate(['']);
+ return false;
+ }
+
+ // recover and clear previousState after external login redirect (e.g. oauth2)
+ let fromStateInfo = this.stateStorageService.getDestinationState().from;
+ let previousState = this.stateStorageService.getPreviousState();
+ if (isAuthenticated && !fromStateInfo.name && previousState) {
+ this.stateStorageService.resetPreviousState();
+ this.router.navigate([previousState.name], { queryParams: previousState.params });
+ return false;
+ }
+
+ if (toStateInfo.data.authorities && toStateInfo.data.authorities.length > 0) {
+ return this.principal.hasAnyAuthority(toStateInfo.data.authorities).then(hasAnyAuthority => {
+ if (!hasAnyAuthority) {
+ if (isAuthenticated) {
+ // user is signed in but not authorized for desired state
+ this.router.navigate(['accessdenied']);
+ } else {
+ // user is not authenticated. Show the state they wanted before you
+ // send them to the login service, so you can return them when you're done
+ let toStateParamsInfo = this.stateStorageService.getDestinationState().params;
+ this.stateStorageService.storePreviousState(toStateInfo.name, toStateParamsInfo);
+ // now, send them to the signin state so they can log in
+ this.router.navigate(['accessdenied']).then(() => {
+ this.loginModalService.open();
+ });
+ }
+ }
+ return hasAnyAuthority;
+ });
+ }
+ return true;
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/auth/csrf.service.ts b/jhipster/src/main/webapp/app/shared/auth/csrf.service.ts
new file mode 100644
index 000000000000..6f1064112a48
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/auth/csrf.service.ts
@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core';
+import { CookieService } from 'angular2-cookie/core';
+
+@Injectable()
+export class CSRFService {
+
+ constructor(private cookieService: CookieService) {}
+
+ getCSRF(name?: string) {
+ name = `${name ? name : 'XSRF-TOKEN'}`;
+ return this.cookieService.get(name);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/auth/has-any-authority.directive.ts b/jhipster/src/main/webapp/app/shared/auth/has-any-authority.directive.ts
new file mode 100644
index 000000000000..858c105fd552
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/auth/has-any-authority.directive.ts
@@ -0,0 +1,42 @@
+import { Directive, ElementRef, Input, TemplateRef, ViewContainerRef } from '@angular/core';
+import { Principal } from './principal.service';
+
+/**
+ * @whatItDoes Conditionally includes an HTML element if current user has any
+ * of the authorities passed as the `expression`.
+ *
+ * @howToUse
+ * ```
+ * ...
+ *
+ * ...
+ * ```
+ */
+@Directive({
+ selector: '[jhiHasAnyAuthority]'
+})
+export class HasAnyAuthorityDirective {
+
+ private authorities: string[];
+
+ constructor(private principal: Principal, private templateRef: TemplateRef, private viewContainerRef: ViewContainerRef) {
+ }
+
+ @Input()
+ set jhiHasAnyAuthority(value: string|string[]) {
+ this.authorities = typeof value === 'string' ? [ value ] : value;
+ this.updateView();
+ // Get notified each time authentication state changes.
+ this.principal.getAuthenticationState().subscribe(identity => this.updateView());
+ }
+
+ private updateView(): void {
+ this.principal.hasAnyAuthority(this.authorities).then(result => {
+ if (result) {
+ this.viewContainerRef.createEmbeddedView(this.templateRef);
+ } else {
+ this.viewContainerRef.clear();
+ }
+ });
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/auth/principal.service.ts b/jhipster/src/main/webapp/app/shared/auth/principal.service.ts
new file mode 100644
index 000000000000..2c7f05dd5687
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/auth/principal.service.ts
@@ -0,0 +1,93 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { Subject } from 'rxjs/Subject';
+import { AccountService } from './account.service';
+
+@Injectable()
+export class Principal {
+ private userIdentity: any;
+ private authenticated = false;
+ private authenticationState = new Subject();
+
+ constructor(
+ private account: AccountService
+ ) {}
+
+ authenticate (identity) {
+ this.userIdentity = identity;
+ this.authenticated = identity !== null;
+ this.authenticationState.next(this.userIdentity);
+ }
+
+ hasAnyAuthority (authorities: string[]): Promise {
+ if (!this.authenticated || !this.userIdentity || !this.userIdentity.authorities) {
+ return Promise.resolve(false);
+ }
+
+ for (let i = 0; i < authorities.length; i++) {
+ if (this.userIdentity.authorities.indexOf(authorities[i]) !== -1) {
+ return Promise.resolve(true);
+ }
+ }
+
+ return Promise.resolve(false);
+ }
+
+ hasAuthority (authority: string): Promise {
+ if (!this.authenticated) {
+ return Promise.resolve(false);
+ }
+
+ return this.identity().then(id => {
+ return Promise.resolve(id.authorities && id.authorities.indexOf(authority) !== -1);
+ }, () => {
+ return Promise.resolve(false);
+ });
+ }
+
+ identity (force?: boolean): Promise {
+ if (force === true) {
+ this.userIdentity = undefined;
+ }
+
+ // check and see if we have retrieved the userIdentity data from the server.
+ // if we have, reuse it by immediately resolving
+ if (this.userIdentity) {
+ return Promise.resolve(this.userIdentity);
+ }
+
+ // retrieve the userIdentity data from the server, update the identity object, and then resolve.
+ return this.account.get().toPromise().then(account => {
+ if (account) {
+ this.userIdentity = account;
+ this.authenticated = true;
+ } else {
+ this.userIdentity = null;
+ this.authenticated = false;
+ }
+ this.authenticationState.next(this.userIdentity);
+ return this.userIdentity;
+ }).catch(err => {
+ this.userIdentity = null;
+ this.authenticated = false;
+ this.authenticationState.next(this.userIdentity);
+ return null;
+ });
+ }
+
+ isAuthenticated (): boolean {
+ return this.authenticated;
+ }
+
+ isIdentityResolved (): boolean {
+ return this.userIdentity !== undefined;
+ }
+
+ getAuthenticationState(): Observable {
+ return this.authenticationState.asObservable();
+ }
+
+ getImageUrl(): String {
+ return this.isIdentityResolved () ? this.userIdentity.imageUrl : null;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/auth/state-storage.service.ts b/jhipster/src/main/webapp/app/shared/auth/state-storage.service.ts
new file mode 100644
index 000000000000..1fe364b06db0
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/auth/state-storage.service.ts
@@ -0,0 +1,40 @@
+import { Injectable } from '@angular/core';
+import { SessionStorageService } from 'ng2-webstorage';
+
+@Injectable()
+export class StateStorageService {
+ constructor(
+ private $sessionStorage: SessionStorageService
+ ) {}
+
+ getPreviousState() {
+ return this.$sessionStorage.retrieve('previousState');
+ }
+
+ resetPreviousState() {
+ this.$sessionStorage.clear('previousState');
+ }
+
+ storePreviousState(previousStateName, previousStateParams) {
+ let previousState = { 'name': previousStateName, 'params': previousStateParams };
+ this.$sessionStorage.store('previousState', previousState);
+ }
+
+ getDestinationState() {
+ return this.$sessionStorage.retrieve('destinationState');
+ }
+
+ storeDestinationState(destinationState, destinationStateParams, fromState) {
+ let destinationInfo = {
+ 'destination': {
+ 'name': destinationState.name,
+ 'data': destinationState.data,
+ },
+ 'params': destinationStateParams,
+ 'from': {
+ 'name': fromState.name,
+ }
+ };
+ this.$sessionStorage.store('destinationState', destinationInfo);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/auth/user-route-access-service.ts b/jhipster/src/main/webapp/app/shared/auth/user-route-access-service.ts
new file mode 100644
index 000000000000..95eb236672c5
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/auth/user-route-access-service.ts
@@ -0,0 +1,15 @@
+import { Injectable } from '@angular/core';
+import { CanActivate, Router, ActivatedRouteSnapshot } from '@angular/router';
+
+import { AuthService } from '../';
+
+@Injectable()
+export class UserRouteAccessService implements CanActivate {
+
+ constructor(private router: Router, private auth: AuthService) {
+ }
+
+ canActivate(route: ActivatedRouteSnapshot): boolean | Promise {
+ return this.auth.authorize(false).then( canActivate => canActivate);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/constants/pagination.constants.ts b/jhipster/src/main/webapp/app/shared/constants/pagination.constants.ts
new file mode 100644
index 000000000000..a148d4579ba3
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/constants/pagination.constants.ts
@@ -0,0 +1 @@
+export const ITEMS_PER_PAGE = 20;
diff --git a/jhipster/src/main/webapp/app/shared/index.ts b/jhipster/src/main/webapp/app/shared/index.ts
new file mode 100644
index 000000000000..d2687cf88457
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/index.ts
@@ -0,0 +1,23 @@
+export * from './alert/alert.component';
+export * from './alert/alert-error.component';
+export * from './auth/csrf.service';
+export * from './auth/state-storage.service';
+export * from './auth/account.service';
+export * from './auth/auth-jwt.service';
+export * from './auth/auth.service';
+export * from './auth/principal.service';
+export * from './auth/has-any-authority.directive';
+export * from './language/language.constants';
+export * from './language/language.helper';
+export * from './language/language.pipe';
+export * from './login/login.component';
+export * from './login/login.service';
+export * from './login/login-modal.service';
+export * from './constants/pagination.constants';
+export * from './user/account.model';
+export * from './user/user.model';
+export * from './user/user.service';
+export * from './shared-libs.module';
+export * from './shared-common.module';
+export * from './shared.module';
+export * from './auth/user-route-access-service';
diff --git a/jhipster/src/main/webapp/app/shared/language/language.constants.ts b/jhipster/src/main/webapp/app/shared/language/language.constants.ts
new file mode 100644
index 000000000000..2292ef4624a5
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/language/language.constants.ts
@@ -0,0 +1,8 @@
+/*
+ Languages codes are ISO_639-1 codes, see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
+ They are written in English to avoid character encoding issues (not a perfect solution)
+*/
+export const LANGUAGES: string[] = [
+ 'en'
+ // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array
+];
diff --git a/jhipster/src/main/webapp/app/shared/language/language.helper.ts b/jhipster/src/main/webapp/app/shared/language/language.helper.ts
new file mode 100644
index 000000000000..d8d609bf0c77
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/language/language.helper.ts
@@ -0,0 +1,53 @@
+import { Injectable } from '@angular/core';
+import { Title } from '@angular/platform-browser';
+import { Router, ActivatedRouteSnapshot } from '@angular/router';
+import { TranslateService, TranslationChangeEvent, LangChangeEvent } from 'ng2-translate/ng2-translate';
+
+import { LANGUAGES } from './language.constants';
+
+@Injectable()
+export class JhiLanguageHelper {
+
+ constructor (private translateService: TranslateService, private titleService: Title, private router: Router) {
+ this.init();
+ }
+
+ getAll(): Promise {
+ return Promise.resolve(LANGUAGES);
+ }
+
+ /**
+ * Update the window title using params in the following
+ * order:
+ * 1. titleKey parameter
+ * 2. $state.$current.data.pageTitle (current state page title)
+ * 3. 'global.title'
+ */
+ updateTitle(titleKey?: string) {
+ if (!titleKey) {
+ titleKey = this.getPageTitle(this.router.routerState.snapshot.root);
+ }
+
+ this.translateService.get(titleKey).subscribe(title => {
+ this.titleService.setTitle(title);
+ });
+ }
+
+ private init () {
+ this.translateService.onTranslationChange.subscribe((event: TranslationChangeEvent) => {
+ this.updateTitle();
+ });
+
+ this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
+ this.updateTitle();
+ });
+ }
+
+ private getPageTitle(routeSnapshot: ActivatedRouteSnapshot) {
+ let title: string = (routeSnapshot.data && routeSnapshot.data['pageTitle']) ? routeSnapshot.data['pageTitle'] : 'baeldungApp';
+ if (routeSnapshot.firstChild) {
+ title = this.getPageTitle(routeSnapshot.firstChild) || title;
+ }
+ return title;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/language/language.pipe.ts b/jhipster/src/main/webapp/app/shared/language/language.pipe.ts
new file mode 100644
index 000000000000..d271c8e2c220
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/language/language.pipe.ts
@@ -0,0 +1,42 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({name: 'findLanguageFromKey'})
+export class FindLanguageFromKeyPipe implements PipeTransform {
+ private languages: any = {
+ 'ca': 'Català',
+ 'cs': 'Český',
+ 'da': 'Dansk',
+ 'de': 'Deutsch',
+ 'el': 'Ελληνικά',
+ 'en': 'English',
+ 'es': 'Español',
+ 'et': 'Eesti',
+ 'fr': 'Français',
+ 'gl': 'Galego',
+ 'hu': 'Magyar',
+ 'hi': 'हिंदी',
+ 'hy': 'Հայերեն',
+ 'it': 'Italiano',
+ 'ja': '日本語',
+ 'ko': '한국어',
+ 'mr': 'मराठी',
+ 'nl': 'Nederlands',
+ 'pl': 'Polski',
+ 'pt-br': 'Português (Brasil)',
+ 'pt-pt': 'Português',
+ 'ro': 'Română',
+ 'ru': 'Русский',
+ 'sk': 'Slovenský',
+ 'sr': 'Srpski',
+ 'sv': 'Svenska',
+ 'ta': 'தமிழ்',
+ 'th': 'ไทย',
+ 'tr': 'Türkçe',
+ 'vi': 'Tiếng Việt',
+ 'zh-cn': '中文(简体)',
+ 'zh-tw': '繁體中文'
+ };
+ transform(lang: string): string {
+ return this.languages[lang];
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/login/login-modal.service.ts b/jhipster/src/main/webapp/app/shared/login/login-modal.service.ts
new file mode 100644
index 000000000000..9e435978eaa6
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/login/login-modal.service.ts
@@ -0,0 +1,26 @@
+import { Injectable } from '@angular/core';
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+
+import { JhiLoginModalComponent } from './login.component';
+
+@Injectable()
+export class LoginModalService {
+ private isOpen = false;
+ constructor (
+ private modalService: NgbModal,
+ ) {}
+
+ open (): NgbModalRef {
+ if (this.isOpen) {
+ return;
+ }
+ this.isOpen = true;
+ let modalRef = this.modalService.open(JhiLoginModalComponent);
+ modalRef.result.then(result => {
+ this.isOpen = false;
+ }, (reason) => {
+ this.isOpen = false;
+ });
+ return modalRef;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/login/login.component.html b/jhipster/src/main/webapp/app/shared/login/login.component.html
new file mode 100644
index 000000000000..a6b6b1924914
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/login/login.component.html
@@ -0,0 +1,46 @@
+
+
+
+
+
Sign in
+
+
+
+ Failed to sign in! Please check your credentials and try again.
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/app/shared/login/login.component.ts b/jhipster/src/main/webapp/app/shared/login/login.component.ts
new file mode 100644
index 000000000000..90acbb03ac33
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/login/login.component.ts
@@ -0,0 +1,90 @@
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { Router } from '@angular/router';
+import { JhiLanguageService, EventManager } from 'ng-jhipster';
+
+import { LoginService } from '../login/login.service';
+import { StateStorageService } from '../auth/state-storage.service';
+
+@Component({
+ selector: 'jhi-login-modal',
+ templateUrl: './login.component.html'
+})
+export class JhiLoginModalComponent implements OnInit, AfterViewInit {
+ authenticationError: boolean;
+ password: string;
+ rememberMe: boolean;
+ username: string;
+ credentials: any;
+
+ constructor(
+ private eventManager: EventManager,
+ private languageService: JhiLanguageService,
+ private loginService: LoginService,
+ private stateStorageService: StateStorageService,
+ private elementRef: ElementRef,
+ private renderer: Renderer,
+ private router: Router,
+ public activeModal: NgbActiveModal
+ ) {
+ this.credentials = {};
+ }
+
+ ngOnInit() {
+ this.languageService.addLocation('login');
+ }
+
+ ngAfterViewInit() {
+ this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#username'), 'focus', []);
+ }
+
+ cancel () {
+ this.credentials = {
+ username: null,
+ password: null,
+ rememberMe: true
+ };
+ this.authenticationError = false;
+ this.activeModal.dismiss('cancel');
+ }
+
+ login () {
+ this.loginService.login({
+ username: this.username,
+ password: this.password,
+ rememberMe: this.rememberMe
+ }).then(() => {
+ this.authenticationError = false;
+ this.activeModal.dismiss('login success');
+ if (this.router.url === '/register' || (/activate/.test(this.router.url)) ||
+ this.router.url === '/finishReset' || this.router.url === '/requestReset') {
+ this.router.navigate(['']);
+ }
+
+ this.eventManager.broadcast({
+ name: 'authenticationSuccess',
+ content: 'Sending Authentication Success'
+ });
+
+ // // previousState was set in the authExpiredInterceptor before being redirected to login modal.
+ // // since login is succesful, go to stored previousState and clear previousState
+ let previousState = this.stateStorageService.getPreviousState();
+ if (previousState) {
+ this.stateStorageService.resetPreviousState();
+ this.router.navigate([previousState.name], { queryParams: previousState.params });
+ }
+ }).catch(() => {
+ this.authenticationError = true;
+ });
+ }
+
+ register () {
+ this.activeModal.dismiss('to state register');
+ this.router.navigate(['/register']);
+ }
+
+ requestResetPassword () {
+ this.activeModal.dismiss('to state requestReset');
+ this.router.navigate(['/reset', 'request']);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/login/login.service.ts b/jhipster/src/main/webapp/app/shared/login/login.service.ts
new file mode 100644
index 000000000000..22352992258b
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/login/login.service.ts
@@ -0,0 +1,45 @@
+import { Injectable } from '@angular/core';
+import { JhiLanguageService } from 'ng-jhipster';
+
+import { Principal } from '../auth/principal.service';
+import { AuthServerProvider } from '../auth/auth-jwt.service';
+
+@Injectable()
+export class LoginService {
+
+ constructor (
+ private languageService: JhiLanguageService,
+ private principal: Principal,
+ private authServerProvider: AuthServerProvider
+ ) {}
+
+ login (credentials, callback?) {
+ let cb = callback || function() {};
+
+ return new Promise((resolve, reject) => {
+ this.authServerProvider.login(credentials).subscribe(data => {
+ this.principal.identity(true).then(account => {
+ // After the login the language will be changed to
+ // the language selected by the user during his registration
+ if (account !== null) {
+ this.languageService.changeLanguage(account.langKey);
+ }
+ resolve(data);
+ });
+ return cb();
+ }, err => {
+ this.logout();
+ reject(err);
+ return cb(err);
+ });
+ });
+ }
+ loginWithToken(jwt, rememberMe) {
+ return this.authServerProvider.loginWithToken(jwt, rememberMe);
+ }
+
+ logout () {
+ this.authServerProvider.logout().subscribe();
+ this.principal.authenticate(null);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/shared-common.module.ts b/jhipster/src/main/webapp/app/shared/shared-common.module.ts
new file mode 100644
index 000000000000..6f713e216bb1
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/shared-common.module.ts
@@ -0,0 +1,47 @@
+import { NgModule, Sanitizer } from '@angular/core';
+import { Title } from '@angular/platform-browser';
+
+import { TranslateService } from 'ng2-translate';
+import { AlertService } from 'ng-jhipster';
+
+import {
+ BaeldungSharedLibsModule,
+ JhiLanguageHelper,
+ FindLanguageFromKeyPipe,
+ JhiAlertComponent,
+ JhiAlertErrorComponent
+} from './';
+
+
+export function alertServiceProvider(sanitizer: Sanitizer, translateService: TranslateService) {
+ // set below to true to make alerts look like toast
+ let isToast = false;
+ return new AlertService(sanitizer, isToast, translateService);
+}
+
+@NgModule({
+ imports: [
+ BaeldungSharedLibsModule
+ ],
+ declarations: [
+ FindLanguageFromKeyPipe,
+ JhiAlertComponent,
+ JhiAlertErrorComponent
+ ],
+ providers: [
+ JhiLanguageHelper,
+ {
+ provide: AlertService,
+ useFactory: alertServiceProvider,
+ deps: [Sanitizer, TranslateService]
+ },
+ Title
+ ],
+ exports: [
+ BaeldungSharedLibsModule,
+ FindLanguageFromKeyPipe,
+ JhiAlertComponent,
+ JhiAlertErrorComponent
+ ]
+})
+export class BaeldungSharedCommonModule {}
diff --git a/jhipster/src/main/webapp/app/shared/shared-libs.module.ts b/jhipster/src/main/webapp/app/shared/shared-libs.module.ts
new file mode 100644
index 000000000000..0bf10eeaa800
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/shared-libs.module.ts
@@ -0,0 +1,27 @@
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { HttpModule } from '@angular/http';
+import { CommonModule } from '@angular/common';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgJhipsterModule } from 'ng-jhipster';
+import { InfiniteScrollModule } from 'angular2-infinite-scroll';
+
+@NgModule({
+ imports: [
+ NgbModule.forRoot(),
+ NgJhipsterModule.forRoot({
+ i18nEnabled: true,
+ defaultI18nLang: 'en'
+ }),
+ InfiniteScrollModule
+ ],
+ exports: [
+ FormsModule,
+ HttpModule,
+ CommonModule,
+ NgbModule,
+ NgJhipsterModule,
+ InfiniteScrollModule
+ ]
+})
+export class BaeldungSharedLibsModule {}
diff --git a/jhipster/src/main/webapp/app/shared/shared.module.ts b/jhipster/src/main/webapp/app/shared/shared.module.ts
new file mode 100644
index 000000000000..f7af13852be8
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/shared.module.ts
@@ -0,0 +1,53 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { DatePipe } from '@angular/common';
+
+import { CookieService } from 'angular2-cookie/services/cookies.service';
+import {
+ BaeldungSharedLibsModule,
+ BaeldungSharedCommonModule,
+ CSRFService,
+ AuthService,
+ AuthServerProvider,
+ AccountService,
+ UserService,
+ StateStorageService,
+ LoginService,
+ LoginModalService,
+ Principal,
+ HasAnyAuthorityDirective,
+ JhiLoginModalComponent
+} from './';
+
+@NgModule({
+ imports: [
+ BaeldungSharedLibsModule,
+ BaeldungSharedCommonModule
+ ],
+ declarations: [
+ JhiLoginModalComponent,
+ HasAnyAuthorityDirective
+ ],
+ providers: [
+ CookieService,
+ LoginService,
+ LoginModalService,
+ AccountService,
+ StateStorageService,
+ Principal,
+ CSRFService,
+ AuthServerProvider,
+ AuthService,
+ UserService,
+ DatePipe
+ ],
+ entryComponents: [JhiLoginModalComponent],
+ exports: [
+ BaeldungSharedCommonModule,
+ JhiLoginModalComponent,
+ HasAnyAuthorityDirective,
+ DatePipe
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+
+})
+export class BaeldungSharedModule {}
diff --git a/jhipster/src/main/webapp/app/shared/user/account.model.ts b/jhipster/src/main/webapp/app/shared/user/account.model.ts
new file mode 100644
index 000000000000..c8b975076039
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/user/account.model.ts
@@ -0,0 +1,12 @@
+export class Account {
+ constructor(
+ public activated: boolean,
+ public authorities: string[],
+ public email: string,
+ public firstName: string,
+ public langKey: string,
+ public lastName: string,
+ public login: string,
+ public imageUrl: string
+ ) { }
+}
diff --git a/jhipster/src/main/webapp/app/shared/user/user.model.ts b/jhipster/src/main/webapp/app/shared/user/user.model.ts
new file mode 100644
index 000000000000..374c3ae0caae
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/user/user.model.ts
@@ -0,0 +1,44 @@
+export class User {
+ public id?: any;
+ public login?: string;
+ public firstName?: string;
+ public lastName?: string;
+ public email?: string;
+ public activated?: Boolean;
+ public langKey?: string;
+ public authorities?: any[];
+ public createdBy?: string;
+ public createdDate?: Date;
+ public lastModifiedBy?: string;
+ public lastModifiedDate?: Date;
+ public password?: string;
+ constructor(
+ id?: any,
+ login?: string,
+ firstName?: string,
+ lastName?: string,
+ email?: string,
+ activated?: Boolean,
+ langKey?: string,
+ authorities?: any[],
+ createdBy?: string,
+ createdDate?: Date,
+ lastModifiedBy?: string,
+ lastModifiedDate?: Date,
+ password?: string
+ ) {
+ this.id = id ? id : null;
+ this.login = login ? login : null;
+ this.firstName = firstName ? firstName : null;
+ this.lastName = lastName ? lastName : null;
+ this.email = email ? email : null;
+ this.activated = activated ? activated : false;
+ this.langKey = langKey ? langKey : null;
+ this.authorities = authorities ? authorities : null;
+ this.createdBy = createdBy ? createdBy : null;
+ this.createdDate = createdDate ? createdDate : null;
+ this.lastModifiedBy = lastModifiedBy ? lastModifiedBy : null;
+ this.lastModifiedDate = lastModifiedDate ? lastModifiedDate : null;
+ this.password = password ? password : null;
+ }
+}
diff --git a/jhipster/src/main/webapp/app/shared/user/user.service.ts b/jhipster/src/main/webapp/app/shared/user/user.service.ts
new file mode 100644
index 000000000000..b0e11212158a
--- /dev/null
+++ b/jhipster/src/main/webapp/app/shared/user/user.service.ts
@@ -0,0 +1,45 @@
+import { Injectable } from '@angular/core';
+import { Http, Response, URLSearchParams } from '@angular/http';
+import { Observable } from 'rxjs/Rx';
+
+import { User } from './user.model';
+
+@Injectable()
+export class UserService {
+ private resourceUrl = 'api/users';
+
+ constructor(private http: Http) { }
+
+ create(user: User): Observable {
+ return this.http.post(this.resourceUrl, user);
+ }
+
+ update(user: User): Observable {
+ return this.http.put(this.resourceUrl, user);
+ }
+
+ find(login: string): Observable {
+ return this.http.get(`${this.resourceUrl}/${login}`).map((res: Response) => res.json());
+ }
+
+ query(req?: any): Observable {
+ let params: URLSearchParams = new URLSearchParams();
+ if (req) {
+ params.set('page', req.page);
+ params.set('size', req.size);
+ if (req.sort) {
+ params.paramsMap.set('sort', req.sort);
+ }
+ }
+
+ let options = {
+ search: params
+ };
+
+ return this.http.get(this.resourceUrl, options);
+ }
+
+ delete(login: string): Observable {
+ return this.http.delete(`${this.resourceUrl}/${login}`);
+ }
+}
diff --git a/jhipster/src/main/webapp/app/vendor.ts b/jhipster/src/main/webapp/app/vendor.ts
new file mode 100644
index 000000000000..f9ccfd5b0000
--- /dev/null
+++ b/jhipster/src/main/webapp/app/vendor.ts
@@ -0,0 +1,3 @@
+/* after changing this file run 'npm install' or 'npm run webpack:build' */
+/* tslint:disable */
+import '../content/scss/vendor.scss';
diff --git a/jhipster/src/main/webapp/content/images/hipster.png b/jhipster/src/main/webapp/content/images/hipster.png
new file mode 100644
index 000000000000..141778d48252
Binary files /dev/null and b/jhipster/src/main/webapp/content/images/hipster.png differ
diff --git a/jhipster/src/main/webapp/content/images/hipster2x.png b/jhipster/src/main/webapp/content/images/hipster2x.png
new file mode 100644
index 000000000000..4751c91680c4
Binary files /dev/null and b/jhipster/src/main/webapp/content/images/hipster2x.png differ
diff --git a/jhipster/src/main/webapp/content/images/logo-jhipster.png b/jhipster/src/main/webapp/content/images/logo-jhipster.png
new file mode 100644
index 000000000000..d8eb48da05c1
Binary files /dev/null and b/jhipster/src/main/webapp/content/images/logo-jhipster.png differ
diff --git a/jhipster/src/main/webapp/content/scss/global.scss b/jhipster/src/main/webapp/content/scss/global.scss
new file mode 100644
index 000000000000..c3699402e609
--- /dev/null
+++ b/jhipster/src/main/webapp/content/scss/global.scss
@@ -0,0 +1,211 @@
+
+/* ==============================================================
+Bootstrap tweaks
+===============================================================*/
+
+body, h1, h2, h3, h4 {
+ font-weight: 300;
+}
+
+
+/* ==========================================================================
+Browser Upgrade Prompt
+========================================================================== */
+.browserupgrade {
+ margin: 0.2em 0;
+ background: #ccc;
+ color: #000;
+ padding: 0.2em 0;
+}
+
+/* ==========================================================================
+Generic styles
+========================================================================== */
+
+/* Error highlight on input fields */
+.ng-valid[required], .ng-valid.required {
+ border-left: 5px solid green;
+}
+
+.ng-invalid:not(form) {
+ border-left: 5px solid red;
+}
+
+/* other generic styles */
+
+.jh-card {
+ padding: 1.5%;
+ margin-top: 20px;
+ border: none;
+}
+
+.error {
+ color: white;
+ background-color: red;
+}
+
+.pad {
+ padding: 10px;
+}
+
+.w-40 {
+ width: 40% !important;
+}
+
+.w-60 {
+ width: 60% !important;
+}
+
+.break {
+ white-space: normal;
+ word-break:break-all;
+}
+
+.readonly {
+ background-color: #eee;
+ opacity: 1;
+}
+
+/*FIXME this is to support grids in table class; */
+table td[class*=col-], table th[class*=col-] {
+ position: static;
+ display: table-cell;
+ float: none;
+}
+
+.footer {
+ border-top: 1px solid rgba(0,0,0,.125);
+}
+
+/* ==========================================================================
+make sure browsers use the pointer cursor for anchors, even with no href
+========================================================================== */
+a:hover {
+ cursor: pointer;
+}
+
+.hand {
+ cursor: pointer;
+}
+
+/* ==========================================================================
+Custom alerts for notification
+========================================================================== */
+.alerts {
+ .alert {
+ text-overflow: ellipsis;
+ pre{
+ background: none;
+ border: none;
+ font: inherit;
+ color: inherit;
+ padding: 0;
+ margin: 0;
+ }
+ .popover pre {
+ font-size: 10px;
+ }
+ }
+ .toast {
+ position: fixed;
+ width: 100%;
+ &.left {
+ left: 5px;
+ }
+ &.right {
+ right: 5px;
+ }
+ &.top {
+ top: 55px;
+ }
+ &.bottom {
+ bottom: 55px;
+ }
+ }
+}
+
+@media screen and (min-width: 480px) {
+ .alerts .toast {
+ width: 50%;
+ }
+}
+
+/* ==========================================================================
+entity tables helpers
+========================================================================== */
+
+/* Remove Bootstrap padding from the element
+ http://stackoverflow.com/questions/19562903/remove-padding-from-columns-in-bootstrap-3 */
+@mixin no-padding($side) {
+ @if $side == 'all' {
+ .no-padding {
+ padding: 0 !important;
+ }
+ } @else {
+ .no-padding-#{$side} {
+ padding-#{$side}: 0 !important;
+ }
+ }
+}
+@include no-padding("left");
+@include no-padding("right");
+@include no-padding("top");
+@include no-padding("bottom");
+@include no-padding("all");
+
+/* bootstrap 3 input-group 100% width
+ http://stackoverflow.com/questions/23436430/bootstrap-3-input-group-100-width */
+.width-min { width: 1% !important; }
+
+/* Makes toolbar not wrap on smaller screens
+ http://www.sketchingwithcss.com/samplechapter/cheatsheet.html#right */
+.flex-btn-group-container {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+ -webkit-justify-content: flex-end;
+ justify-content: flex-end;
+}
+
+/* ==========================================================================
+entity detail page css
+========================================================================== */
+.row.jh-entity-details > {
+ dd {
+ margin-bottom: 15px;
+ }
+}
+
+@media screen and (min-width: 768px) {
+ .row.jh-entity-details > {
+ dt {
+ margin-bottom: 15px;
+ }
+ dd {
+ border-bottom: 1px solid #eee;
+ padding-left: 180px;
+ margin-left: 0;
+ }
+ }
+}
+
+/* ==========================================================================
+ui bootstrap tweaks
+========================================================================== */
+.nav, .pagination, .carousel, .panel-title a {
+ cursor: pointer;
+}
+
+.datetime-picker-dropdown > li.date-picker-menu div > table .btn-default,
+.uib-datepicker-popup > li > div.uib-datepicker > table .btn-default {
+ border: 0;
+}
+
+.datetime-picker-dropdown > li.date-picker-menu div > table:focus,
+.uib-datepicker-popup > li > div.uib-datepicker > table:focus {
+ outline: none;
+}
+
+
+/* jhipster-needle-scss-add-main JHipster will add new css style */
diff --git a/jhipster/src/main/webapp/content/scss/vendor.scss b/jhipster/src/main/webapp/content/scss/vendor.scss
new file mode 100644
index 000000000000..55990bbef599
--- /dev/null
+++ b/jhipster/src/main/webapp/content/scss/vendor.scss
@@ -0,0 +1,10 @@
+/* after changing this file run 'npm install' or 'npm run webpack:build' */
+$fa-font-path: '~font-awesome/fonts';
+
+/***************************
+put Sass variables here:
+eg $input-color: red;
+****************************/
+
+@import 'node_modules/bootstrap/scss/bootstrap';
+@import 'node_modules/font-awesome/scss/font-awesome';
diff --git a/jhipster/src/main/webapp/favicon.ico b/jhipster/src/main/webapp/favicon.ico
new file mode 100644
index 000000000000..44c7595f58f5
Binary files /dev/null and b/jhipster/src/main/webapp/favicon.ico differ
diff --git a/jhipster/src/main/webapp/i18n/en/activate.json b/jhipster/src/main/webapp/i18n/en/activate.json
new file mode 100644
index 000000000000..e766161398f0
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/activate.json
@@ -0,0 +1,9 @@
+{
+ "activate": {
+ "title": "Activation",
+ "messages": {
+ "success": "Your user has been activated. Please ",
+ "error": "Your user could not be activated. Please use the registration form to sign up."
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/audits.json b/jhipster/src/main/webapp/i18n/en/audits.json
new file mode 100644
index 000000000000..ed5e16d4c544
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/audits.json
@@ -0,0 +1,27 @@
+{
+ "audits": {
+ "title": "Audits",
+ "filter": {
+ "title": "Filter per date",
+ "from": "from",
+ "to": "to",
+ "button": {
+ "weeks": "Weeks",
+ "today": "today",
+ "clear": "clear",
+ "close": "close"
+ }
+ },
+ "table": {
+ "header": {
+ "principal": "User",
+ "date": "Date",
+ "status": "State",
+ "data": "Extra data"
+ },
+ "data": {
+ "remoteAddress": "Remote Address:"
+ }
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/comment.json b/jhipster/src/main/webapp/i18n/en/comment.json
new file mode 100644
index 000000000000..eed148855a55
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/comment.json
@@ -0,0 +1,23 @@
+{
+ "baeldungApp": {
+ "comment" : {
+ "home": {
+ "title": "Comments",
+ "createLabel": "Create a new Comment",
+ "createOrEditLabel": "Create or edit a Comment"
+ },
+ "created": "A new Comment is created with identifier {{ param }}",
+ "updated": "A Comment is updated with identifier {{ param }}",
+ "deleted": "A Comment is deleted with identifier {{ param }}",
+ "delete": {
+ "question": "Are you sure you want to delete Comment {{ id }}?"
+ },
+ "detail": {
+ "title": "Comment"
+ },
+ "text": "Text",
+ "creationDate": "Creation Date",
+ "post": "Post"
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/configuration.json b/jhipster/src/main/webapp/i18n/en/configuration.json
new file mode 100644
index 000000000000..81e208de5c0b
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/configuration.json
@@ -0,0 +1,10 @@
+{
+ "configuration": {
+ "title": "Configuration",
+ "filter": "Filter (by prefix)",
+ "table": {
+ "prefix": "Prefix",
+ "properties": "Properties"
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/error.json b/jhipster/src/main/webapp/i18n/en/error.json
new file mode 100644
index 000000000000..65246c3b0308
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/error.json
@@ -0,0 +1,6 @@
+{
+ "error": {
+ "title": "Error page!",
+ "403": "You are not authorized to access the page."
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/gateway.json b/jhipster/src/main/webapp/i18n/en/gateway.json
new file mode 100644
index 000000000000..8c8bbd7e981c
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/gateway.json
@@ -0,0 +1,15 @@
+{
+ "gateway": {
+ "title": "Gateway",
+ "routes": {
+ "title": "Current routes",
+ "url": "URL",
+ "service": "service",
+ "servers": "Available servers",
+ "error": "Warning: no server available!"
+ },
+ "refresh": {
+ "button": "Refresh"
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/global.json b/jhipster/src/main/webapp/i18n/en/global.json
new file mode 100644
index 000000000000..0c49362336cf
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/global.json
@@ -0,0 +1,133 @@
+{
+ "global": {
+ "title": "Baeldung",
+ "browsehappy": "You are using an outdated browser. Please upgrade your browser to improve your experience.",
+ "menu": {
+ "home": "Home",
+ "jhipster-needle-menu-add-element": "JHipster will add additional menu entries here (do not translate!)",
+ "entities": {
+ "main": "Entities",
+ "post": "Post",
+ "comment": "Comment",
+ "jhipster-needle-menu-add-entry": "JHipster will add additional entities here (do not translate!)"
+ },
+ "account": {
+ "main": "Account",
+ "settings": "Settings",
+ "password": "Password",
+ "sessions": "Sessions",
+ "login": "Sign in",
+ "logout": "Sign out",
+ "register": "Register"
+ },
+ "admin": {
+ "main": "Administration",
+ "userManagement": "User management",
+ "tracker": "User tracker",
+ "metrics": "Metrics",
+ "health": "Health",
+ "configuration": "Configuration",
+ "logs": "Logs",
+ "audits": "Audits",
+ "apidocs": "API",
+ "database": "Database",
+ "jhipster-needle-menu-add-admin-element": "JHipster will add additional menu entries here (do not translate!)"
+ },
+ "language": "Language"
+ },
+ "form": {
+ "username": "Username",
+ "username.placeholder": "Your username",
+ "newpassword": "New password",
+ "newpassword.placeholder": "New password",
+ "confirmpassword": "New password confirmation",
+ "confirmpassword.placeholder": "Confirm the new password",
+ "email": "E-mail",
+ "email.placeholder": "Your e-mail"
+ },
+ "messages": {
+ "info": {
+ "authenticated": {
+ "prefix": "If you want to ",
+ "link": "sign in",
+ "suffix": ", you can try the default accounts: - Administrator (login=\"admin\" and password=\"admin\") - User (login=\"user\" and password=\"user\")."
+ },
+ "register": {
+ "noaccount": "You don't have an account yet?",
+ "link": "Register a new account"
+ }
+ },
+ "error": {
+ "dontmatch": "The password and its confirmation do not match!"
+ },
+ "validate": {
+ "newpassword": {
+ "required": "Your password is required.",
+ "minlength": "Your password is required to be at least 4 characters.",
+ "maxlength": "Your password cannot be longer than 50 characters.",
+ "strength": "Password strength:"
+ },
+ "confirmpassword": {
+ "required": "Your confirmation password is required.",
+ "minlength": "Your confirmation password is required to be at least 4 characters.",
+ "maxlength": "Your confirmation password cannot be longer than 50 characters."
+ },
+ "email": {
+ "required": "Your e-mail is required.",
+ "invalid": "Your e-mail is invalid.",
+ "minlength": "Your e-mail is required to be at least 5 characters.",
+ "maxlength": "Your e-mail cannot be longer than 50 characters."
+ }
+ }
+ },
+ "field": {
+ "id": "ID"
+ },
+ "ribbon": {
+ "dev":"Development"
+ }
+ },
+ "entity": {
+ "action": {
+ "addblob": "Add blob",
+ "addimage": "Add image",
+ "back": "Back",
+ "cancel": "Cancel",
+ "delete": "Delete",
+ "edit": "Edit",
+ "open": "Open",
+ "save": "Save",
+ "view": "View"
+ },
+ "detail": {
+ "field": "Field",
+ "value": "Value"
+ },
+ "delete": {
+ "title": "Confirm delete operation"
+ },
+ "validation": {
+ "required": "This field is required.",
+ "minlength": "This field is required to be at least {{ min }} characters.",
+ "maxlength": "This field cannot be longer than {{ max }} characters.",
+ "min": "This field should be at least {{ min }}.",
+ "max": "This field cannot be more than {{ max }}.",
+ "minbytes": "This field should be at least {{ min }} bytes.",
+ "maxbytes": "This field cannot be more than {{ max }} bytes.",
+ "pattern": "This field should follow pattern {{ pattern }}.",
+ "number": "This field should be a number.",
+ "datetimelocal": "This field should be a date and time."
+ }
+ },
+ "error": {
+ "internalServerError": "Internal server error",
+ "server.not.reachable": "Server not reachable",
+ "url.not.found": "Not found",
+ "NotNull": "Field {{ fieldName }} cannot be empty!",
+ "Size": "Field {{ fieldName }} does not meet min/max size requirements!",
+ "userexists": "Login name already used!",
+ "emailexists": "E-mail is already in use!",
+ "idexists": "A new {{ entityName }} cannot already have an ID"
+ },
+ "footer": "This is your footer"
+}
diff --git a/jhipster/src/main/webapp/i18n/en/health.json b/jhipster/src/main/webapp/i18n/en/health.json
new file mode 100644
index 000000000000..868ea3fda4f0
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/health.json
@@ -0,0 +1,27 @@
+{
+ "health": {
+ "title": "Health Checks",
+ "refresh.button": "Refresh",
+ "stacktrace": "Stacktrace",
+ "details": {
+ "details": "Details",
+ "properties": "Properties",
+ "name": "Name",
+ "value": "Value",
+ "error": "Error"
+ },
+ "indicator": {
+ "diskSpace": "Disk space",
+ "mail": "Email",
+ "db": "Database"
+ },
+ "table": {
+ "service": "Service name",
+ "status": "Status"
+ },
+ "status": {
+ "UP": "UP",
+ "DOWN": "DOWN"
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/home.json b/jhipster/src/main/webapp/i18n/en/home.json
new file mode 100644
index 000000000000..402f18700ad9
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/home.json
@@ -0,0 +1,19 @@
+{
+ "home": {
+ "title": "Welcome, Java Hipster!",
+ "subtitle": "This is your homepage",
+ "logged": {
+ "message": "You are logged in as user \"{{username}}\"."
+ },
+ "question": "If you have any question on JHipster:",
+ "link": {
+ "homepage": "JHipster homepage",
+ "stackoverflow": "JHipster on Stack Overflow",
+ "bugtracker": "JHipster bug tracker",
+ "chat": "JHipster public chat room",
+ "follow": "follow @java_hipster on Twitter"
+ },
+ "like": "If you like JHipster, don't forget to give us a star on",
+ "github": "GitHub"
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/login.json b/jhipster/src/main/webapp/i18n/en/login.json
new file mode 100644
index 000000000000..3a000852e62c
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/login.json
@@ -0,0 +1,19 @@
+{
+ "login": {
+ "title": "Sign in",
+ "form": {
+ "password": "Password",
+ "password.placeholder": "Your password",
+ "rememberme": "Remember me",
+ "button": "Sign in"
+ },
+ "messages": {
+ "error": {
+ "authentication": "Failed to sign in! Please check your credentials and try again."
+ }
+ },
+ "password" : {
+ "forgot": "Did you forget your password?"
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/logs.json b/jhipster/src/main/webapp/i18n/en/logs.json
new file mode 100644
index 000000000000..a614b128da59
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/logs.json
@@ -0,0 +1,11 @@
+{
+ "logs": {
+ "title": "Logs",
+ "nbloggers": "There are {{ total }} loggers.",
+ "filter": "Filter",
+ "table": {
+ "name": "Name",
+ "level": "Level"
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/metrics.json b/jhipster/src/main/webapp/i18n/en/metrics.json
new file mode 100644
index 000000000000..9d804947c571
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/metrics.json
@@ -0,0 +1,101 @@
+{
+ "metrics": {
+ "title": "Application Metrics",
+ "refresh.button": "Refresh",
+ "updating": "Updating...",
+ "jvm": {
+ "title": "JVM Metrics",
+ "memory": {
+ "title": "Memory",
+ "total": "Total Memory",
+ "heap": "Heap Memory",
+ "nonheap": "Non-Heap Memory"
+ },
+ "threads": {
+ "title": "Threads",
+ "all": "All",
+ "runnable": "Runnable",
+ "timedwaiting": "Timed waiting",
+ "waiting": "Waiting",
+ "blocked": "Blocked",
+ "dump": {
+ "title": "Threads dump",
+ "id": "Id: ",
+ "blockedtime": "Blocked Time",
+ "blockedcount": "Blocked Count",
+ "waitedtime": "Waited Time",
+ "waitedcount": "Waited Count",
+ "lockname": "Lock name",
+ "stacktrace": "Stacktrace",
+ "show": "Show Stacktrace",
+ "hide": "Hide Stacktrace"
+ }
+ },
+ "gc": {
+ "title": "Garbage collections",
+ "marksweepcount": "Mark Sweep count",
+ "marksweeptime": "Mark Sweep time",
+ "scavengecount": "Scavenge count",
+ "scavengetime": "Scavenge time"
+ },
+ "http": {
+ "title": "HTTP requests (events per second)",
+ "active": "Active requests:",
+ "total": "Total requests:",
+ "table": {
+ "code": "Code",
+ "count": "Count",
+ "mean": "Mean",
+ "average": "Average"
+ },
+ "code": {
+ "ok": "Ok",
+ "notfound": "Not found",
+ "servererror": "Server Error"
+ }
+ }
+ },
+ "servicesstats": {
+ "title": "Services statistics (time in millisecond)",
+ "table": {
+ "name": "Service name",
+ "count": "Count",
+ "mean": "Mean",
+ "min": "Min",
+ "max": "Max",
+ "p50": "p50",
+ "p75": "p75",
+ "p95": "p95",
+ "p99": "p99"
+ }
+ },
+ "cache": {
+ "title": "Cache statistics",
+ "cachename": "Cache name",
+ "hits": "Cache Hits",
+ "misses": "Cache Misses",
+ "gets": "Cache Gets",
+ "puts": "Cache Puts",
+ "removals": "Cache Removals",
+ "evictions": "Cache Evictions",
+ "hitPercent": "Cache Hit %",
+ "missPercent": "Cache Miss %",
+ "averageGetTime": "Average get time (µs)",
+ "averagePutTime": "Average put time (µs)",
+ "averageRemoveTime": "Average remove time (µs)"
+ },
+ "datasource": {
+ "usage": "Usage",
+ "title": "DataSource statistics (time in millisecond)",
+ "name": "Pool usage",
+ "count": "Count",
+ "mean": "Mean",
+ "min": "Min",
+ "max": "Max",
+ "p50": "p50",
+ "p75": "p75",
+ "p95": "p95",
+ "p99": "p99"
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/password.json b/jhipster/src/main/webapp/i18n/en/password.json
new file mode 100644
index 000000000000..46227a77024c
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/password.json
@@ -0,0 +1,12 @@
+{
+ "password": {
+ "title": "Password for [{{username}} ]",
+ "form": {
+ "button": "Save"
+ },
+ "messages": {
+ "error": "An error has occurred! The password could not be changed.",
+ "success": "Password changed! "
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/post.json b/jhipster/src/main/webapp/i18n/en/post.json
new file mode 100644
index 000000000000..14c64f3f903b
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/post.json
@@ -0,0 +1,24 @@
+{
+ "baeldungApp": {
+ "post" : {
+ "home": {
+ "title": "Posts",
+ "createLabel": "Create a new Post",
+ "createOrEditLabel": "Create or edit a Post"
+ },
+ "created": "A new Post is created with identifier {{ param }}",
+ "updated": "A Post is updated with identifier {{ param }}",
+ "deleted": "A Post is deleted with identifier {{ param }}",
+ "delete": {
+ "question": "Are you sure you want to delete Post {{ id }}?"
+ },
+ "detail": {
+ "title": "Post"
+ },
+ "title": "Title",
+ "content": "Content",
+ "creationDate": "Creation Date",
+ "creator": "Creator"
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/register.json b/jhipster/src/main/webapp/i18n/en/register.json
new file mode 100644
index 000000000000..df8f6e31b8ce
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/register.json
@@ -0,0 +1,24 @@
+{
+ "register": {
+ "title": "Registration",
+ "form": {
+ "button": "Register"
+ },
+ "messages": {
+ "validate": {
+ "login": {
+ "required": "Your username is required.",
+ "minlength": "Your username is required to be at least 1 character.",
+ "maxlength": "Your username cannot be longer than 50 characters.",
+ "pattern": "Your username can only contain lower-case letters and digits."
+ }
+ },
+ "success": "Registration saved! Please check your email for confirmation.",
+ "error": {
+ "fail": "Registration failed! Please try again later.",
+ "userexists": "Login name already registered! Please choose another one.",
+ "emailexists": "E-mail is already in use! Please choose another one."
+ }
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/reset.json b/jhipster/src/main/webapp/i18n/en/reset.json
new file mode 100644
index 000000000000..fc61e0070f26
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/reset.json
@@ -0,0 +1,27 @@
+{
+ "reset": {
+ "request": {
+ "title": "Reset your password",
+ "form": {
+ "button": "Reset password"
+ },
+ "messages": {
+ "info": "Enter the e-mail address you used to register",
+ "success": "Check your e-mails for details on how to reset your password.",
+ "notfound": "E-Mail address isn't registered! Please check and try again"
+ }
+ },
+ "finish" : {
+ "title": "Reset password",
+ "form": {
+ "button": "Validate new password"
+ },
+ "messages": {
+ "info": "Choose a new password",
+ "success": "Your password has been reset. Please ",
+ "keymissing": "The reset key is missing.",
+ "error": "Your password couldn't be reset. Remember a password request is only valid for 24 hours."
+ }
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/sessions.json b/jhipster/src/main/webapp/i18n/en/sessions.json
new file mode 100644
index 000000000000..d410035ee7ca
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/sessions.json
@@ -0,0 +1,15 @@
+{
+ "sessions": {
+ "title": "Active sessions for [{{username}} ]",
+ "table": {
+ "ipaddress": "IP address",
+ "useragent": "User Agent",
+ "date": "Date",
+ "button": "Invalidate"
+ },
+ "messages": {
+ "success": "Session invalidated! ",
+ "error": "An error has occurred! The session could not be invalidated."
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/settings.json b/jhipster/src/main/webapp/i18n/en/settings.json
new file mode 100644
index 000000000000..919ab51cc93c
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/settings.json
@@ -0,0 +1,32 @@
+{
+ "settings": {
+ "title": "User settings for [{{username}} ]",
+ "form": {
+ "firstname": "First Name",
+ "firstname.placeholder": "Your first name",
+ "lastname": "Last Name",
+ "lastname.placeholder": "Your last name",
+ "language": "Language",
+ "button": "Save"
+ },
+ "messages": {
+ "error": {
+ "fail": "An error has occurred! Settings could not be saved.",
+ "emailexists": "E-mail is already in use! Please choose another one."
+ },
+ "success": "Settings saved! ",
+ "validate": {
+ "firstname": {
+ "required": "Your first name is required.",
+ "minlength": "Your first name is required to be at least 1 character",
+ "maxlength": "Your first name cannot be longer than 50 characters"
+ },
+ "lastname": {
+ "required": "Your last name is required.",
+ "minlength": "Your last name is required to be at least 1 character",
+ "maxlength": "Your last name cannot be longer than 50 characters"
+ }
+ }
+ }
+ }
+}
diff --git a/jhipster/src/main/webapp/i18n/en/user-management.json b/jhipster/src/main/webapp/i18n/en/user-management.json
new file mode 100644
index 000000000000..30c125b6d00b
--- /dev/null
+++ b/jhipster/src/main/webapp/i18n/en/user-management.json
@@ -0,0 +1,30 @@
+{
+ "userManagement": {
+ "home": {
+ "title": "Users",
+ "createLabel": "Create a new user",
+ "createOrEditLabel": "Create or edit a user"
+ },
+ "created": "A new user is created with identifier {{ param }}",
+ "updated": "An user is updated with identifier {{ param }}",
+ "deleted": "An user is deleted with identifier {{ param }}",
+ "delete": {
+ "question": "Are you sure you want to delete user {{ login }}?"
+ },
+ "detail": {
+ "title": "User"
+ },
+ "login": "Login",
+ "firstName": "First name",
+ "lastName": "Last name",
+ "email": "Email",
+ "activated": "Activated",
+ "deactivated": "Deactivated",
+ "profiles": "Profiles",
+ "langKey": "Language",
+ "createdBy": "Created by",
+ "createdDate": "Created date",
+ "lastModifiedBy": "Modified by",
+ "lastModifiedDate": "Modified date"
+ }
+}
diff --git a/jhipster/src/main/webapp/index.html b/jhipster/src/main/webapp/index.html
new file mode 100644
index 000000000000..6227d628230a
--- /dev/null
+++ b/jhipster/src/main/webapp/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ baeldung
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/main/webapp/robots.txt b/jhipster/src/main/webapp/robots.txt
new file mode 100644
index 000000000000..7de2585cf605
--- /dev/null
+++ b/jhipster/src/main/webapp/robots.txt
@@ -0,0 +1,11 @@
+# robotstxt.org/
+
+User-agent: *
+Disallow: /api/account
+Disallow: /api/account/change_password
+Disallow: /api/account/sessions
+Disallow: /api/audits/
+Disallow: /api/logs/
+Disallow: /api/users/
+Disallow: /management/
+Disallow: /v2/api-docs/
diff --git a/jhipster/src/main/webapp/swagger-ui/images/throbber.gif b/jhipster/src/main/webapp/swagger-ui/images/throbber.gif
new file mode 100644
index 000000000000..06393889242f
Binary files /dev/null and b/jhipster/src/main/webapp/swagger-ui/images/throbber.gif differ
diff --git a/jhipster/src/main/webapp/swagger-ui/index.html b/jhipster/src/main/webapp/swagger-ui/index.html
new file mode 100644
index 000000000000..42692c0d4573
--- /dev/null
+++ b/jhipster/src/main/webapp/swagger-ui/index.html
@@ -0,0 +1,177 @@
+
+
+
+
+ Swagger UI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/test/gatling/conf/gatling.conf b/jhipster/src/test/gatling/conf/gatling.conf
new file mode 100644
index 000000000000..e509853f225d
--- /dev/null
+++ b/jhipster/src/test/gatling/conf/gatling.conf
@@ -0,0 +1,131 @@
+#########################
+# Gatling Configuration #
+#########################
+
+# This file contains all the settings configurable for Gatling with their default values
+
+gatling {
+ core {
+ #outputDirectoryBaseName = "" # The prefix for each simulation result folder (then suffixed by the report generation timestamp)
+ #runDescription = "" # The description for this simulation run, displayed in each report
+ #encoding = "utf-8" # Encoding to use throughout Gatling for file and string manipulation
+ #simulationClass = "" # The FQCN of the simulation to run (when used in conjunction with noReports, the simulation for which assertions will be validated)
+ #mute = false # When set to true, don't ask for simulation name nor run description (currently only used by Gatling SBT plugin)
+ #elFileBodiesCacheMaxCapacity = 200 # Cache size for request body EL templates, set to 0 to disable
+ #rawFileBodiesCacheMaxCapacity = 200 # Cache size for request body Raw templates, set to 0 to disable
+ #rawFileBodiesInMemoryMaxSize = 1000 # Below this limit, raw file bodies will be cached in memory
+
+ extract {
+ regex {
+ #cacheMaxCapacity = 200 # Cache size for the compiled regexes, set to 0 to disable caching
+ }
+ xpath {
+ #cacheMaxCapacity = 200 # Cache size for the compiled XPath queries, set to 0 to disable caching
+ }
+ jsonPath {
+ #cacheMaxCapacity = 200 # Cache size for the compiled jsonPath queries, set to 0 to disable caching
+ #preferJackson = false # When set to true, prefer Jackson over Boon for JSON-related operations
+ }
+ css {
+ #cacheMaxCapacity = 200 # Cache size for the compiled CSS selectors queries, set to 0 to disable caching
+ }
+ }
+ directory {
+ #data = user-files/data # Folder where user's data (e.g. files used by Feeders) is located
+ #bodies = user-files/bodies # Folder where bodies are located
+ #simulations = user-files/simulations # Folder where the bundle's simulations are located
+ #reportsOnly = "" # If set, name of report folder to look for in order to generate its report
+ #binaries = "" # If set, name of the folder where compiles classes are located: Defaults to GATLING_HOME/target.
+ #results = results # Name of the folder where all reports folder are located
+ }
+ }
+ charting {
+ #noReports = false # When set to true, don't generate HTML reports
+ #maxPlotPerSeries = 1000 # Number of points per graph in Gatling reports
+ #useGroupDurationMetric = false # Switch group timings from cumulated response time to group duration.
+ indicators {
+ #lowerBound = 800 # Lower bound for the requests' response time to track in the reports and the console summary
+ #higherBound = 1200 # Higher bound for the requests' response time to track in the reports and the console summary
+ #percentile1 = 50 # Value for the 1st percentile to track in the reports, the console summary and Graphite
+ #percentile2 = 75 # Value for the 2nd percentile to track in the reports, the console summary and Graphite
+ #percentile3 = 95 # Value for the 3rd percentile to track in the reports, the console summary and Graphite
+ #percentile4 = 99 # Value for the 4th percentile to track in the reports, the console summary and Graphite
+ }
+ }
+ http {
+ #fetchedCssCacheMaxCapacity = 200 # Cache size for CSS parsed content, set to 0 to disable
+ #fetchedHtmlCacheMaxCapacity = 200 # Cache size for HTML parsed content, set to 0 to disable
+ #perUserCacheMaxCapacity = 200 # Per virtual user cache size, set to 0 to disable
+ #warmUpUrl = "http://gatling.io" # The URL to use to warm-up the HTTP stack (blank means disabled)
+ #enableGA = true # Very light Google Analytics, please support
+ ssl {
+ keyStore {
+ #type = "" # Type of SSLContext's KeyManagers store
+ #file = "" # Location of SSLContext's KeyManagers store
+ #password = "" # Password for SSLContext's KeyManagers store
+ #algorithm = "" # Algorithm used SSLContext's KeyManagers store
+ }
+ trustStore {
+ #type = "" # Type of SSLContext's TrustManagers store
+ #file = "" # Location of SSLContext's TrustManagers store
+ #password = "" # Password for SSLContext's TrustManagers store
+ #algorithm = "" # Algorithm used by SSLContext's TrustManagers store
+ }
+ }
+ ahc {
+ #keepAlive = true # Allow pooling HTTP connections (keep-alive header automatically added)
+ #connectTimeout = 10000 # Timeout when establishing a connection
+ #handshakeTimeout = 10000 # Timeout when performing TLS hashshake
+ #pooledConnectionIdleTimeout = 60000 # Timeout when a connection stays unused in the pool
+ #readTimeout = 60000 # Timeout when a used connection stays idle
+ #maxRetry = 2 # Number of times that a request should be tried again
+ #requestTimeout = 60000 # Timeout of the requests
+ #acceptAnyCertificate = true # When set to true, doesn't validate SSL certificates
+ #httpClientCodecMaxInitialLineLength = 4096 # Maximum length of the initial line of the response (e.g. "HTTP/1.0 200 OK")
+ #httpClientCodecMaxHeaderSize = 8192 # Maximum size, in bytes, of each request's headers
+ #httpClientCodecMaxChunkSize = 8192 # Maximum length of the content or each chunk
+ #webSocketMaxFrameSize = 10240000 # Maximum frame payload size
+ #sslEnabledProtocols = [TLSv1.2, TLSv1.1, TLSv1] # Array of enabled protocols for HTTPS, if empty use the JDK defaults
+ #sslEnabledCipherSuites = [] # Array of enabled cipher suites for HTTPS, if empty use the AHC defaults
+ #sslSessionCacheSize = 0 # SSLSession cache size, set to 0 to use JDK's default
+ #sslSessionTimeout = 0 # SSLSession timeout in seconds, set to 0 to use JDK's default (24h)
+ #useOpenSsl = false # if OpenSSL should be used instead of JSSE (requires tcnative jar)
+ #useNativeTransport = false # if native transport should be used instead of Java NIO (requires netty-transport-native-epoll, currently Linux only)
+ #tcpNoDelay = true
+ #soReuseAddress = false
+ #soLinger = -1
+ #soSndBuf = -1
+ #soRcvBuf = -1
+ #allocator = "pooled" # switch to unpooled for unpooled ByteBufAllocator
+ #maxThreadLocalCharBufferSize = 200000 # Netty's default is 16k
+ }
+ dns {
+ #queryTimeout = 5000 # Timeout of each DNS query in millis
+ #maxQueriesPerResolve = 6 # Maximum allowed number of DNS queries for a given name resolution
+ }
+ }
+ jms {
+ #acknowledgedMessagesBufferSize = 5000 # size of the buffer used to tracked acknowledged messages and protect against duplicate receives
+ }
+ data {
+ #writers = [console, file] # The list of DataWriters to which Gatling write simulation data (currently supported : console, file, graphite, jdbc)
+ console {
+ #light = false # When set to true, displays a light version without detailed request stats
+ }
+ file {
+ #bufferSize = 8192 # FileDataWriter's internal data buffer size, in bytes
+ }
+ leak {
+ #noActivityTimeout = 30 # Period, in seconds, for which Gatling may have no activity before considering a leak may be happening
+ }
+ graphite {
+ #light = false # only send the all* stats
+ #host = "localhost" # The host where the Carbon server is located
+ #port = 2003 # The port to which the Carbon server listens to (2003 is default for plaintext, 2004 is default for pickle)
+ #protocol = "tcp" # The protocol used to send data to Carbon (currently supported : "tcp", "udp")
+ #rootPathPrefix = "gatling" # The common prefix of all metrics sent to Graphite
+ #bufferSize = 8192 # GraphiteDataWriter's internal data buffer size, in bytes
+ #writeInterval = 1 # GraphiteDataWriter's write interval, in seconds
+ }
+ }
+}
diff --git a/jhipster/src/test/gatling/conf/logback.xml b/jhipster/src/test/gatling/conf/logback.xml
new file mode 100644
index 000000000000..7b037e6813a8
--- /dev/null
+++ b/jhipster/src/test/gatling/conf/logback.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jhipster/src/test/gatling/simulations/CommentGatlingTest.scala b/jhipster/src/test/gatling/simulations/CommentGatlingTest.scala
new file mode 100644
index 000000000000..93d066f9bb90
--- /dev/null
+++ b/jhipster/src/test/gatling/simulations/CommentGatlingTest.scala
@@ -0,0 +1,92 @@
+import _root_.io.gatling.core.scenario.Simulation
+import ch.qos.logback.classic.{Level, LoggerContext}
+import io.gatling.core.Predef._
+import io.gatling.http.Predef._
+import org.slf4j.LoggerFactory
+
+import scala.concurrent.duration._
+
+/**
+ * Performance test for the Comment entity.
+ */
+class CommentGatlingTest extends Simulation {
+
+ val context: LoggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
+ // Log all HTTP requests
+ //context.getLogger("io.gatling.http").setLevel(Level.valueOf("TRACE"))
+ // Log failed HTTP requests
+ //context.getLogger("io.gatling.http").setLevel(Level.valueOf("DEBUG"))
+
+ val baseURL = Option(System.getProperty("baseURL")) getOrElse """http://127.0.0.1:8080"""
+
+ val httpConf = http
+ .baseURL(baseURL)
+ .inferHtmlResources()
+ .acceptHeader("*/*")
+ .acceptEncodingHeader("gzip, deflate")
+ .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3")
+ .connectionHeader("keep-alive")
+ .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0")
+
+ val headers_http = Map(
+ "Accept" -> """application/json"""
+ )
+
+ val headers_http_authentication = Map(
+ "Content-Type" -> """application/json""",
+ "Accept" -> """application/json"""
+ )
+
+ val headers_http_authenticated = Map(
+ "Accept" -> """application/json""",
+ "Authorization" -> "${access_token}"
+ )
+
+ val scn = scenario("Test the Comment entity")
+ .exec(http("First unauthenticated request")
+ .get("/api/account")
+ .headers(headers_http)
+ .check(status.is(401))).exitHereIfFailed
+ .pause(10)
+ .exec(http("Authentication")
+ .post("/api/authenticate")
+ .headers(headers_http_authentication)
+ .body(StringBody("""{"username":"admin", "password":"admin"}""")).asJSON
+ .check(header.get("Authorization").saveAs("access_token"))).exitHereIfFailed
+ .pause(1)
+ .exec(http("Authenticated request")
+ .get("/api/account")
+ .headers(headers_http_authenticated)
+ .check(status.is(200)))
+ .pause(10)
+ .repeat(2) {
+ exec(http("Get all comments")
+ .get("/api/comments")
+ .headers(headers_http_authenticated)
+ .check(status.is(200)))
+ .pause(10 seconds, 20 seconds)
+ .exec(http("Create new comment")
+ .post("/api/comments")
+ .headers(headers_http_authenticated)
+ .body(StringBody("""{"id":null, "text":"SAMPLE_TEXT", "creationDate":"2020-01-01T00:00:00.000Z"}""")).asJSON
+ .check(status.is(201))
+ .check(headerRegex("Location", "(.*)").saveAs("new_comment_url"))).exitHereIfFailed
+ .pause(10)
+ .repeat(5) {
+ exec(http("Get created comment")
+ .get("${new_comment_url}")
+ .headers(headers_http_authenticated))
+ .pause(10)
+ }
+ .exec(http("Delete created comment")
+ .delete("${new_comment_url}")
+ .headers(headers_http_authenticated))
+ .pause(10)
+ }
+
+ val users = scenario("Users").exec(scn)
+
+ setUp(
+ users.inject(rampUsers(100) over (1 minutes))
+ ).protocols(httpConf)
+}
diff --git a/jhipster/src/test/gatling/simulations/PostGatlingTest.scala b/jhipster/src/test/gatling/simulations/PostGatlingTest.scala
new file mode 100644
index 000000000000..d76198c9ae21
--- /dev/null
+++ b/jhipster/src/test/gatling/simulations/PostGatlingTest.scala
@@ -0,0 +1,92 @@
+import _root_.io.gatling.core.scenario.Simulation
+import ch.qos.logback.classic.{Level, LoggerContext}
+import io.gatling.core.Predef._
+import io.gatling.http.Predef._
+import org.slf4j.LoggerFactory
+
+import scala.concurrent.duration._
+
+/**
+ * Performance test for the Post entity.
+ */
+class PostGatlingTest extends Simulation {
+
+ val context: LoggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
+ // Log all HTTP requests
+ //context.getLogger("io.gatling.http").setLevel(Level.valueOf("TRACE"))
+ // Log failed HTTP requests
+ //context.getLogger("io.gatling.http").setLevel(Level.valueOf("DEBUG"))
+
+ val baseURL = Option(System.getProperty("baseURL")) getOrElse """http://127.0.0.1:8080"""
+
+ val httpConf = http
+ .baseURL(baseURL)
+ .inferHtmlResources()
+ .acceptHeader("*/*")
+ .acceptEncodingHeader("gzip, deflate")
+ .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3")
+ .connectionHeader("keep-alive")
+ .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0")
+
+ val headers_http = Map(
+ "Accept" -> """application/json"""
+ )
+
+ val headers_http_authentication = Map(
+ "Content-Type" -> """application/json""",
+ "Accept" -> """application/json"""
+ )
+
+ val headers_http_authenticated = Map(
+ "Accept" -> """application/json""",
+ "Authorization" -> "${access_token}"
+ )
+
+ val scn = scenario("Test the Post entity")
+ .exec(http("First unauthenticated request")
+ .get("/api/account")
+ .headers(headers_http)
+ .check(status.is(401))).exitHereIfFailed
+ .pause(10)
+ .exec(http("Authentication")
+ .post("/api/authenticate")
+ .headers(headers_http_authentication)
+ .body(StringBody("""{"username":"admin", "password":"admin"}""")).asJSON
+ .check(header.get("Authorization").saveAs("access_token"))).exitHereIfFailed
+ .pause(1)
+ .exec(http("Authenticated request")
+ .get("/api/account")
+ .headers(headers_http_authenticated)
+ .check(status.is(200)))
+ .pause(10)
+ .repeat(2) {
+ exec(http("Get all posts")
+ .get("/api/posts")
+ .headers(headers_http_authenticated)
+ .check(status.is(200)))
+ .pause(10 seconds, 20 seconds)
+ .exec(http("Create new post")
+ .post("/api/posts")
+ .headers(headers_http_authenticated)
+ .body(StringBody("""{"id":null, "title":"SAMPLE_TEXT", "content":"SAMPLE_TEXT", "creationDate":"2020-01-01T00:00:00.000Z"}""")).asJSON
+ .check(status.is(201))
+ .check(headerRegex("Location", "(.*)").saveAs("new_post_url"))).exitHereIfFailed
+ .pause(10)
+ .repeat(5) {
+ exec(http("Get created post")
+ .get("${new_post_url}")
+ .headers(headers_http_authenticated))
+ .pause(10)
+ }
+ .exec(http("Delete created post")
+ .delete("${new_post_url}")
+ .headers(headers_http_authenticated))
+ .pause(10)
+ }
+
+ val users = scenario("Users").exec(scn)
+
+ setUp(
+ users.inject(rampUsers(100) over (1 minutes))
+ ).protocols(httpConf)
+}
diff --git a/jhipster/src/test/java/com/baeldung/security/SecurityUtilsUnitTest.java b/jhipster/src/test/java/com/baeldung/security/SecurityUtilsUnitTest.java
new file mode 100644
index 000000000000..b78a18790b5b
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/security/SecurityUtilsUnitTest.java
@@ -0,0 +1,50 @@
+package com.baeldung.security;
+
+import org.junit.Test;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+* Test class for the SecurityUtils utility class.
+*
+* @see SecurityUtils
+*/
+public class SecurityUtilsUnitTest {
+
+ @Test
+ public void testgetCurrentUserLogin() {
+ SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+ securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin"));
+ SecurityContextHolder.setContext(securityContext);
+ String login = SecurityUtils.getCurrentUserLogin();
+ assertThat(login).isEqualTo("admin");
+ }
+
+ @Test
+ public void testIsAuthenticated() {
+ SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+ securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin"));
+ SecurityContextHolder.setContext(securityContext);
+ boolean isAuthenticated = SecurityUtils.isAuthenticated();
+ assertThat(isAuthenticated).isTrue();
+ }
+
+ @Test
+ public void testAnonymousIsNotAuthenticated() {
+ SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+ Collection authorities = new ArrayList<>();
+ authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS));
+ securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities));
+ SecurityContextHolder.setContext(securityContext);
+ boolean isAuthenticated = SecurityUtils.isAuthenticated();
+ assertThat(isAuthenticated).isFalse();
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/security/jwt/TokenProviderTest.java b/jhipster/src/test/java/com/baeldung/security/jwt/TokenProviderTest.java
new file mode 100644
index 000000000000..3fec4bfb88a1
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/security/jwt/TokenProviderTest.java
@@ -0,0 +1,107 @@
+package com.baeldung.security.jwt;
+
+import com.baeldung.security.AuthoritiesConstants;
+import io.github.jhipster.config.JHipsterProperties;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TokenProviderTest {
+
+ private final String secretKey = "e5c9ee274ae87bc031adda32e27fa98b9290da83";
+ private final long ONE_MINUTE = 60000;
+ private JHipsterProperties jHipsterProperties;
+ private TokenProvider tokenProvider;
+
+ @Before
+ public void setup() {
+ jHipsterProperties = Mockito.mock(JHipsterProperties.class);
+ tokenProvider = new TokenProvider(jHipsterProperties);
+ ReflectionTestUtils.setField(tokenProvider, "secretKey", secretKey);
+ ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", ONE_MINUTE);
+ }
+
+ @Test
+ public void testReturnFalseWhenJWThasInvalidSignature() {
+ boolean isTokenValid = tokenProvider.validateToken(createTokenWithDifferentSignature());
+
+ assertThat(isTokenValid).isEqualTo(false);
+ }
+
+ @Test
+ public void testReturnFalseWhenJWTisMalformed() {
+ Authentication authentication = createAuthentication();
+ String token = tokenProvider.createToken(authentication, false);
+ String invalidToken = token.substring(1);
+ boolean isTokenValid = tokenProvider.validateToken(invalidToken);
+
+ assertThat(isTokenValid).isEqualTo(false);
+ }
+
+ @Test
+ public void testReturnFalseWhenJWTisExpired() {
+ ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", -ONE_MINUTE);
+
+ Authentication authentication = createAuthentication();
+ String token = tokenProvider.createToken(authentication, false);
+
+ boolean isTokenValid = tokenProvider.validateToken(token);
+
+ assertThat(isTokenValid).isEqualTo(false);
+ }
+
+ @Test
+ public void testReturnFalseWhenJWTisUnsupported() {
+ Date expirationDate = new Date(new Date().getTime() + ONE_MINUTE);
+
+ Authentication authentication = createAuthentication();
+
+ String unsupportedToken = createUnsupportedToken();
+
+ boolean isTokenValid = tokenProvider.validateToken(unsupportedToken);
+
+ assertThat(isTokenValid).isEqualTo(false);
+ }
+
+ @Test
+ public void testReturnFalseWhenJWTisInvalid() {
+
+ boolean isTokenValid = tokenProvider.validateToken("");
+
+ assertThat(isTokenValid).isEqualTo(false);
+ }
+
+ private Authentication createAuthentication() {
+ Collection authorities = new ArrayList<>();
+ authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS));
+ return new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities);
+ }
+
+ private String createUnsupportedToken() {
+ return Jwts.builder()
+ .setPayload("payload")
+ .signWith(SignatureAlgorithm.HS512, secretKey)
+ .compact();
+ }
+
+ private String createTokenWithDifferentSignature() {
+ return Jwts.builder()
+ .setSubject("anonymous")
+ .signWith(SignatureAlgorithm.HS512, "e5c9ee274ae87bc031adda32e27fa98b9290da90")
+ .setExpiration(new Date(new Date().getTime() + ONE_MINUTE))
+ .compact();
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/service/UserServiceIntTest.java b/jhipster/src/test/java/com/baeldung/service/UserServiceIntTest.java
new file mode 100644
index 000000000000..968f0a7f08ac
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/service/UserServiceIntTest.java
@@ -0,0 +1,129 @@
+package com.baeldung.service;
+
+import com.baeldung.BaeldungApp;
+import com.baeldung.domain.User;
+import com.baeldung.config.Constants;
+import com.baeldung.repository.UserRepository;
+import com.baeldung.service.dto.UserDTO;
+import java.time.ZonedDateTime;
+import com.baeldung.service.util.RandomUtil;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import java.util.Optional;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+/**
+ * Test class for the UserResource REST controller.
+ *
+ * @see UserService
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = BaeldungApp.class)
+@Transactional
+public class UserServiceIntTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private UserService userService;
+
+ @Test
+ public void assertThatUserMustExistToResetPassword() {
+ Optional maybeUser = userService.requestPasswordReset("john.doe@localhost");
+ assertThat(maybeUser.isPresent()).isFalse();
+
+ maybeUser = userService.requestPasswordReset("admin@localhost");
+ assertThat(maybeUser.isPresent()).isTrue();
+
+ assertThat(maybeUser.get().getEmail()).isEqualTo("admin@localhost");
+ assertThat(maybeUser.get().getResetDate()).isNotNull();
+ assertThat(maybeUser.get().getResetKey()).isNotNull();
+ }
+
+ @Test
+ public void assertThatOnlyActivatedUserCanRequestPasswordReset() {
+ User user = userService.createUser("johndoe", "johndoe", "John", "Doe", "john.doe@localhost", "http://placehold.it/50x50", "en-US");
+ Optional maybeUser = userService.requestPasswordReset("john.doe@localhost");
+ assertThat(maybeUser.isPresent()).isFalse();
+ userRepository.delete(user);
+ }
+
+ @Test
+ public void assertThatResetKeyMustNotBeOlderThan24Hours() {
+ User user = userService.createUser("johndoe", "johndoe", "John", "Doe", "john.doe@localhost", "http://placehold.it/50x50", "en-US");
+
+ ZonedDateTime daysAgo = ZonedDateTime.now().minusHours(25);
+ String resetKey = RandomUtil.generateResetKey();
+ user.setActivated(true);
+ user.setResetDate(daysAgo);
+ user.setResetKey(resetKey);
+
+ userRepository.save(user);
+
+ Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey());
+
+ assertThat(maybeUser.isPresent()).isFalse();
+
+ userRepository.delete(user);
+ }
+
+ @Test
+ public void assertThatResetKeyMustBeValid() {
+ User user = userService.createUser("johndoe", "johndoe", "John", "Doe", "john.doe@localhost", "http://placehold.it/50x50", "en-US");
+
+ ZonedDateTime daysAgo = ZonedDateTime.now().minusHours(25);
+ user.setActivated(true);
+ user.setResetDate(daysAgo);
+ user.setResetKey("1234");
+ userRepository.save(user);
+ Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey());
+ assertThat(maybeUser.isPresent()).isFalse();
+ userRepository.delete(user);
+ }
+
+ @Test
+ public void assertThatUserCanResetPassword() {
+ User user = userService.createUser("johndoe", "johndoe", "John", "Doe", "john.doe@localhost", "http://placehold.it/50x50", "en-US");
+ String oldPassword = user.getPassword();
+ ZonedDateTime daysAgo = ZonedDateTime.now().minusHours(2);
+ String resetKey = RandomUtil.generateResetKey();
+ user.setActivated(true);
+ user.setResetDate(daysAgo);
+ user.setResetKey(resetKey);
+ userRepository.save(user);
+ Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey());
+ assertThat(maybeUser.isPresent()).isTrue();
+ assertThat(maybeUser.get().getResetDate()).isNull();
+ assertThat(maybeUser.get().getResetKey()).isNull();
+ assertThat(maybeUser.get().getPassword()).isNotEqualTo(oldPassword);
+
+ userRepository.delete(user);
+ }
+
+ @Test
+ public void testFindNotActivatedUsersByCreationDateBefore() {
+ userService.removeNotActivatedUsers();
+ ZonedDateTime now = ZonedDateTime.now();
+ List users = userRepository.findAllByActivatedIsFalseAndCreatedDateBefore(now.minusDays(3));
+ assertThat(users).isEmpty();
+ }
+
+ @Test
+ public void assertThatAnonymousUserIsNotGet() {
+ final PageRequest pageable = new PageRequest(0, (int) userRepository.count());
+ final Page allManagedUsers = userService.getAllManagedUsers(pageable);
+ assertThat(allManagedUsers.getContent().stream()
+ .noneMatch(user -> Constants.ANONYMOUS_USER.equals(user.getLogin())))
+ .isTrue();
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/web/rest/AccountResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/AccountResourceIntTest.java
new file mode 100644
index 000000000000..e42ce1c6d481
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/web/rest/AccountResourceIntTest.java
@@ -0,0 +1,395 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.BaeldungApp;
+import com.baeldung.domain.Authority;
+import com.baeldung.domain.User;
+import com.baeldung.repository.AuthorityRepository;
+import com.baeldung.repository.UserRepository;
+import com.baeldung.security.AuthoritiesConstants;
+import com.baeldung.service.MailService;
+import com.baeldung.service.UserService;
+import com.baeldung.service.dto.UserDTO;
+import com.baeldung.web.rest.vm.ManagedUserVM;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+/**
+ * Test class for the AccountResource REST controller.
+ *
+ * @see AccountResource
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = BaeldungApp.class)
+public class AccountResourceIntTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private AuthorityRepository authorityRepository;
+
+ @Autowired
+ private UserService userService;
+
+ @Mock
+ private UserService mockUserService;
+
+ @Mock
+ private MailService mockMailService;
+
+ private MockMvc restUserMockMvc;
+
+ private MockMvc restMvc;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ doNothing().when(mockMailService).sendActivationEmail(anyObject());
+
+ AccountResource accountResource =
+ new AccountResource(userRepository, userService, mockMailService);
+
+ AccountResource accountUserMockResource =
+ new AccountResource(userRepository, mockUserService, mockMailService);
+
+ this.restMvc = MockMvcBuilders.standaloneSetup(accountResource).build();
+ this.restUserMockMvc = MockMvcBuilders.standaloneSetup(accountUserMockResource).build();
+ }
+
+ @Test
+ public void testNonAuthenticatedUser() throws Exception {
+ restUserMockMvc.perform(get("/api/authenticate")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().string(""));
+ }
+
+ @Test
+ public void testAuthenticatedUser() throws Exception {
+ restUserMockMvc.perform(get("/api/authenticate")
+ .with(request -> {
+ request.setRemoteUser("test");
+ return request;
+ })
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().string("test"));
+ }
+
+ @Test
+ public void testGetExistingAccount() throws Exception {
+ Set authorities = new HashSet<>();
+ Authority authority = new Authority();
+ authority.setName(AuthoritiesConstants.ADMIN);
+ authorities.add(authority);
+
+ User user = new User();
+ user.setLogin("test");
+ user.setFirstName("john");
+ user.setLastName("doe");
+ user.setEmail("john.doe@jhipster.com");
+ user.setImageUrl("http://placehold.it/50x50");
+ user.setAuthorities(authorities);
+ when(mockUserService.getUserWithAuthorities()).thenReturn(user);
+
+ restUserMockMvc.perform(get("/api/account")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.login").value("test"))
+ .andExpect(jsonPath("$.firstName").value("john"))
+ .andExpect(jsonPath("$.lastName").value("doe"))
+ .andExpect(jsonPath("$.email").value("john.doe@jhipster.com"))
+ .andExpect(jsonPath("$.imageUrl").value("http://placehold.it/50x50"))
+ .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN));
+ }
+
+ @Test
+ public void testGetUnknownAccount() throws Exception {
+ when(mockUserService.getUserWithAuthorities()).thenReturn(null);
+
+ restUserMockMvc.perform(get("/api/account")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isInternalServerError());
+ }
+
+ @Test
+ @Transactional
+ public void testRegisterValid() throws Exception {
+ ManagedUserVM validUser = new ManagedUserVM(
+ null, // id
+ "joe", // login
+ "password", // password
+ "Joe", // firstName
+ "Shmoe", // lastName
+ "joe@example.com", // e-mail
+ true, // activated
+ "http://placehold.it/50x50", //imageUrl
+ "en", // langKey
+ null, // createdBy
+ null, // createdDate
+ null, // lastModifiedBy
+ null, // lastModifiedDate
+ new HashSet<>(Arrays.asList(AuthoritiesConstants.USER)));
+
+ restMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(validUser)))
+ .andExpect(status().isCreated());
+
+ Optional user = userRepository.findOneByLogin("joe");
+ assertThat(user.isPresent()).isTrue();
+ }
+
+ @Test
+ @Transactional
+ public void testRegisterInvalidLogin() throws Exception {
+ ManagedUserVM invalidUser = new ManagedUserVM(
+ null, // id
+ "funky-log!n", // login <-- invalid
+ "password", // password
+ "Funky", // firstName
+ "One", // lastName
+ "funky@example.com", // e-mail
+ true, // activated
+ "http://placehold.it/50x50", //imageUrl
+ "en", // langKey
+ null, // createdBy
+ null, // createdDate
+ null, // lastModifiedBy
+ null, // lastModifiedDate
+ new HashSet<>(Arrays.asList(AuthoritiesConstants.USER)));
+
+ restUserMockMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(invalidUser)))
+ .andExpect(status().isBadRequest());
+
+ Optional user = userRepository.findOneByEmail("funky@example.com");
+ assertThat(user.isPresent()).isFalse();
+ }
+
+ @Test
+ @Transactional
+ public void testRegisterInvalidEmail() throws Exception {
+ ManagedUserVM invalidUser = new ManagedUserVM(
+ null, // id
+ "bob", // login
+ "password", // password
+ "Bob", // firstName
+ "Green", // lastName
+ "invalid", // e-mail <-- invalid
+ true, // activated
+ "http://placehold.it/50x50", //imageUrl
+ "en", // langKey
+ null, // createdBy
+ null, // createdDate
+ null, // lastModifiedBy
+ null, // lastModifiedDate
+ new HashSet<>(Arrays.asList(AuthoritiesConstants.USER)));
+
+ restUserMockMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(invalidUser)))
+ .andExpect(status().isBadRequest());
+
+ Optional user = userRepository.findOneByLogin("bob");
+ assertThat(user.isPresent()).isFalse();
+ }
+
+ @Test
+ @Transactional
+ public void testRegisterInvalidPassword() throws Exception {
+ ManagedUserVM invalidUser = new ManagedUserVM(
+ null, // id
+ "bob", // login
+ "123", // password with only 3 digits
+ "Bob", // firstName
+ "Green", // lastName
+ "bob@example.com", // e-mail
+ true, // activated
+ "http://placehold.it/50x50", //imageUrl
+ "en", // langKey
+ null, // createdBy
+ null, // createdDate
+ null, // lastModifiedBy
+ null, // lastModifiedDate
+ new HashSet<>(Arrays.asList(AuthoritiesConstants.USER)));
+
+ restUserMockMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(invalidUser)))
+ .andExpect(status().isBadRequest());
+
+ Optional user = userRepository.findOneByLogin("bob");
+ assertThat(user.isPresent()).isFalse();
+ }
+
+ @Test
+ @Transactional
+ public void testRegisterDuplicateLogin() throws Exception {
+ // Good
+ ManagedUserVM validUser = new ManagedUserVM(
+ null, // id
+ "alice", // login
+ "password", // password
+ "Alice", // firstName
+ "Something", // lastName
+ "alice@example.com", // e-mail
+ true, // activated
+ "http://placehold.it/50x50", //imageUrl
+ "en", // langKey
+ null, // createdBy
+ null, // createdDate
+ null, // lastModifiedBy
+ null, // lastModifiedDate
+ new HashSet<>(Arrays.asList(AuthoritiesConstants.USER)));
+
+ // Duplicate login, different e-mail
+ ManagedUserVM duplicatedUser = new ManagedUserVM(validUser.getId(), validUser.getLogin(), validUser.getPassword(), validUser.getLogin(), validUser.getLastName(),
+ "alicejr@example.com", true, validUser.getImageUrl(), validUser.getLangKey(), validUser.getCreatedBy(), validUser.getCreatedDate(), validUser.getLastModifiedBy(), validUser.getLastModifiedDate(), validUser.getAuthorities());
+
+ // Good user
+ restMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(validUser)))
+ .andExpect(status().isCreated());
+
+ // Duplicate login
+ restMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(duplicatedUser)))
+ .andExpect(status().is4xxClientError());
+
+ Optional userDup = userRepository.findOneByEmail("alicejr@example.com");
+ assertThat(userDup.isPresent()).isFalse();
+ }
+
+ @Test
+ @Transactional
+ public void testRegisterDuplicateEmail() throws Exception {
+ // Good
+ ManagedUserVM validUser = new ManagedUserVM(
+ null, // id
+ "john", // login
+ "password", // password
+ "John", // firstName
+ "Doe", // lastName
+ "john@example.com", // e-mail
+ true, // activated
+ "http://placehold.it/50x50", //imageUrl
+ "en", // langKey
+ null, // createdBy
+ null, // createdDate
+ null, // lastModifiedBy
+ null, // lastModifiedDate
+ new HashSet<>(Arrays.asList(AuthoritiesConstants.USER)));
+
+ // Duplicate e-mail, different login
+ ManagedUserVM duplicatedUser = new ManagedUserVM(validUser.getId(), "johnjr", validUser.getPassword(), validUser.getLogin(), validUser.getLastName(),
+ validUser.getEmail(), true, validUser.getImageUrl(), validUser.getLangKey(), validUser.getCreatedBy(), validUser.getCreatedDate(), validUser.getLastModifiedBy(), validUser.getLastModifiedDate(), validUser.getAuthorities());
+
+ // Good user
+ restMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(validUser)))
+ .andExpect(status().isCreated());
+
+ // Duplicate e-mail
+ restMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(duplicatedUser)))
+ .andExpect(status().is4xxClientError());
+
+ Optional userDup = userRepository.findOneByLogin("johnjr");
+ assertThat(userDup.isPresent()).isFalse();
+ }
+
+ @Test
+ @Transactional
+ public void testRegisterAdminIsIgnored() throws Exception {
+ ManagedUserVM validUser = new ManagedUserVM(
+ null, // id
+ "badguy", // login
+ "password", // password
+ "Bad", // firstName
+ "Guy", // lastName
+ "badguy@example.com", // e-mail
+ true, // activated
+ "http://placehold.it/50x50", //imageUrl
+ "en", // langKey
+ null, // createdBy
+ null, // createdDate
+ null, // lastModifiedBy
+ null, // lastModifiedDate
+ new HashSet<>(Arrays.asList(AuthoritiesConstants.ADMIN)));
+
+ restMvc.perform(
+ post("/api/register")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(validUser)))
+ .andExpect(status().isCreated());
+
+ Optional userDup = userRepository.findOneByLogin("badguy");
+ assertThat(userDup.isPresent()).isTrue();
+ assertThat(userDup.get().getAuthorities()).hasSize(1)
+ .containsExactly(authorityRepository.findOne(AuthoritiesConstants.USER));
+ }
+
+ @Test
+ @Transactional
+ public void testSaveInvalidLogin() throws Exception {
+ UserDTO invalidUser = new UserDTO(
+ null, // id
+ "funky-log!n", // login <-- invalid
+ "Funky", // firstName
+ "One", // lastName
+ "funky@example.com", // e-mail
+ true, // activated
+ "http://placehold.it/50x50", //imageUrl
+ "en", // langKey
+ null, // createdBy
+ null, // createdDate
+ null, // lastModifiedBy
+ null, // lastModifiedDate
+ new HashSet<>(Arrays.asList(AuthoritiesConstants.USER))
+ );
+
+ restUserMockMvc.perform(
+ post("/api/account")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(invalidUser)))
+ .andExpect(status().isBadRequest());
+
+ Optional user = userRepository.findOneByEmail("funky@example.com");
+ assertThat(user.isPresent()).isFalse();
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/web/rest/AuditResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/AuditResourceIntTest.java
new file mode 100644
index 000000000000..127cb36f0755
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/web/rest/AuditResourceIntTest.java
@@ -0,0 +1,147 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.BaeldungApp;
+import com.baeldung.config.audit.AuditEventConverter;
+import com.baeldung.domain.PersistentAuditEvent;
+import com.baeldung.repository.PersistenceAuditEventRepository;
+import com.baeldung.service.AuditEventService;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
+import org.springframework.format.support.FormattingConversionService;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import static org.hamcrest.Matchers.hasItem;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+/**
+ * Test class for the AuditResource REST controller.
+ *
+ * @see AuditResource
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = BaeldungApp.class)
+@Transactional
+public class AuditResourceIntTest {
+
+ private static final String SAMPLE_PRINCIPAL = "SAMPLE_PRINCIPAL";
+ private static final String SAMPLE_TYPE = "SAMPLE_TYPE";
+ private static final LocalDateTime SAMPLE_TIMESTAMP = LocalDateTime.parse("2015-08-04T10:11:30");
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+ @Autowired
+ private PersistenceAuditEventRepository auditEventRepository;
+
+ @Autowired
+ private AuditEventConverter auditEventConverter;
+
+ @Autowired
+ private MappingJackson2HttpMessageConverter jacksonMessageConverter;
+
+ @Autowired
+ private FormattingConversionService formattingConversionService;
+
+ @Autowired
+ private PageableHandlerMethodArgumentResolver pageableArgumentResolver;
+
+ private PersistentAuditEvent auditEvent;
+
+ private MockMvc restAuditMockMvc;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ AuditEventService auditEventService =
+ new AuditEventService(auditEventRepository, auditEventConverter);
+ AuditResource auditResource = new AuditResource(auditEventService);
+ this.restAuditMockMvc = MockMvcBuilders.standaloneSetup(auditResource)
+ .setCustomArgumentResolvers(pageableArgumentResolver)
+ .setConversionService(formattingConversionService)
+ .setMessageConverters(jacksonMessageConverter).build();
+ }
+
+ @Before
+ public void initTest() {
+ auditEventRepository.deleteAll();
+ auditEvent = new PersistentAuditEvent();
+ auditEvent.setAuditEventType(SAMPLE_TYPE);
+ auditEvent.setPrincipal(SAMPLE_PRINCIPAL);
+ auditEvent.setAuditEventDate(SAMPLE_TIMESTAMP);
+ }
+
+ @Test
+ public void getAllAudits() throws Exception {
+ // Initialize the database
+ auditEventRepository.save(auditEvent);
+
+ // Get all the audits
+ restAuditMockMvc.perform(get("/management/audits"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.[*].principal").value(hasItem(SAMPLE_PRINCIPAL)));
+ }
+
+ @Test
+ public void getAudit() throws Exception {
+ // Initialize the database
+ auditEventRepository.save(auditEvent);
+
+ // Get the audit
+ restAuditMockMvc.perform(get("/management/audits/{id}", auditEvent.getId()))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.principal").value(SAMPLE_PRINCIPAL));
+ }
+
+ @Test
+ public void getAuditsByDate() throws Exception {
+ // Initialize the database
+ auditEventRepository.save(auditEvent);
+
+ // Generate dates for selecting audits by date, making sure the period will contain the audit
+ String fromDate = SAMPLE_TIMESTAMP.minusDays(1).format(FORMATTER);
+ String toDate = SAMPLE_TIMESTAMP.plusDays(1).format(FORMATTER);
+
+ // Get the audit
+ restAuditMockMvc.perform(get("/management/audits?fromDate="+fromDate+"&toDate="+toDate))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.[*].principal").value(hasItem(SAMPLE_PRINCIPAL)));
+ }
+
+ @Test
+ public void getNonExistingAuditsByDate() throws Exception {
+ // Initialize the database
+ auditEventRepository.save(auditEvent);
+
+ // Generate dates for selecting audits by date, making sure the period will not contain the sample audit
+ String fromDate = SAMPLE_TIMESTAMP.minusDays(2).format(FORMATTER);
+ String toDate = SAMPLE_TIMESTAMP.minusDays(1).format(FORMATTER);
+
+ // Query audits but expect no results
+ restAuditMockMvc.perform(get("/management/audits?fromDate=" + fromDate + "&toDate=" + toDate))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(header().string("X-Total-Count", "0"));
+ }
+
+ @Test
+ public void getNonExistingAudit() throws Exception {
+ // Get the audit
+ restAuditMockMvc.perform(get("/management/audits/{id}", Long.MAX_VALUE))
+ .andExpect(status().isNotFound());
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/web/rest/CommentResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/CommentResourceIntTest.java
new file mode 100644
index 000000000000..04b16b25f879
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/web/rest/CommentResourceIntTest.java
@@ -0,0 +1,279 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.BaeldungApp;
+
+import com.baeldung.domain.Comment;
+import com.baeldung.domain.Post;
+import com.baeldung.repository.CommentRepository;
+import com.baeldung.web.rest.errors.ExceptionTranslator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+/**
+ * Test class for the CommentResource REST controller.
+ *
+ * @see CommentResource
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = BaeldungApp.class)
+public class CommentResourceIntTest {
+
+ private static final String DEFAULT_TEXT = "AAAAAAAAAA";
+ private static final String UPDATED_TEXT = "BBBBBBBBBB";
+
+ private static final LocalDate DEFAULT_CREATION_DATE = LocalDate.ofEpochDay(0L);
+ private static final LocalDate UPDATED_CREATION_DATE = LocalDate.now(ZoneId.systemDefault());
+
+ @Autowired
+ private CommentRepository commentRepository;
+
+ @Autowired
+ private MappingJackson2HttpMessageConverter jacksonMessageConverter;
+
+ @Autowired
+ private PageableHandlerMethodArgumentResolver pageableArgumentResolver;
+
+ @Autowired
+ private ExceptionTranslator exceptionTranslator;
+
+ @Autowired
+ private EntityManager em;
+
+ private MockMvc restCommentMockMvc;
+
+ private Comment comment;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ CommentResource commentResource = new CommentResource(commentRepository);
+ this.restCommentMockMvc = MockMvcBuilders.standaloneSetup(commentResource)
+ .setCustomArgumentResolvers(pageableArgumentResolver)
+ .setControllerAdvice(exceptionTranslator)
+ .setMessageConverters(jacksonMessageConverter).build();
+ }
+
+ /**
+ * Create an entity for this test.
+ *
+ * This is a static method, as tests for other entities might also need it,
+ * if they test an entity which requires the current entity.
+ */
+ public static Comment createEntity(EntityManager em) {
+ Comment comment = new Comment()
+ .text(DEFAULT_TEXT)
+ .creationDate(DEFAULT_CREATION_DATE);
+ // Add required entity
+ Post post = PostResourceIntTest.createEntity(em);
+ em.persist(post);
+ em.flush();
+ comment.setPost(post);
+ return comment;
+ }
+
+ @Before
+ public void initTest() {
+ comment = createEntity(em);
+ }
+
+ @Test
+ @Transactional
+ public void createComment() throws Exception {
+ int databaseSizeBeforeCreate = commentRepository.findAll().size();
+
+ // Create the Comment
+ restCommentMockMvc.perform(post("/api/comments")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(comment)))
+ .andExpect(status().isCreated());
+
+ // Validate the Comment in the database
+ List commentList = commentRepository.findAll();
+ assertThat(commentList).hasSize(databaseSizeBeforeCreate + 1);
+ Comment testComment = commentList.get(commentList.size() - 1);
+ assertThat(testComment.getText()).isEqualTo(DEFAULT_TEXT);
+ assertThat(testComment.getCreationDate()).isEqualTo(DEFAULT_CREATION_DATE);
+ }
+
+ @Test
+ @Transactional
+ public void createCommentWithExistingId() throws Exception {
+ int databaseSizeBeforeCreate = commentRepository.findAll().size();
+
+ // Create the Comment with an existing ID
+ comment.setId(1L);
+
+ // An entity with an existing ID cannot be created, so this API call must fail
+ restCommentMockMvc.perform(post("/api/comments")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(comment)))
+ .andExpect(status().isBadRequest());
+
+ // Validate the Alice in the database
+ List commentList = commentRepository.findAll();
+ assertThat(commentList).hasSize(databaseSizeBeforeCreate);
+ }
+
+ @Test
+ @Transactional
+ public void checkTextIsRequired() throws Exception {
+ int databaseSizeBeforeTest = commentRepository.findAll().size();
+ // set the field null
+ comment.setText(null);
+
+ // Create the Comment, which fails.
+
+ restCommentMockMvc.perform(post("/api/comments")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(comment)))
+ .andExpect(status().isBadRequest());
+
+ List commentList = commentRepository.findAll();
+ assertThat(commentList).hasSize(databaseSizeBeforeTest);
+ }
+
+ @Test
+ @Transactional
+ public void checkCreationDateIsRequired() throws Exception {
+ int databaseSizeBeforeTest = commentRepository.findAll().size();
+ // set the field null
+ comment.setCreationDate(null);
+
+ // Create the Comment, which fails.
+
+ restCommentMockMvc.perform(post("/api/comments")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(comment)))
+ .andExpect(status().isBadRequest());
+
+ List commentList = commentRepository.findAll();
+ assertThat(commentList).hasSize(databaseSizeBeforeTest);
+ }
+
+ @Test
+ @Transactional
+ public void getAllComments() throws Exception {
+ // Initialize the database
+ commentRepository.saveAndFlush(comment);
+
+ // Get all the commentList
+ restCommentMockMvc.perform(get("/api/comments?sort=id,desc"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.[*].id").value(hasItem(comment.getId().intValue())))
+ .andExpect(jsonPath("$.[*].text").value(hasItem(DEFAULT_TEXT.toString())))
+ .andExpect(jsonPath("$.[*].creationDate").value(hasItem(DEFAULT_CREATION_DATE.toString())));
+ }
+
+ @Test
+ @Transactional
+ public void getComment() throws Exception {
+ // Initialize the database
+ commentRepository.saveAndFlush(comment);
+
+ // Get the comment
+ restCommentMockMvc.perform(get("/api/comments/{id}", comment.getId()))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.id").value(comment.getId().intValue()))
+ .andExpect(jsonPath("$.text").value(DEFAULT_TEXT.toString()))
+ .andExpect(jsonPath("$.creationDate").value(DEFAULT_CREATION_DATE.toString()));
+ }
+
+ @Test
+ @Transactional
+ public void getNonExistingComment() throws Exception {
+ // Get the comment
+ restCommentMockMvc.perform(get("/api/comments/{id}", Long.MAX_VALUE))
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ @Transactional
+ public void updateComment() throws Exception {
+ // Initialize the database
+ commentRepository.saveAndFlush(comment);
+ int databaseSizeBeforeUpdate = commentRepository.findAll().size();
+
+ // Update the comment
+ Comment updatedComment = commentRepository.findOne(comment.getId());
+ updatedComment
+ .text(UPDATED_TEXT)
+ .creationDate(UPDATED_CREATION_DATE);
+
+ restCommentMockMvc.perform(put("/api/comments")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(updatedComment)))
+ .andExpect(status().isOk());
+
+ // Validate the Comment in the database
+ List commentList = commentRepository.findAll();
+ assertThat(commentList).hasSize(databaseSizeBeforeUpdate);
+ Comment testComment = commentList.get(commentList.size() - 1);
+ assertThat(testComment.getText()).isEqualTo(UPDATED_TEXT);
+ assertThat(testComment.getCreationDate()).isEqualTo(UPDATED_CREATION_DATE);
+ }
+
+ @Test
+ @Transactional
+ public void updateNonExistingComment() throws Exception {
+ int databaseSizeBeforeUpdate = commentRepository.findAll().size();
+
+ // Create the Comment
+
+ // If the entity doesn't have an ID, it will be created instead of just being updated
+ restCommentMockMvc.perform(put("/api/comments")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(comment)))
+ .andExpect(status().isCreated());
+
+ // Validate the Comment in the database
+ List commentList = commentRepository.findAll();
+ assertThat(commentList).hasSize(databaseSizeBeforeUpdate + 1);
+ }
+
+ @Test
+ @Transactional
+ public void deleteComment() throws Exception {
+ // Initialize the database
+ commentRepository.saveAndFlush(comment);
+ int databaseSizeBeforeDelete = commentRepository.findAll().size();
+
+ // Get the comment
+ restCommentMockMvc.perform(delete("/api/comments/{id}", comment.getId())
+ .accept(TestUtil.APPLICATION_JSON_UTF8))
+ .andExpect(status().isOk());
+
+ // Validate the database is empty
+ List commentList = commentRepository.findAll();
+ assertThat(commentList).hasSize(databaseSizeBeforeDelete - 1);
+ }
+
+ @Test
+ @Transactional
+ public void equalsVerifier() throws Exception {
+ TestUtil.equalsVerifier(Comment.class);
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/web/rest/LogsResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/LogsResourceIntTest.java
new file mode 100644
index 000000000000..92bb976205cb
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/web/rest/LogsResourceIntTest.java
@@ -0,0 +1,59 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.BaeldungApp;
+import com.baeldung.web.rest.vm.LoggerVM;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Test class for the LogsResource REST controller.
+ *
+ * @see LogsResource
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = BaeldungApp.class)
+public class LogsResourceIntTest {
+
+ private MockMvc restLogsMockMvc;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ LogsResource logsResource = new LogsResource();
+ this.restLogsMockMvc = MockMvcBuilders
+ .standaloneSetup(logsResource)
+ .build();
+ }
+
+ @Test
+ public void getAllLogs()throws Exception {
+ restLogsMockMvc.perform(get("/management/logs"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
+ }
+
+ @Test
+ public void changeLogs()throws Exception {
+ LoggerVM logger = new LoggerVM();
+ logger.setLevel("INFO");
+ logger.setName("ROOT");
+
+ restLogsMockMvc.perform(put("/management/logs")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(logger)))
+ .andExpect(status().isNoContent());
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/web/rest/PostResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/PostResourceIntTest.java
new file mode 100644
index 000000000000..2a23452711c4
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/web/rest/PostResourceIntTest.java
@@ -0,0 +1,306 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.BaeldungApp;
+
+import com.baeldung.domain.Post;
+import com.baeldung.domain.User;
+import com.baeldung.repository.PostRepository;
+import com.baeldung.web.rest.errors.ExceptionTranslator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+/**
+ * Test class for the PostResource REST controller.
+ *
+ * @see PostResource
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = BaeldungApp.class)
+public class PostResourceIntTest {
+
+ private static final String DEFAULT_TITLE = "AAAAAAAAAA";
+ private static final String UPDATED_TITLE = "BBBBBBBBBB";
+
+ private static final String DEFAULT_CONTENT = "AAAAAAAAAA";
+ private static final String UPDATED_CONTENT = "BBBBBBBBBB";
+
+ private static final LocalDate DEFAULT_CREATION_DATE = LocalDate.ofEpochDay(0L);
+ private static final LocalDate UPDATED_CREATION_DATE = LocalDate.now(ZoneId.systemDefault());
+
+ @Autowired
+ private PostRepository postRepository;
+
+ @Autowired
+ private MappingJackson2HttpMessageConverter jacksonMessageConverter;
+
+ @Autowired
+ private PageableHandlerMethodArgumentResolver pageableArgumentResolver;
+
+ @Autowired
+ private ExceptionTranslator exceptionTranslator;
+
+ @Autowired
+ private EntityManager em;
+
+ private MockMvc restPostMockMvc;
+
+ private Post post;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ PostResource postResource = new PostResource(postRepository);
+ this.restPostMockMvc = MockMvcBuilders.standaloneSetup(postResource)
+ .setCustomArgumentResolvers(pageableArgumentResolver)
+ .setControllerAdvice(exceptionTranslator)
+ .setMessageConverters(jacksonMessageConverter).build();
+ }
+
+ /**
+ * Create an entity for this test.
+ *
+ * This is a static method, as tests for other entities might also need it,
+ * if they test an entity which requires the current entity.
+ */
+ public static Post createEntity(EntityManager em) {
+ Post post = new Post()
+ .title(DEFAULT_TITLE)
+ .content(DEFAULT_CONTENT)
+ .creationDate(DEFAULT_CREATION_DATE);
+ // Add required entity
+ User creator = UserResourceIntTest.createEntity(em);
+ em.persist(creator);
+ em.flush();
+ post.setCreator(creator);
+ return post;
+ }
+
+ @Before
+ public void initTest() {
+ post = createEntity(em);
+ }
+
+ @Test
+ @Transactional
+ public void createPost() throws Exception {
+ int databaseSizeBeforeCreate = postRepository.findAll().size();
+
+ // Create the Post
+ restPostMockMvc.perform(post("/api/posts")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(post)))
+ .andExpect(status().isCreated());
+
+ // Validate the Post in the database
+ List postList = postRepository.findAll();
+ assertThat(postList).hasSize(databaseSizeBeforeCreate + 1);
+ Post testPost = postList.get(postList.size() - 1);
+ assertThat(testPost.getTitle()).isEqualTo(DEFAULT_TITLE);
+ assertThat(testPost.getContent()).isEqualTo(DEFAULT_CONTENT);
+ assertThat(testPost.getCreationDate()).isEqualTo(DEFAULT_CREATION_DATE);
+ }
+
+ @Test
+ @Transactional
+ public void createPostWithExistingId() throws Exception {
+ int databaseSizeBeforeCreate = postRepository.findAll().size();
+
+ // Create the Post with an existing ID
+ post.setId(1L);
+
+ // An entity with an existing ID cannot be created, so this API call must fail
+ restPostMockMvc.perform(post("/api/posts")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(post)))
+ .andExpect(status().isBadRequest());
+
+ // Validate the Alice in the database
+ List postList = postRepository.findAll();
+ assertThat(postList).hasSize(databaseSizeBeforeCreate);
+ }
+
+ @Test
+ @Transactional
+ public void checkTitleIsRequired() throws Exception {
+ int databaseSizeBeforeTest = postRepository.findAll().size();
+ // set the field null
+ post.setTitle(null);
+
+ // Create the Post, which fails.
+
+ restPostMockMvc.perform(post("/api/posts")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(post)))
+ .andExpect(status().isBadRequest());
+
+ List postList = postRepository.findAll();
+ assertThat(postList).hasSize(databaseSizeBeforeTest);
+ }
+
+ @Test
+ @Transactional
+ public void checkContentIsRequired() throws Exception {
+ int databaseSizeBeforeTest = postRepository.findAll().size();
+ // set the field null
+ post.setContent(null);
+
+ // Create the Post, which fails.
+
+ restPostMockMvc.perform(post("/api/posts")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(post)))
+ .andExpect(status().isBadRequest());
+
+ List postList = postRepository.findAll();
+ assertThat(postList).hasSize(databaseSizeBeforeTest);
+ }
+
+ @Test
+ @Transactional
+ public void checkCreationDateIsRequired() throws Exception {
+ int databaseSizeBeforeTest = postRepository.findAll().size();
+ // set the field null
+ post.setCreationDate(null);
+
+ // Create the Post, which fails.
+
+ restPostMockMvc.perform(post("/api/posts")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(post)))
+ .andExpect(status().isBadRequest());
+
+ List postList = postRepository.findAll();
+ assertThat(postList).hasSize(databaseSizeBeforeTest);
+ }
+
+ @Test
+ @Transactional
+ public void getAllPosts() throws Exception {
+ // Initialize the database
+ postRepository.saveAndFlush(post);
+
+ // Get all the postList
+ restPostMockMvc.perform(get("/api/posts?sort=id,desc"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.[*].id").value(hasItem(post.getId().intValue())))
+ .andExpect(jsonPath("$.[*].title").value(hasItem(DEFAULT_TITLE.toString())))
+ .andExpect(jsonPath("$.[*].content").value(hasItem(DEFAULT_CONTENT.toString())))
+ .andExpect(jsonPath("$.[*].creationDate").value(hasItem(DEFAULT_CREATION_DATE.toString())));
+ }
+
+ @Test
+ @Transactional
+ public void getPost() throws Exception {
+ // Initialize the database
+ postRepository.saveAndFlush(post);
+
+ // Get the post
+ restPostMockMvc.perform(get("/api/posts/{id}", post.getId()))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.id").value(post.getId().intValue()))
+ .andExpect(jsonPath("$.title").value(DEFAULT_TITLE.toString()))
+ .andExpect(jsonPath("$.content").value(DEFAULT_CONTENT.toString()))
+ .andExpect(jsonPath("$.creationDate").value(DEFAULT_CREATION_DATE.toString()));
+ }
+
+ @Test
+ @Transactional
+ public void getNonExistingPost() throws Exception {
+ // Get the post
+ restPostMockMvc.perform(get("/api/posts/{id}", Long.MAX_VALUE))
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ @Transactional
+ public void updatePost() throws Exception {
+ // Initialize the database
+ postRepository.saveAndFlush(post);
+ int databaseSizeBeforeUpdate = postRepository.findAll().size();
+
+ // Update the post
+ Post updatedPost = postRepository.findOne(post.getId());
+ updatedPost
+ .title(UPDATED_TITLE)
+ .content(UPDATED_CONTENT)
+ .creationDate(UPDATED_CREATION_DATE);
+
+ restPostMockMvc.perform(put("/api/posts")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(updatedPost)))
+ .andExpect(status().isOk());
+
+ // Validate the Post in the database
+ List postList = postRepository.findAll();
+ assertThat(postList).hasSize(databaseSizeBeforeUpdate);
+ Post testPost = postList.get(postList.size() - 1);
+ assertThat(testPost.getTitle()).isEqualTo(UPDATED_TITLE);
+ assertThat(testPost.getContent()).isEqualTo(UPDATED_CONTENT);
+ assertThat(testPost.getCreationDate()).isEqualTo(UPDATED_CREATION_DATE);
+ }
+
+ @Test
+ @Transactional
+ public void updateNonExistingPost() throws Exception {
+ int databaseSizeBeforeUpdate = postRepository.findAll().size();
+
+ // Create the Post
+
+ // If the entity doesn't have an ID, it will be created instead of just being updated
+ restPostMockMvc.perform(put("/api/posts")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(post)))
+ .andExpect(status().isCreated());
+
+ // Validate the Post in the database
+ List postList = postRepository.findAll();
+ assertThat(postList).hasSize(databaseSizeBeforeUpdate + 1);
+ }
+
+ @Test
+ @Transactional
+ public void deletePost() throws Exception {
+ // Initialize the database
+ postRepository.saveAndFlush(post);
+ int databaseSizeBeforeDelete = postRepository.findAll().size();
+
+ // Get the post
+ restPostMockMvc.perform(delete("/api/posts/{id}", post.getId())
+ .accept(TestUtil.APPLICATION_JSON_UTF8))
+ .andExpect(status().isOk());
+
+ // Validate the database is empty
+ List postList = postRepository.findAll();
+ assertThat(postList).hasSize(databaseSizeBeforeDelete - 1);
+ }
+
+ @Test
+ @Transactional
+ public void equalsVerifier() throws Exception {
+ TestUtil.equalsVerifier(Post.class);
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/web/rest/ProfileInfoResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/ProfileInfoResourceIntTest.java
new file mode 100644
index 000000000000..df3544f34421
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/web/rest/ProfileInfoResourceIntTest.java
@@ -0,0 +1,86 @@
+package com.baeldung.web.rest;
+
+import io.github.jhipster.config.JHipsterProperties;
+import com.baeldung.BaeldungApp;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.env.Environment;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Test class for the ProfileInfoResource REST controller.
+ *
+ * @see ProfileInfoResource
+ **/
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = BaeldungApp.class)
+public class ProfileInfoResourceIntTest {
+
+ @Mock
+ private Environment environment;
+
+ @Mock
+ private JHipsterProperties jHipsterProperties;
+
+ private MockMvc restProfileMockMvc;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ String mockProfile[] = {"test"};
+ JHipsterProperties.Ribbon ribbon = new JHipsterProperties.Ribbon();
+ ribbon.setDisplayOnActiveProfiles(mockProfile);
+ when(jHipsterProperties.getRibbon()).thenReturn(ribbon);
+
+ String activeProfiles[] = {"test"};
+ when(environment.getDefaultProfiles()).thenReturn(activeProfiles);
+ when(environment.getActiveProfiles()).thenReturn(activeProfiles);
+
+ ProfileInfoResource profileInfoResource = new ProfileInfoResource(environment, jHipsterProperties);
+ this.restProfileMockMvc = MockMvcBuilders
+ .standaloneSetup(profileInfoResource)
+ .build();
+ }
+
+ @Test
+ public void getProfileInfoWithRibbon() throws Exception {
+ restProfileMockMvc.perform(get("/api/profile-info"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
+ }
+
+ @Test
+ public void getProfileInfoWithoutRibbon() throws Exception {
+ JHipsterProperties.Ribbon ribbon = new JHipsterProperties.Ribbon();
+ ribbon.setDisplayOnActiveProfiles(null);
+ when(jHipsterProperties.getRibbon()).thenReturn(ribbon);
+
+ restProfileMockMvc.perform(get("/api/profile-info"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
+ }
+
+ @Test
+ public void getProfileInfoWithoutActiveProfiles() throws Exception {
+ String emptyProfile[] = {};
+ when(environment.getDefaultProfiles()).thenReturn(emptyProfile);
+ when(environment.getActiveProfiles()).thenReturn(emptyProfile);
+
+ restProfileMockMvc.perform(get("/api/profile-info"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/web/rest/TestUtil.java b/jhipster/src/test/java/com/baeldung/web/rest/TestUtil.java
new file mode 100644
index 000000000000..64d092fdf175
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/web/rest/TestUtil.java
@@ -0,0 +1,120 @@
+package com.baeldung.web.rest;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.http.MediaType;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeParseException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Utility class for testing REST controllers.
+ */
+public class TestUtil {
+
+ /** MediaType for JSON UTF8 */
+ public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(
+ MediaType.APPLICATION_JSON.getType(),
+ MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
+
+ /**
+ * Convert an object to JSON byte array.
+ *
+ * @param object
+ * the object to convert
+ * @return the JSON byte array
+ * @throws IOException
+ */
+ public static byte[] convertObjectToJsonBytes(Object object)
+ throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+ JavaTimeModule module = new JavaTimeModule();
+ mapper.registerModule(module);
+
+ return mapper.writeValueAsBytes(object);
+ }
+
+ /**
+ * Create a byte array with a specific size filled with specified data.
+ *
+ * @param size the size of the byte array
+ * @param data the data to put in the byte array
+ * @return the JSON byte array
+ */
+ public static byte[] createByteArray(int size, String data) {
+ byte[] byteArray = new byte[size];
+ for (int i = 0; i < size; i++) {
+ byteArray[i] = Byte.parseByte(data, 2);
+ }
+ return byteArray;
+ }
+
+ /**
+ * A matcher that tests that the examined string represents the same instant as the reference datetime.
+ */
+ public static class ZonedDateTimeMatcher extends TypeSafeDiagnosingMatcher {
+
+ private final ZonedDateTime date;
+
+ public ZonedDateTimeMatcher(ZonedDateTime date) {
+ this.date = date;
+ }
+
+ @Override
+ protected boolean matchesSafely(String item, Description mismatchDescription) {
+ try {
+ if (!date.isEqual(ZonedDateTime.parse(item))) {
+ mismatchDescription.appendText("was ").appendValue(item);
+ return false;
+ }
+ return true;
+ } catch (DateTimeParseException e) {
+ mismatchDescription.appendText("was ").appendValue(item)
+ .appendText(", which could not be parsed as a ZonedDateTime");
+ return false;
+ }
+
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("a String representing the same Instant as ").appendValue(date);
+ }
+ }
+
+ /**
+ * Creates a matcher that matches when the examined string reprensents the same instant as the reference datetime
+ * @param date the reference datetime against which the examined string is checked
+ */
+ public static ZonedDateTimeMatcher sameInstant(ZonedDateTime date) {
+ return new ZonedDateTimeMatcher(date);
+ }
+
+ /**
+ * Verifies the equals/hashcode contract on the domain object.
+ */
+ public static void equalsVerifier(Class clazz) throws Exception {
+ Object domainObject1 = clazz.getConstructor().newInstance();
+ assertThat(domainObject1.toString()).isNotNull();
+ assertThat(domainObject1).isEqualTo(domainObject1);
+ assertThat(domainObject1.hashCode()).isEqualTo(domainObject1.hashCode());
+ // Test with an instance of another class
+ Object testOtherObject = new Object();
+ assertThat(domainObject1).isNotEqualTo(testOtherObject);
+ // Test with an instance of the same class
+ Object domainObject2 = clazz.getConstructor().newInstance();
+ assertThat(domainObject1).isNotEqualTo(domainObject2);
+ // HashCodes are equals because the objects are not persisted yet
+ assertThat(domainObject1.hashCode()).isEqualTo(domainObject2.hashCode());
+ }
+}
diff --git a/jhipster/src/test/java/com/baeldung/web/rest/UserResourceIntTest.java b/jhipster/src/test/java/com/baeldung/web/rest/UserResourceIntTest.java
new file mode 100644
index 000000000000..74df23283aab
--- /dev/null
+++ b/jhipster/src/test/java/com/baeldung/web/rest/UserResourceIntTest.java
@@ -0,0 +1,522 @@
+package com.baeldung.web.rest;
+
+import com.baeldung.BaeldungApp;
+import com.baeldung.domain.User;
+import com.baeldung.repository.UserRepository;
+import com.baeldung.service.MailService;
+import com.baeldung.service.UserService;
+import com.baeldung.web.rest.errors.ExceptionTranslator;
+import com.baeldung.web.rest.vm.ManagedUserVM;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+/**
+ * Test class for the UserResource REST controller.
+ *
+ * @see UserResource
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = BaeldungApp.class)
+public class UserResourceIntTest {
+
+ private static final String DEFAULT_LOGIN = "johndoe";
+ private static final String UPDATED_LOGIN = "jhipster";
+
+ private static final String DEFAULT_PASSWORD = "passjohndoe";
+ private static final String UPDATED_PASSWORD = "passjhipster";
+
+ private static final String DEFAULT_EMAIL = "johndoe@localhost";
+ private static final String UPDATED_EMAIL = "jhipster@localhost";
+
+ private static final String DEFAULT_FIRSTNAME = "john";
+ private static final String UPDATED_FIRSTNAME = "jhipsterFirstName";
+
+ private static final String DEFAULT_LASTNAME = "doe";
+ private static final String UPDATED_LASTNAME = "jhipsterLastName";
+
+ private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50";
+ private static final String UPDATED_IMAGEURL = "http://placehold.it/40x40";
+
+ private static final String DEFAULT_LANGKEY = "en";
+ private static final String UPDATED_LANGKEY = "fr";
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private MailService mailService;
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private MappingJackson2HttpMessageConverter jacksonMessageConverter;
+
+ @Autowired
+ private PageableHandlerMethodArgumentResolver pageableArgumentResolver;
+
+ @Autowired
+ private ExceptionTranslator exceptionTranslator;
+
+ @Autowired
+ private EntityManager em;
+
+ private MockMvc restUserMockMvc;
+
+ private User user;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ UserResource userResource = new UserResource(userRepository, mailService, userService);
+ this.restUserMockMvc = MockMvcBuilders.standaloneSetup(userResource)
+ .setCustomArgumentResolvers(pageableArgumentResolver)
+ .setControllerAdvice(exceptionTranslator)
+ .setMessageConverters(jacksonMessageConverter)
+ .build();
+ }
+
+ /**
+ * Create a User.
+ *
+ * This is a static method, as tests for other entities might also need it,
+ * if they test an entity which has a required relationship to the User entity.
+ */
+ public static User createEntity(EntityManager em) {
+ User user = new User();
+ user.setLogin(DEFAULT_LOGIN);
+ user.setPassword(RandomStringUtils.random(60));
+ user.setActivated(true);
+ user.setEmail(DEFAULT_EMAIL);
+ user.setFirstName(DEFAULT_FIRSTNAME);
+ user.setLastName(DEFAULT_LASTNAME);
+ user.setImageUrl(DEFAULT_IMAGEURL);
+ user.setLangKey(DEFAULT_LANGKEY);
+ return user;
+ }
+
+ @Before
+ public void initTest() {
+ user = createEntity(em);
+ }
+
+ @Test
+ @Transactional
+ public void createUser() throws Exception {
+ int databaseSizeBeforeCreate = userRepository.findAll().size();
+
+ // Create the User
+ Set autorities = new HashSet<>();
+ autorities.add("ROLE_USER");
+ ManagedUserVM managedUserVM = new ManagedUserVM(
+ null,
+ DEFAULT_LOGIN,
+ DEFAULT_PASSWORD,
+ DEFAULT_FIRSTNAME,
+ DEFAULT_LASTNAME,
+ DEFAULT_EMAIL,
+ true,
+ DEFAULT_IMAGEURL,
+ DEFAULT_LANGKEY,
+ null,
+ null,
+ null,
+ null,
+ autorities);
+
+ restUserMockMvc.perform(post("/api/users")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
+ .andExpect(status().isCreated());
+
+ // Validate the User in the database
+ List userList = userRepository.findAll();
+ assertThat(userList).hasSize(databaseSizeBeforeCreate + 1);
+ User testUser = userList.get(userList.size() - 1);
+ assertThat(testUser.getLogin()).isEqualTo(DEFAULT_LOGIN);
+ assertThat(testUser.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME);
+ assertThat(testUser.getLastName()).isEqualTo(DEFAULT_LASTNAME);
+ assertThat(testUser.getEmail()).isEqualTo(DEFAULT_EMAIL);
+ assertThat(testUser.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL);
+ assertThat(testUser.getLangKey()).isEqualTo(DEFAULT_LANGKEY);
+ }
+
+ @Test
+ @Transactional
+ public void createUserWithExistingId() throws Exception {
+ int databaseSizeBeforeCreate = userRepository.findAll().size();
+
+ Set autorities = new HashSet<>();
+ autorities.add("ROLE_USER");
+ ManagedUserVM managedUserVM = new ManagedUserVM(
+ 1L,
+ DEFAULT_LOGIN,
+ DEFAULT_PASSWORD,
+ DEFAULT_FIRSTNAME,
+ DEFAULT_LASTNAME,
+ DEFAULT_EMAIL,
+ true,
+ DEFAULT_IMAGEURL,
+ DEFAULT_LANGKEY,
+ null,
+ null,
+ null,
+ null,
+ autorities);
+
+ // An entity with an existing ID cannot be created, so this API call must fail
+ restUserMockMvc.perform(post("/api/users")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
+ .andExpect(status().isBadRequest());
+
+ // Validate the User in the database
+ List userList = userRepository.findAll();
+ assertThat(userList).hasSize(databaseSizeBeforeCreate);
+ }
+
+ @Test
+ @Transactional
+ public void createUserWithExistingLogin() throws Exception {
+ // Initialize the database
+ userRepository.saveAndFlush(user);
+ int databaseSizeBeforeCreate = userRepository.findAll().size();
+
+ Set autorities = new HashSet<>();
+ autorities.add("ROLE_USER");
+ ManagedUserVM managedUserVM = new ManagedUserVM(
+ null,
+ DEFAULT_LOGIN, // this login should already be used
+ DEFAULT_PASSWORD,
+ DEFAULT_FIRSTNAME,
+ DEFAULT_LASTNAME,
+ "anothermail@localhost",
+ true,
+ DEFAULT_IMAGEURL,
+ DEFAULT_LANGKEY,
+ null,
+ null,
+ null,
+ null,
+ autorities);
+
+ // Create the User
+ restUserMockMvc.perform(post("/api/users")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
+ .andExpect(status().isBadRequest());
+
+ // Validate the User in the database
+ List userList = userRepository.findAll();
+ assertThat(userList).hasSize(databaseSizeBeforeCreate);
+ }
+
+ @Test
+ @Transactional
+ public void createUserWithExistingEmail() throws Exception {
+ // Initialize the database
+ userRepository.saveAndFlush(user);
+ int databaseSizeBeforeCreate = userRepository.findAll().size();
+
+ Set autorities = new HashSet<>();
+ autorities.add("ROLE_USER");
+ ManagedUserVM managedUserVM = new ManagedUserVM(
+ null,
+ "anotherlogin",
+ DEFAULT_PASSWORD,
+ DEFAULT_FIRSTNAME,
+ DEFAULT_LASTNAME,
+ DEFAULT_EMAIL, // this email should already be used
+ true,
+ DEFAULT_IMAGEURL,
+ DEFAULT_LANGKEY,
+ null,
+ null,
+ null,
+ null,
+ autorities);
+
+ // Create the User
+ restUserMockMvc.perform(post("/api/users")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
+ .andExpect(status().isBadRequest());
+
+ // Validate the User in the database
+ List userList = userRepository.findAll();
+ assertThat(userList).hasSize(databaseSizeBeforeCreate);
+ }
+
+ @Test
+ @Transactional
+ public void getAllUsers() throws Exception {
+ // Initialize the database
+ userRepository.saveAndFlush(user);
+
+ // Get all the users
+ restUserMockMvc.perform(get("/api/users?sort=id,desc")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN)))
+ .andExpect(jsonPath("$.[*].firstName").value(hasItem(DEFAULT_FIRSTNAME)))
+ .andExpect(jsonPath("$.[*].lastName").value(hasItem(DEFAULT_LASTNAME)))
+ .andExpect(jsonPath("$.[*].email").value(hasItem(DEFAULT_EMAIL)))
+ .andExpect(jsonPath("$.[*].imageUrl").value(hasItem(DEFAULT_IMAGEURL)))
+ .andExpect(jsonPath("$.[*].langKey").value(hasItem(DEFAULT_LANGKEY)));
+ }
+
+ @Test
+ @Transactional
+ public void getUser() throws Exception {
+ // Initialize the database
+ userRepository.saveAndFlush(user);
+
+ // Get the user
+ restUserMockMvc.perform(get("/api/users/{login}", user.getLogin()))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$.login").value(user.getLogin()))
+ .andExpect(jsonPath("$.firstName").value(DEFAULT_FIRSTNAME))
+ .andExpect(jsonPath("$.lastName").value(DEFAULT_LASTNAME))
+ .andExpect(jsonPath("$.email").value(DEFAULT_EMAIL))
+ .andExpect(jsonPath("$.imageUrl").value(DEFAULT_IMAGEURL))
+ .andExpect(jsonPath("$.langKey").value(DEFAULT_LANGKEY));
+ }
+
+ @Test
+ @Transactional
+ public void getNonExistingUser() throws Exception {
+ restUserMockMvc.perform(get("/api/users/unknown"))
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ @Transactional
+ public void updateUser() throws Exception {
+ // Initialize the database
+ userRepository.saveAndFlush(user);
+ int databaseSizeBeforeUpdate = userRepository.findAll().size();
+
+ // Update the user
+ User updatedUser = userRepository.findOne(user.getId());
+
+ Set autorities = new HashSet<>();
+ autorities.add("ROLE_USER");
+ ManagedUserVM managedUserVM = new ManagedUserVM(
+ updatedUser.getId(),
+ updatedUser.getLogin(),
+ UPDATED_PASSWORD,
+ UPDATED_FIRSTNAME,
+ UPDATED_LASTNAME,
+ UPDATED_EMAIL,
+ updatedUser.getActivated(),
+ UPDATED_IMAGEURL,
+ UPDATED_LANGKEY,
+ updatedUser.getCreatedBy(),
+ updatedUser.getCreatedDate(),
+ updatedUser.getLastModifiedBy(),
+ updatedUser.getLastModifiedDate(),
+ autorities);
+
+ restUserMockMvc.perform(put("/api/users")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
+ .andExpect(status().isOk());
+
+ // Validate the User in the database
+ List userList = userRepository.findAll();
+ assertThat(userList).hasSize(databaseSizeBeforeUpdate);
+ User testUser = userList.get(userList.size() - 1);
+ assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME);
+ assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME);
+ assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL);
+ assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL);
+ assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY);
+ }
+
+ @Test
+ @Transactional
+ public void updateUserLogin() throws Exception {
+ // Initialize the database
+ userRepository.saveAndFlush(user);
+ int databaseSizeBeforeUpdate = userRepository.findAll().size();
+
+ // Update the user
+ User updatedUser = userRepository.findOne(user.getId());
+
+ Set autorities = new HashSet<>();
+ autorities.add("ROLE_USER");
+ ManagedUserVM managedUserVM = new ManagedUserVM(
+ updatedUser.getId(),
+ UPDATED_LOGIN,
+ UPDATED_PASSWORD,
+ UPDATED_FIRSTNAME,
+ UPDATED_LASTNAME,
+ UPDATED_EMAIL,
+ updatedUser.getActivated(),
+ UPDATED_IMAGEURL,
+ UPDATED_LANGKEY,
+ updatedUser.getCreatedBy(),
+ updatedUser.getCreatedDate(),
+ updatedUser.getLastModifiedBy(),
+ updatedUser.getLastModifiedDate(),
+ autorities);
+
+ restUserMockMvc.perform(put("/api/users")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
+ .andExpect(status().isOk());
+
+ // Validate the User in the database
+ List userList = userRepository.findAll();
+ assertThat(userList).hasSize(databaseSizeBeforeUpdate);
+ User testUser = userList.get(userList.size() - 1);
+ assertThat(testUser.getLogin()).isEqualTo(UPDATED_LOGIN);
+ assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME);
+ assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME);
+ assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL);
+ assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL);
+ assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY);
+ }
+
+ @Test
+ @Transactional
+ public void updateUserExistingEmail() throws Exception {
+ // Initialize the database with 2 users
+ userRepository.saveAndFlush(user);
+
+ User anotherUser = new User();
+ anotherUser.setLogin("jhipster");
+ anotherUser.setPassword(RandomStringUtils.random(60));
+ anotherUser.setActivated(true);
+ anotherUser.setEmail("jhipster@localhost");
+ anotherUser.setFirstName("java");
+ anotherUser.setLastName("hipster");
+ anotherUser.setImageUrl("");
+ anotherUser.setLangKey("en");
+ userRepository.saveAndFlush(anotherUser);
+
+ int databaseSizeBeforeUpdate = userRepository.findAll().size();
+
+ // Update the user
+ User updatedUser = userRepository.findOne(user.getId());
+
+ Set autorities = new HashSet<>();
+ autorities.add("ROLE_USER");
+ ManagedUserVM managedUserVM = new ManagedUserVM(
+ updatedUser.getId(),
+ updatedUser.getLogin(),
+ updatedUser.getPassword(),
+ updatedUser.getFirstName(),
+ updatedUser.getLastName(),
+ "jhipster@localhost", // this email should already be used by anotherUser
+ updatedUser.getActivated(),
+ updatedUser.getImageUrl(),
+ updatedUser.getLangKey(),
+ updatedUser.getCreatedBy(),
+ updatedUser.getCreatedDate(),
+ updatedUser.getLastModifiedBy(),
+ updatedUser.getLastModifiedDate(),
+ autorities);
+
+ restUserMockMvc.perform(put("/api/users")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
+ .andExpect(status().isBadRequest());
+ }
+
+ @Test
+ @Transactional
+ public void updateUserExistingLogin() throws Exception {
+ // Initialize the database
+ userRepository.saveAndFlush(user);
+
+ User anotherUser = new User();
+ anotherUser.setLogin("jhipster");
+ anotherUser.setPassword(RandomStringUtils.random(60));
+ anotherUser.setActivated(true);
+ anotherUser.setEmail("jhipster@localhost");
+ anotherUser.setFirstName("java");
+ anotherUser.setLastName("hipster");
+ anotherUser.setImageUrl("");
+ anotherUser.setLangKey("en");
+ userRepository.saveAndFlush(anotherUser);
+ int databaseSizeBeforeUpdate = userRepository.findAll().size();
+
+ // Update the user
+ User updatedUser = userRepository.findOne(user.getId());
+
+ Set autorities = new HashSet<>();
+ autorities.add("ROLE_USER");
+ ManagedUserVM managedUserVM = new ManagedUserVM(
+ updatedUser.getId(),
+ "jhipster", // this login should already be used by anotherUser
+ updatedUser.getPassword(),
+ updatedUser.getFirstName(),
+ updatedUser.getLastName(),
+ updatedUser.getEmail(),
+ updatedUser.getActivated(),
+ updatedUser.getImageUrl(),
+ updatedUser.getLangKey(),
+ updatedUser.getCreatedBy(),
+ updatedUser.getCreatedDate(),
+ updatedUser.getLastModifiedBy(),
+ updatedUser.getLastModifiedDate(),
+ autorities);
+
+ restUserMockMvc.perform(put("/api/users")
+ .contentType(TestUtil.APPLICATION_JSON_UTF8)
+ .content(TestUtil.convertObjectToJsonBytes(managedUserVM)))
+ .andExpect(status().isBadRequest());
+ }
+
+ @Test
+ @Transactional
+ public void deleteUser() throws Exception {
+ // Initialize the database
+ userRepository.saveAndFlush(user);
+ int databaseSizeBeforeDelete = userRepository.findAll().size();
+
+ // Delete the user
+ restUserMockMvc.perform(delete("/api/users/{login}", user.getLogin())
+ .accept(TestUtil.APPLICATION_JSON_UTF8))
+ .andExpect(status().isOk());
+
+ // Validate the database is empty
+ List userList = userRepository.findAll();
+ assertThat(userList).hasSize(databaseSizeBeforeDelete - 1);
+ }
+
+ @Test
+ @Transactional
+ public void equalsVerifier() throws Exception {
+ User userA = new User();
+ userA.setLogin("AAA");
+ User userB = new User();
+ userB.setLogin("BBB");
+ assertThat(userA).isNotEqualTo(userB);
+ }
+}
diff --git a/jhipster/src/test/javascript/e2e/account/account.spec.ts b/jhipster/src/test/javascript/e2e/account/account.spec.ts
new file mode 100644
index 000000000000..cfa9fae0786e
--- /dev/null
+++ b/jhipster/src/test/javascript/e2e/account/account.spec.ts
@@ -0,0 +1,108 @@
+import { browser, element, by, $ } from 'protractor';
+
+describe('account', () => {
+
+ const username = element(by.id('username'));
+ const password = element(by.id('password'));
+ const accountMenu = element(by.id('account-menu'));
+ const login = element(by.id('login'));
+ const logout = element(by.id('logout'));
+
+ beforeAll(() => {
+ browser.get('/');
+ });
+
+ it('should fail to login with bad password', () => {
+ const expect1 = /home.title/;
+ element.all(by.css('h1')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ accountMenu.click();
+ login.click();
+
+ username.sendKeys('admin');
+ password.sendKeys('foo');
+ element(by.css('button[type=submit]')).click();
+
+ const expect2 = /login.messages.error.authentication/;
+ element.all(by.css('.alert-danger')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect2);
+ });
+ });
+
+ it('should login successfully with admin account', () => {
+ const expect1 = /login.title/;
+ element.all(by.css('.modal-content h1')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ username.clear();
+ username.sendKeys('admin');
+ password.clear();
+ password.sendKeys('admin');
+ element(by.css('button[type=submit]')).click();
+
+ browser.waitForAngular();
+
+ const expect2 = /home.logged.message/;
+ element.all(by.css('.alert-success span')).getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect2);
+ });
+ });
+
+ it('should be able to update settings', () => {
+ accountMenu.click();
+ element(by.css('[routerLink="settings"]')).click();
+
+ const expect1 = /settings.title/;
+ element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ element(by.css('button[type=submit]')).click();
+
+ const expect2 = /settings.messages.success/;
+ element.all(by.css('.alert-success')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect2);
+ });
+ });
+
+ it('should be able to update password', () => {
+ accountMenu.click();
+ element(by.css('[routerLink="password"]')).click();
+
+ const expect1 = /password.title/;
+ element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ password.sendKeys('newpassword');
+ element(by.id('confirmPassword')).sendKeys('newpassword');
+ element(by.css('button[type=submit]')).click();
+
+ const expect2 = /password.messages.success/;
+ element.all(by.css('.alert-success')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect2);
+ });
+ accountMenu.click();
+ logout.click();
+
+ accountMenu.click();
+ login.click();
+
+ username.sendKeys('admin');
+ password.sendKeys('newpassword');
+ element(by.css('button[type=submit]')).click();
+
+ accountMenu.click();
+ element(by.css('[routerLink="password"]')).click();
+ // change back to default
+ password.clear();
+ password.sendKeys('admin');
+ element(by.id('confirmPassword')).clear();
+ element(by.id('confirmPassword')).sendKeys('admin');
+ element(by.css('button[type=submit]')).click();
+ });
+
+ afterAll(() => {
+ accountMenu.click();
+ logout.click();
+ });
+});
diff --git a/jhipster/src/test/javascript/e2e/admin/administration.spec.ts b/jhipster/src/test/javascript/e2e/admin/administration.spec.ts
new file mode 100644
index 000000000000..0516f7a27d96
--- /dev/null
+++ b/jhipster/src/test/javascript/e2e/admin/administration.spec.ts
@@ -0,0 +1,80 @@
+import { browser, element, by, $ } from 'protractor';
+
+describe('administration', () => {
+
+ const username = element(by.id('username'));
+ const password = element(by.id('password'));
+ const accountMenu = element(by.id('account-menu'));
+ const adminMenu = element(by.id('admin-menu'));
+ const login = element(by.id('login'));
+ const logout = element(by.id('logout'));
+
+ beforeAll(() => {
+ browser.get('/');
+
+ accountMenu.click();
+ login.click();
+
+ username.sendKeys('admin');
+ password.sendKeys('admin');
+ element(by.css('button[type=submit]')).click();
+ browser.waitForAngular();
+ });
+
+ beforeEach(() => {
+ adminMenu.click();
+ });
+
+ it('should load user management', () => {
+ element(by.css('[routerLink="user-management"]')).click();
+ const expect1 = /userManagement.home.title/;
+ element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ });
+
+ it('should load metrics', () => {
+ element(by.css('[routerLink="jhi-metrics"]')).click();
+ const expect1 = /metrics.title/;
+ element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ });
+
+ it('should load health', () => {
+ element(by.css('[routerLink="jhi-health"]')).click();
+ const expect1 = /health.title/;
+ element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ });
+
+ it('should load configuration', () => {
+ element(by.css('[routerLink="jhi-configuration"]')).click();
+ const expect1 = /configuration.title/;
+ element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ });
+
+ it('should load audits', () => {
+ element(by.css('[routerLink="audits"]')).click();
+ const expect1 = /audits.title/;
+ element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ });
+
+ it('should load logs', () => {
+ element(by.css('[routerLink="logs"]')).click();
+ const expect1 = /logs.title/;
+ element.all(by.css('h2')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expect1);
+ });
+ });
+
+ afterAll(() => {
+ accountMenu.click();
+ logout.click();
+ });
+});
diff --git a/jhipster/src/test/javascript/e2e/entities/comment.spec.ts b/jhipster/src/test/javascript/e2e/entities/comment.spec.ts
new file mode 100644
index 000000000000..3032bd136495
--- /dev/null
+++ b/jhipster/src/test/javascript/e2e/entities/comment.spec.ts
@@ -0,0 +1,49 @@
+import { browser, element, by, $ } from 'protractor';
+
+describe('Comment e2e test', () => {
+
+ const username = element(by.id('username'));
+ const password = element(by.id('password'));
+ const entityMenu = element(by.id('entity-menu'));
+ const accountMenu = element(by.id('account-menu'));
+ const login = element(by.id('login'));
+ const logout = element(by.id('logout'));
+
+ beforeAll(() => {
+ browser.get('/');
+
+ accountMenu.click();
+ login.click();
+
+ username.sendKeys('admin');
+ password.sendKeys('admin');
+ element(by.css('button[type=submit]')).click();
+ browser.waitForAngular();
+ });
+
+ it('should load Comments', () => {
+ entityMenu.click();
+ element.all(by.css('[routerLink="comment"]')).first().click().then(() => {
+ const expectVal = /baeldungApp.comment.home.title/;
+ element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expectVal);
+ });
+ });
+ });
+
+ it('should load create Comment dialog', function () {
+ element(by.css('button.create-comment')).click().then(() => {
+ const expectVal = /baeldungApp.comment.home.createOrEditLabel/;
+ element.all(by.css('h4.modal-title')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expectVal);
+ });
+
+ element(by.css('button.close')).click();
+ });
+ });
+
+ afterAll(function () {
+ accountMenu.click();
+ logout.click();
+ });
+});
diff --git a/jhipster/src/test/javascript/e2e/entities/post.spec.ts b/jhipster/src/test/javascript/e2e/entities/post.spec.ts
new file mode 100644
index 000000000000..3c8d04f7316d
--- /dev/null
+++ b/jhipster/src/test/javascript/e2e/entities/post.spec.ts
@@ -0,0 +1,49 @@
+import { browser, element, by, $ } from 'protractor';
+
+describe('Post e2e test', () => {
+
+ const username = element(by.id('username'));
+ const password = element(by.id('password'));
+ const entityMenu = element(by.id('entity-menu'));
+ const accountMenu = element(by.id('account-menu'));
+ const login = element(by.id('login'));
+ const logout = element(by.id('logout'));
+
+ beforeAll(() => {
+ browser.get('/');
+
+ accountMenu.click();
+ login.click();
+
+ username.sendKeys('admin');
+ password.sendKeys('admin');
+ element(by.css('button[type=submit]')).click();
+ browser.waitForAngular();
+ });
+
+ it('should load Posts', () => {
+ entityMenu.click();
+ element.all(by.css('[routerLink="post"]')).first().click().then(() => {
+ const expectVal = /baeldungApp.post.home.title/;
+ element.all(by.css('h2 span')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expectVal);
+ });
+ });
+ });
+
+ it('should load create Post dialog', function () {
+ element(by.css('button.create-post')).click().then(() => {
+ const expectVal = /baeldungApp.post.home.createOrEditLabel/;
+ element.all(by.css('h4.modal-title')).first().getAttribute('jhiTranslate').then((value) => {
+ expect(value).toMatch(expectVal);
+ });
+
+ element(by.css('button.close')).click();
+ });
+ });
+
+ afterAll(function () {
+ accountMenu.click();
+ logout.click();
+ });
+});
diff --git a/jhipster/src/test/javascript/karma.conf.js b/jhipster/src/test/javascript/karma.conf.js
new file mode 100644
index 000000000000..1b1022695503
--- /dev/null
+++ b/jhipster/src/test/javascript/karma.conf.js
@@ -0,0 +1,126 @@
+'use strict';
+
+const path = require('path');
+const webpack = require('webpack');
+const WATCH = process.argv.indexOf('--watch') > -1;
+const LoaderOptionsPlugin = require("webpack/lib/LoaderOptionsPlugin");
+
+module.exports = function (config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: './',
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine', 'intl-shim'],
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'spec/entry.ts'
+ ],
+
+
+ // list of files to exclude
+ exclude: ['e2e/**'],
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ 'spec/entry.ts': ['webpack', 'sourcemap']
+ },
+
+ webpack: {
+ resolve: {
+ extensions: ['.ts', '.js']
+ },
+ module: {
+ rules: [
+ {
+ test: /\.ts$/, enforce: 'pre', loader: 'tslint-loader', exclude: /(test|node_modules)/
+ },
+ {
+ test: /\.ts$/,
+ loaders: ['awesome-typescript-loader', 'angular2-template-loader?keepUrl=true'],
+ exclude: /node_modules/
+ },
+ {
+ test: /\.(html|css)$/,
+ loader: 'raw-loader',
+ exclude: /\.async\.(html|css)$/
+ },
+ {
+ test: /\.async\.(html|css)$/,
+ loaders: ['file?name=[name].[hash].[ext]', 'extract']
+ },
+ {
+ test: /\.scss$/,
+ loaders: ['to-string-loader', 'css-loader', 'sass-loader']
+ },
+ {
+ test: /src\/main\/webapp\/.+\.ts$/,
+ enforce: 'post',
+ exclude: /(test|node_modules)/,
+ loader: 'sourcemap-istanbul-instrumenter-loader?force-sourcemap=true'
+ }]
+ },
+ devtool: 'inline-source-map',
+ plugins: [
+ new webpack.ContextReplacementPlugin(
+ // The (\\|\/) piece accounts for path separators in *nix and Windows
+ /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
+ root('./src') // location of your src
+ ),
+ new LoaderOptionsPlugin({
+ options: {
+ tslint: {
+ emitErrors: !WATCH,
+ failOnHint: false
+ }
+ }
+ })
+ ]
+ },
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['dots', 'junit', 'progress', 'karma-remap-istanbul'],
+
+ junitReporter: {
+ outputFile: '../../../../target/test-results/karma/TESTS-results.xml'
+ },
+
+ remapIstanbulReporter: {
+ reports: { // eslint-disable-line
+ 'html': 'target/test-results/coverage',
+ 'text-summary': null
+ }
+ },
+
+ // web server port
+ port: 9876,
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: WATCH,
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: !WATCH
+ });
+};
+
+function root(__path) {
+ return path.join(__dirname, __path);
+}
diff --git a/jhipster/src/test/javascript/protractor.conf.js b/jhipster/src/test/javascript/protractor.conf.js
new file mode 100644
index 000000000000..e79b09e17bba
--- /dev/null
+++ b/jhipster/src/test/javascript/protractor.conf.js
@@ -0,0 +1,48 @@
+var HtmlScreenshotReporter = require("protractor-jasmine2-screenshot-reporter");
+var JasmineReporters = require('jasmine-reporters');
+
+exports.config = {
+ allScriptsTimeout: 20000,
+
+ specs: [
+ './e2e/account/*.spec.ts',
+ './e2e/admin/*.spec.ts',
+ './e2e/entities/*.spec.ts'
+ ],
+
+ capabilities: {
+ 'browserName': 'chrome',
+ 'phantomjs.binary.path': require('phantomjs-prebuilt').path,
+ 'phantomjs.ghostdriver.cli.args': ['--loglevel=DEBUG']
+ },
+
+ directConnect: true,
+
+ baseUrl: 'http://localhost:8080/',
+
+ framework: 'jasmine2',
+
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000
+ },
+
+ beforeLaunch: function() {
+ require('ts-node').register({
+ project: ''
+ });
+ },
+
+ onPrepare: function() {
+ browser.driver.manage().window().setSize(1280, 1024);
+ jasmine.getEnv().addReporter(new JasmineReporters.JUnitXmlReporter({
+ savePath: 'target/reports/e2e',
+ consolidateAll: false
+ }));
+ jasmine.getEnv().addReporter(new HtmlScreenshotReporter({
+ dest: "target/reports/e2e/screenshots"
+ }));
+ },
+
+ useAllAngular2AppRoots: true
+};
diff --git a/jhipster/src/test/javascript/spec/app/account/activate/activate.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/activate/activate.component.spec.ts
new file mode 100644
index 000000000000..76a6e9f9417f
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/account/activate/activate.component.spec.ts
@@ -0,0 +1,84 @@
+import { TestBed, async, tick, fakeAsync, inject } from '@angular/core/testing';
+import { ActivatedRoute } from '@angular/router';
+import { Observable } from 'rxjs/Rx';
+import { BaeldungTestModule } from '../../../test.module';
+import { MockActivatedRoute } from '../../../helpers/mock-route.service';
+import { LoginModalService } from '../../../../../../main/webapp/app/shared';
+import { Activate } from '../../../../../../main/webapp/app/account/activate/activate.service';
+import { ActivateComponent } from '../../../../../../main/webapp/app/account/activate/activate.component';
+
+describe('Component Tests', () => {
+
+ describe('ActivateComponent', () => {
+
+ let comp: ActivateComponent;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [BaeldungTestModule],
+ declarations: [ActivateComponent],
+ providers: [
+ Activate,
+ {
+ provide: ActivatedRoute,
+ useValue: new MockActivatedRoute({'key': 'ABC123'})
+ },
+ {
+ provide: LoginModalService,
+ useValue: null
+ }
+ ]
+ }).overrideComponent(ActivateComponent, {
+ set: {
+ template: ''
+ }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ let fixture = TestBed.createComponent(ActivateComponent);
+ comp = fixture.componentInstance;
+ });
+
+ it('calls activate.get with the key from params',
+ inject([Activate],
+ fakeAsync((service: Activate) => {
+ spyOn(service, 'get').and.returnValue(Observable.of());
+
+ comp.ngOnInit();
+ tick();
+
+ expect(service.get).toHaveBeenCalledWith('ABC123');
+ })
+ )
+ );
+
+ it('should set set success to OK upon successful activation',
+ inject([Activate],
+ fakeAsync((service: Activate) => {
+ spyOn(service, 'get').and.returnValue(Observable.of({}));
+
+ comp.ngOnInit();
+ tick();
+
+ expect(comp.error).toBe(null);
+ expect(comp.success).toEqual('OK');
+ })
+ )
+ );
+
+ it('should set set error to ERROR upon activation failure',
+ inject([Activate],
+ fakeAsync((service: Activate) => {
+ spyOn(service, 'get').and.returnValue(Observable.throw('ERROR'));
+
+ comp.ngOnInit();
+ tick();
+
+ expect(comp.error).toBe('ERROR');
+ expect(comp.success).toEqual(null);
+ })
+ )
+ );
+ });
+});
diff --git a/jhipster/src/test/javascript/spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts
new file mode 100644
index 000000000000..537c58351ef9
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/account/password-reset/finish/password-reset-finish.component.spec.ts
@@ -0,0 +1,79 @@
+import { ComponentFixture, TestBed, inject } from '@angular/core/testing';
+import { Renderer, ElementRef } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { LoginModalService } from '../../../../../../../main/webapp/app/shared';
+import { Observable } from 'rxjs/Rx';
+import { BaeldungTestModule } from '../../../../test.module';
+import { PasswordResetFinishComponent } from '../../../../../../../main/webapp/app/account/password-reset/finish/password-reset-finish.component';
+import { PasswordResetFinish } from '../../../../../../../main/webapp/app/account/password-reset/finish/password-reset-finish.service';
+import { MockActivatedRoute } from '../../../../helpers/mock-route.service';
+
+
+describe('Component Tests', () => {
+
+ describe('PasswordResetFinishComponent', () => {
+
+ let fixture: ComponentFixture;
+ let comp: PasswordResetFinishComponent;
+
+ beforeEach(() => {
+ fixture = TestBed.configureTestingModule({
+ imports: [BaeldungTestModule],
+ declarations: [PasswordResetFinishComponent],
+ providers: [
+ PasswordResetFinish,
+ {
+ provide: LoginModalService,
+ useValue: null
+ },
+ {
+ provide: ActivatedRoute,
+ useValue: new MockActivatedRoute({'key': 'XYZPDQ'})
+ },
+ {
+ provide: Renderer,
+ useValue: {
+ invokeElementMethod(renderElement: any, methodName: string, args?: any[]) {}
+ }
+ },
+ {
+ provide: ElementRef,
+ useValue: new ElementRef(null)
+ }
+ ]
+ }).overrideComponent(PasswordResetFinishComponent, {
+ set: {
+ template: ''
+ }
+ }).createComponent(PasswordResetFinishComponent);
+ comp = fixture.componentInstance;
+ });
+
+ it('should define its initial state', function () {
+ comp.ngOnInit();
+
+ expect(comp.keyMissing).toBeFalsy();
+ expect(comp.key).toEqual('XYZPDQ');
+ expect(comp.resetAccount).toEqual({});
+ });
+
+ it('sets focus after the view has been initialized',
+ inject([ElementRef], (elementRef: ElementRef) => {
+ let element = fixture.nativeElement;
+ let node = {
+ focus() {}
+ };
+
+ elementRef.nativeElement = element;
+ spyOn(element, 'querySelector').and.returnValue(node);
+ spyOn(node, 'focus');
+
+ comp.ngAfterViewInit();
+
+ expect(element.querySelector).toHaveBeenCalledWith('#password');
+ expect(node.focus).toHaveBeenCalled();
+ })
+ );
+
+ });
+});
diff --git a/jhipster/src/test/javascript/spec/app/account/password-reset/init/password-reset-init.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/password-reset/init/password-reset-init.component.spec.ts
new file mode 100644
index 000000000000..55c0a81922d3
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/account/password-reset/init/password-reset-init.component.spec.ts
@@ -0,0 +1,115 @@
+import { ComponentFixture, TestBed, inject } from '@angular/core/testing';
+import { Renderer, ElementRef } from '@angular/core';
+import { Observable } from 'rxjs/Rx';
+import { BaeldungTestModule } from '../../../../test.module';
+import { PasswordResetInitComponent } from '../../../../../../../main/webapp/app/account/password-reset/init/password-reset-init.component';
+import { PasswordResetInit } from '../../../../../../../main/webapp/app/account/password-reset/init/password-reset-init.service';
+
+
+describe('Component Tests', () => {
+
+ describe('PasswordResetInitComponent', function () {
+ let fixture: ComponentFixture;
+ let comp: PasswordResetInitComponent;
+
+ beforeEach(() => {
+ fixture = TestBed.configureTestingModule({
+ imports: [BaeldungTestModule],
+ declarations: [PasswordResetInitComponent],
+ providers: [
+ PasswordResetInit,
+ {
+ provide: Renderer,
+ useValue: {
+ invokeElementMethod(renderElement: any, methodName: string, args?: any[]) {}
+ }
+ },
+ {
+ provide: ElementRef,
+ useValue: new ElementRef(null)
+ }
+ ]
+ }).overrideComponent(PasswordResetInitComponent, {
+ set: {
+ template: ''
+ }
+ }).createComponent(PasswordResetInitComponent);
+ comp = fixture.componentInstance;
+ comp.ngOnInit();
+ });
+
+ it('should define its initial state', function () {
+ expect(comp.success).toBeUndefined();
+ expect(comp.error).toBeUndefined();
+ expect(comp.errorEmailNotExists).toBeUndefined();
+ expect(comp.resetAccount).toEqual({});
+ });
+
+ it('sets focus after the view has been initialized',
+ inject([ElementRef], (elementRef: ElementRef) => {
+ let element = fixture.nativeElement;
+ let node = {
+ focus() {}
+ };
+
+ elementRef.nativeElement = element;
+ spyOn(element, 'querySelector').and.returnValue(node);
+ spyOn(node, 'focus');
+
+ comp.ngAfterViewInit();
+
+ expect(element.querySelector).toHaveBeenCalledWith('#email');
+ expect(node.focus).toHaveBeenCalled();
+ })
+ );
+
+ it('notifies of success upon successful requestReset',
+ inject([PasswordResetInit], (service: PasswordResetInit) => {
+ spyOn(service, 'save').and.returnValue(Observable.of({}));
+ comp.resetAccount.email = 'user@domain.com';
+
+ comp.requestReset();
+
+ expect(service.save).toHaveBeenCalledWith('user@domain.com');
+ expect(comp.success).toEqual('OK');
+ expect(comp.error).toBeNull();
+ expect(comp.errorEmailNotExists).toBeNull();
+ })
+ );
+
+ it('notifies of unknown email upon e-mail address not registered/400',
+ inject([PasswordResetInit], (service: PasswordResetInit) => {
+ spyOn(service, 'save').and.returnValue(Observable.throw({
+ status: 400,
+ data: 'e-mail address not registered'
+ }));
+ comp.resetAccount.email = 'user@domain.com';
+
+ comp.requestReset();
+
+ expect(service.save).toHaveBeenCalledWith('user@domain.com');
+ expect(comp.success).toBeNull();
+ expect(comp.error).toBeNull();
+ expect(comp.errorEmailNotExists).toEqual('ERROR');
+ })
+ );
+
+ it('notifies of error upon error response',
+ inject([PasswordResetInit], (service: PasswordResetInit) => {
+ spyOn(service, 'save').and.returnValue(Observable.throw({
+ status: 503,
+ data: 'something else'
+ }));
+ comp.resetAccount.email = 'user@domain.com';
+
+ comp.requestReset();
+
+ expect(service.save).toHaveBeenCalledWith('user@domain.com');
+ expect(comp.success).toBeNull();
+ expect(comp.errorEmailNotExists).toBeNull();
+ expect(comp.error).toEqual('ERROR');
+ })
+ );
+
+ });
+});
diff --git a/jhipster/src/test/javascript/spec/app/account/password/password-strength-bar.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/password/password-strength-bar.component.spec.ts
new file mode 100644
index 000000000000..9cdc55529cb3
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/account/password/password-strength-bar.component.spec.ts
@@ -0,0 +1,53 @@
+import { ComponentFixture, TestBed, async } from '@angular/core/testing';
+
+import { PasswordStrengthBarComponent } from '../../../../../../main/webapp/app/account/password/password-strength-bar.component';
+
+describe('Component Tests', () => {
+
+ describe('PasswordStrengthBarComponent', () => {
+
+ let comp: PasswordStrengthBarComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [PasswordStrengthBarComponent]
+ }).overrideComponent(PasswordStrengthBarComponent, {
+ set: {
+ template: ''
+ }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PasswordStrengthBarComponent);
+ comp = fixture.componentInstance;
+ });
+
+ describe('PasswordStrengthBarComponents', () => {
+ it('should initialize with default values', () => {
+ expect(comp.measureStrength('')).toBe(0);
+ expect(comp.colors).toEqual(['#F00', '#F90', '#FF0', '#9F0', '#0F0']);
+ expect(comp.getColor(0).idx).toBe(1);
+ expect(comp.getColor(0).col).toBe(comp.colors[0]);
+ });
+
+ it('should increase strength upon password value change', () => {
+ expect(comp.measureStrength('')).toBe(0);
+ expect(comp.measureStrength('aa')).toBeGreaterThanOrEqual(comp.measureStrength(''));
+ expect(comp.measureStrength('aa^6')).toBeGreaterThanOrEqual(comp.measureStrength('aa'));
+ expect(comp.measureStrength('Aa090(**)')).toBeGreaterThanOrEqual(comp.measureStrength('aa^6'));
+ expect(comp.measureStrength('Aa090(**)+-07365')).toBeGreaterThanOrEqual(comp.measureStrength('Aa090(**)'));
+ });
+
+ it('should change the color based on strength', () => {
+ expect(comp.getColor(0).col).toBe(comp.colors[0]);
+ expect(comp.getColor(11).col).toBe(comp.colors[1]);
+ expect(comp.getColor(22).col).toBe(comp.colors[2]);
+ expect(comp.getColor(33).col).toBe(comp.colors[3]);
+ expect(comp.getColor(44).col).toBe(comp.colors[4]);
+ });
+ });
+ });
+});
+
diff --git a/jhipster/src/test/javascript/spec/app/account/password/password.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/password/password.component.spec.ts
new file mode 100644
index 000000000000..e6f498378549
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/account/password/password.component.spec.ts
@@ -0,0 +1,92 @@
+import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing';
+import { Observable } from 'rxjs/Rx';
+import { BaeldungTestModule } from '../../../test.module';
+import { PasswordComponent } from '../../../../../../main/webapp/app/account/password/password.component';
+import { Password } from '../../../../../../main/webapp/app/account/password/password.service';
+import { Principal } from '../../../../../../main/webapp/app/shared/auth/principal.service';
+import { AccountService } from '../../../../../../main/webapp/app/shared/auth/account.service';
+
+
+describe('Component Tests', () => {
+
+ describe('PasswordComponent', () => {
+
+ let comp: PasswordComponent;
+ let fixture: ComponentFixture;
+ let service: Password;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [BaeldungTestModule],
+ declarations: [PasswordComponent],
+ providers: [
+ Principal,
+ AccountService,
+ Password
+ ]
+ }).overrideComponent(PasswordComponent, {
+ set: {
+ template: ''
+ }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PasswordComponent);
+ comp = fixture.componentInstance;
+ service = fixture.debugElement.injector.get(Password);
+ });
+
+ it('should show error if passwords do not match', () => {
+ // GIVEN
+ comp.password = 'password1';
+ comp.confirmPassword = 'password2';
+ // WHEN
+ comp.changePassword();
+ // THEN
+ expect(comp.doNotMatch).toBe('ERROR');
+ expect(comp.error).toBeNull();
+ expect(comp.success).toBeNull();
+ });
+
+ it('should call Auth.changePassword when passwords match', () => {
+ // GIVEN
+ spyOn(service, 'save').and.returnValue(Observable.of(true));
+ comp.password = comp.confirmPassword = 'myPassword';
+
+ // WHEN
+ comp.changePassword();
+
+ // THEN
+ expect(service.save).toHaveBeenCalledWith('myPassword');
+ });
+
+ it('should set success to OK upon success', function() {
+ // GIVEN
+ spyOn(service, 'save').and.returnValue(Observable.of(true));
+ comp.password = comp.confirmPassword = 'myPassword';
+
+ // WHEN
+ comp.changePassword();
+
+ // THEN
+ expect(comp.doNotMatch).toBeNull();
+ expect(comp.error).toBeNull();
+ expect(comp.success).toBe('OK');
+ });
+
+ it('should notify of error if change password fails', function() {
+ // GIVEN
+ spyOn(service, 'save').and.returnValue(Observable.throw('ERROR'));
+ comp.password = comp.confirmPassword = 'myPassword';
+
+ // WHEN
+ comp.changePassword();
+
+ // THEN
+ expect(comp.doNotMatch).toBeNull();
+ expect(comp.success).toBeNull();
+ expect(comp.error).toBe('ERROR');
+ });
+ });
+});
diff --git a/jhipster/src/test/javascript/spec/app/account/register/register.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/register/register.component.spec.ts
new file mode 100644
index 000000000000..c475c2f3d2c9
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/account/register/register.component.spec.ts
@@ -0,0 +1,138 @@
+import { ComponentFixture, TestBed, async, inject, tick, fakeAsync } from '@angular/core/testing';
+import { Renderer, ElementRef } from '@angular/core';
+import { Observable } from 'rxjs/Rx';
+import { JhiLanguageService } from 'ng-jhipster';
+import { MockLanguageService } from '../../../helpers/mock-language.service';
+import { BaeldungTestModule } from '../../../test.module';
+import { LoginModalService } from '../../../../../../main/webapp/app/shared';
+import { Register } from '../../../../../../main/webapp/app/account/register/register.service';
+import { RegisterComponent } from '../../../../../../main/webapp/app/account/register/register.component';
+
+
+describe('Component Tests', () => {
+
+ describe('RegisterComponent', () => {
+ let fixture: ComponentFixture;
+ let comp: RegisterComponent;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [BaeldungTestModule],
+ declarations: [RegisterComponent],
+ providers: [
+ Register,
+ {
+ provide: LoginModalService,
+ useValue: null
+ },
+ {
+ provide: Renderer,
+ useValue: null
+ },
+ {
+ provide: ElementRef,
+ useValue: null
+ }
+ ]
+ }).overrideComponent(RegisterComponent, {
+ set: {
+ template: ''
+ }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RegisterComponent);
+ comp = fixture.componentInstance;
+ comp.ngOnInit();
+ });
+
+ it('should ensure the two passwords entered match', function () {
+ comp.registerAccount.password = 'password';
+ comp.confirmPassword = 'non-matching';
+
+ comp.register();
+
+ expect(comp.doNotMatch).toEqual('ERROR');
+ });
+
+ it('should update success to OK after creating an account',
+ inject([Register, JhiLanguageService],
+ fakeAsync((service: Register, mockTranslate: MockLanguageService) => {
+ spyOn(service, 'save').and.returnValue(Observable.of({}));
+ comp.registerAccount.password = comp.confirmPassword = 'password';
+
+ comp.register();
+ tick();
+
+ expect(service.save).toHaveBeenCalledWith({
+ password: 'password',
+ langKey: 'en'
+ });
+ expect(comp.success).toEqual(true);
+ expect(comp.registerAccount.langKey).toEqual('en');
+ expect(mockTranslate.getCurrentSpy).toHaveBeenCalled();
+ expect(comp.errorUserExists).toBeNull();
+ expect(comp.errorEmailExists).toBeNull();
+ expect(comp.error).toBeNull();
+ })
+ )
+ );
+
+ it('should notify of user existence upon 400/login already in use',
+ inject([Register],
+ fakeAsync((service: Register) => {
+ spyOn(service, 'save').and.returnValue(Observable.throw({
+ status: 400,
+ _body: 'login already in use'
+ }));
+ comp.registerAccount.password = comp.confirmPassword = 'password';
+
+ comp.register();
+ tick();
+
+ expect(comp.errorUserExists).toEqual('ERROR');
+ expect(comp.errorEmailExists).toBeNull();
+ expect(comp.error).toBeNull();
+ })
+ )
+ );
+
+ it('should notify of email existence upon 400/e-mail address already in use',
+ inject([Register],
+ fakeAsync((service: Register) => {
+ spyOn(service, 'save').and.returnValue(Observable.throw({
+ status: 400,
+ _body: 'e-mail address already in use'
+ }));
+ comp.registerAccount.password = comp.confirmPassword = 'password';
+
+ comp.register();
+ tick();
+
+ expect(comp.errorEmailExists).toEqual('ERROR');
+ expect(comp.errorUserExists).toBeNull();
+ expect(comp.error).toBeNull();
+ })
+ )
+ );
+
+ it('should notify of generic error',
+ inject([Register],
+ fakeAsync((service: Register) => {
+ spyOn(service, 'save').and.returnValue(Observable.throw({
+ status: 503
+ }));
+ comp.registerAccount.password = comp.confirmPassword = 'password';
+
+ comp.register();
+ tick();
+
+ expect(comp.errorUserExists).toBeNull();
+ expect(comp.errorEmailExists).toBeNull();
+ expect(comp.error).toEqual('ERROR');
+ })
+ )
+ );
+ });
+});
diff --git a/jhipster/src/test/javascript/spec/app/account/settings/settings.component.spec.ts b/jhipster/src/test/javascript/spec/app/account/settings/settings.component.spec.ts
new file mode 100644
index 000000000000..266a33be790d
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/account/settings/settings.component.spec.ts
@@ -0,0 +1,103 @@
+import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing';
+import { Observable } from 'rxjs/Rx';
+import { JhiLanguageHelper } from '../../../../../../main/webapp/app/shared';
+import { BaeldungTestModule } from '../../../test.module';
+import { Principal, AccountService } from '../../../../../../main/webapp/app/shared';
+import { SettingsComponent } from '../../../../../../main/webapp/app/account/settings/settings.component';
+import { MockAccountService } from '../../../helpers/mock-account.service';
+import { MockPrincipal } from '../../../helpers/mock-principal.service';
+
+
+describe('Component Tests', () => {
+
+ describe('SettingsComponent', () => {
+
+ let comp: SettingsComponent;
+ let fixture: ComponentFixture;
+ let mockAuth: MockAccountService;
+ let mockPrincipal: MockPrincipal;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [BaeldungTestModule],
+ declarations: [SettingsComponent],
+ providers: [
+ {
+ provide: Principal,
+ useClass: MockPrincipal
+ },
+ {
+ provide: AccountService,
+ useClass: MockAccountService
+ },
+ {
+ provide: JhiLanguageHelper,
+ useValue: null
+ },
+ ]
+ }).overrideComponent(SettingsComponent, {
+ set: {
+ template: ''
+ }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SettingsComponent);
+ comp = fixture.componentInstance;
+ mockAuth = fixture.debugElement.injector.get(AccountService);
+ mockPrincipal = fixture.debugElement.injector.get(Principal);
+ });
+
+ it('should send the current identity upon save', function () {
+ // GIVEN
+ let accountValues = {
+ firstName: 'John',
+ lastName: 'Doe',
+
+ activated: true,
+ email: 'john.doe@mail.com',
+ langKey: 'en',
+ login: 'john'
+ };
+ mockPrincipal.setResponse(accountValues);
+
+ // WHEN
+ comp.settingsAccount = accountValues;
+ comp.save();
+
+ // THEN
+ expect(mockPrincipal.identitySpy).toHaveBeenCalled();
+ expect(mockAuth.saveSpy).toHaveBeenCalledWith(accountValues);
+ expect(comp.settingsAccount).toEqual(accountValues);
+ });
+
+ it('should notify of success upon successful save', function () {
+ // GIVEN
+ let accountValues = {
+ firstName: 'John',
+ lastName: 'Doe'
+ };
+ mockPrincipal.setResponse(accountValues);
+
+ // WHEN
+ comp.save();
+
+ // THEN
+ expect(comp.error).toBeNull();
+ expect(comp.success).toBe('OK');
+ });
+
+ it('should notify of error upon failed save', function () {
+ // GIVEN
+ mockAuth.saveSpy.and.returnValue(Observable.throw('ERROR'));
+
+ // WHEN
+ comp.save();
+
+ // THEN
+ expect(comp.error).toEqual('ERROR');
+ expect(comp.success).toBeNull();
+ });
+ });
+});
diff --git a/jhipster/src/test/javascript/spec/app/admin/audits/audits.component.spec.ts b/jhipster/src/test/javascript/spec/app/admin/audits/audits.component.spec.ts
new file mode 100644
index 000000000000..d16673de03db
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/admin/audits/audits.component.spec.ts
@@ -0,0 +1,82 @@
+import { ComponentFixture, TestBed, async } from '@angular/core/testing';
+import { DatePipe } from '@angular/common';
+import { NgbPaginationConfig} from '@ng-bootstrap/ng-bootstrap';
+import { ParseLinks } from 'ng-jhipster';
+import { BaeldungTestModule } from '../../../test.module';
+import { PaginationConfig } from '../../../../../../main/webapp/app/blocks/config/uib-pagination.config'
+import { AuditsComponent } from '../../../../../../main/webapp/app/admin/audits/audits.component';
+import { AuditsService } from '../../../../../../main/webapp/app/admin/audits/audits.service';
+import { ITEMS_PER_PAGE } from '../../../../../../main/webapp/app/shared';
+
+
+function getDate(isToday= true){
+ let date: Date = new Date();
+ if (isToday) {
+ // Today + 1 day - needed if the current day must be included
+ date.setDate(date.getDate() + 1);
+ return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
+ }
+ return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
+}
+
+describe('Component Tests', () => {
+
+ describe('AuditsComponent', () => {
+
+ let comp: AuditsComponent;
+ let fixture: ComponentFixture;
+ let service: AuditsService;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [BaeldungTestModule],
+ declarations: [AuditsComponent],
+ providers: [
+ AuditsService,
+ NgbPaginationConfig,
+ ParseLinks,
+ PaginationConfig,
+ DatePipe
+ ]
+ })
+ .overrideComponent(AuditsComponent, {
+ set: {
+ template: ''
+ }
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AuditsComponent);
+ comp = fixture.componentInstance;
+ service = fixture.debugElement.injector.get(AuditsService);
+ });
+
+ describe('today function ', () => {
+ it('should set toDate to current date', () => {
+ comp.today();
+ expect(comp.toDate).toBe(getDate());
+ });
+ });
+
+ describe('previousMonth function ', () => {
+ it('should set toDate to current date', () => {
+ comp.previousMonth();
+ expect(comp.fromDate).toBe(getDate(false));
+ });
+ });
+
+ describe('By default, on init', () => {
+ it('should set all default values correctly', () => {
+ fixture.detectChanges();
+ expect(comp.toDate).toBe(getDate());
+ expect(comp.fromDate).toBe(getDate(false));
+ expect(comp.itemsPerPage).toBe(ITEMS_PER_PAGE);
+ expect(comp.page).toBe(1);
+ expect(comp.reverse).toBeFalsy();
+ expect(comp.orderProp).toBe('timestamp');
+ });
+ });
+ });
+});
diff --git a/jhipster/src/test/javascript/spec/app/admin/health/health.component.spec.ts b/jhipster/src/test/javascript/spec/app/admin/health/health.component.spec.ts
new file mode 100644
index 000000000000..b80c96db662d
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/admin/health/health.component.spec.ts
@@ -0,0 +1,295 @@
+import { ComponentFixture, TestBed, async } from '@angular/core/testing';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { BaeldungTestModule } from '../../../test.module';
+import { JhiHealthCheckComponent } from '../../../../../../main/webapp/app/admin/health/health.component';
+import { JhiHealthService } from '../../../../../../main/webapp/app/admin/health/health.service';
+
+
+describe('Component Tests', () => {
+
+ describe('JhiHealthCheckComponent', () => {
+
+ let comp: JhiHealthCheckComponent;
+ let fixture: ComponentFixture;
+ let service: JhiHealthService;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [BaeldungTestModule],
+ declarations: [JhiHealthCheckComponent],
+ providers: [
+ JhiHealthService,
+ {
+ provide: NgbModal,
+ useValue: null
+ }
+ ]
+ })
+ .overrideComponent(JhiHealthCheckComponent, {
+ set: {
+ template: ''
+ }
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(JhiHealthCheckComponent);
+ comp = fixture.componentInstance;
+ service = fixture.debugElement.injector.get(JhiHealthService);
+ });
+
+ describe('baseName and subSystemName', () => {
+ it('should return the basename when it has no sub system', () => {
+ expect(comp.baseName('base')).toBe('base');
+ });
+
+ it('should return the basename when it has sub systems', () => {
+ expect(comp.baseName('base.subsystem.system')).toBe('base');
+ });
+
+ it('should return the sub system name', () => {
+ expect(comp.subSystemName('subsystem')).toBe('');
+ });
+
+ it('should return the subsystem when it has multiple keys', () => {
+ expect(comp.subSystemName('subsystem.subsystem.system')).toBe(' - subsystem.system');
+ });
+ });
+
+ describe('transformHealthData', () => {
+ it('should flatten empty health data', () => {
+ const data = {};
+ const expected = [];
+ expect(service.transformHealthData(data)).toEqual(expected);
+ });
+ });
+
+ it('should flatten health data with no subsystems', () => {
+ const data = {
+ 'status': 'UP',
+ 'db': {
+ 'status': 'UP',
+ 'database': 'H2',
+ 'hello': '1'
+ },
+ 'mail': {
+ 'status': 'UP',
+ 'error': 'mail.a.b.c'
+ }
+ };
+ const expected = [
+ {
+ 'name': 'db',
+ 'error': undefined,
+ 'status': 'UP',
+ 'details': {
+ 'database': 'H2',
+ 'hello': '1'
+ }
+ },
+ {
+ 'name': 'mail',
+ 'error': 'mail.a.b.c',
+ 'status': 'UP'
+ }
+ ];
+ expect(service.transformHealthData(data)).toEqual(expected);
+ });
+
+ it('should flatten health data with subsystems at level 1, main system has no additional information', () => {
+ const data = {
+ 'status': 'UP',
+ 'db': {
+ 'status': 'UP',
+ 'database': 'H2',
+ 'hello': '1'
+ },
+ 'mail': {
+ 'status': 'UP',
+ 'error': 'mail.a.b.c'
+ },
+ 'system': {
+ 'status': 'DOWN',
+ 'subsystem1': {
+ 'status': 'UP',
+ 'property1': 'system.subsystem1.property1'
+ },
+ 'subsystem2': {
+ 'status': 'DOWN',
+ 'error': 'system.subsystem1.error',
+ 'property2': 'system.subsystem2.property2'
+ }
+ }
+ };
+ const expected = [
+ {
+ 'name': 'db',
+ 'error': undefined,
+ 'status': 'UP',
+ 'details': {
+ 'database': 'H2',
+ 'hello': '1'
+ }
+ },
+ {
+ 'name': 'mail',
+ 'error': 'mail.a.b.c',
+ 'status': 'UP'
+ },
+ {
+ 'name': 'system.subsystem1',
+ 'error': undefined,
+ 'status': 'UP',
+ 'details': {
+ 'property1': 'system.subsystem1.property1'
+ }
+ },
+ {
+ 'name': 'system.subsystem2',
+ 'error': 'system.subsystem1.error',
+ 'status': 'DOWN',
+ 'details': {
+ 'property2': 'system.subsystem2.property2'
+ }
+ }
+ ];
+ expect(service.transformHealthData(data)).toEqual(expected);
+ });
+
+ it('should flatten health data with subsystems at level 1, main system has additional information', () => {
+ const data = {
+ 'status': 'UP',
+ 'db': {
+ 'status': 'UP',
+ 'database': 'H2',
+ 'hello': '1'
+ },
+ 'mail': {
+ 'status': 'UP',
+ 'error': 'mail.a.b.c'
+ },
+ 'system': {
+ 'status': 'DOWN',
+ 'property1': 'system.property1',
+ 'subsystem1': {
+ 'status': 'UP',
+ 'property1': 'system.subsystem1.property1'
+ },
+ 'subsystem2': {
+ 'status': 'DOWN',
+ 'error': 'system.subsystem1.error',
+ 'property2': 'system.subsystem2.property2'
+ }
+ }
+ };
+ const expected = [
+ {
+ 'name': 'db',
+ 'error': undefined,
+ 'status': 'UP',
+ 'details': {
+ 'database': 'H2',
+ 'hello': '1'
+ }
+ },
+ {
+ 'name': 'mail',
+ 'error': 'mail.a.b.c',
+ 'status': 'UP'
+ },
+ {
+ 'name': 'system',
+ 'error': undefined,
+ 'status': 'DOWN',
+ 'details': {
+ 'property1': 'system.property1'
+ }
+ },
+ {
+ 'name': 'system.subsystem1',
+ 'error': undefined,
+ 'status': 'UP',
+ 'details': {
+ 'property1': 'system.subsystem1.property1'
+ }
+ },
+ {
+ 'name': 'system.subsystem2',
+ 'error': 'system.subsystem1.error',
+ 'status': 'DOWN',
+ 'details': {
+ 'property2': 'system.subsystem2.property2'
+ }
+ }
+ ];
+ expect(service.transformHealthData(data)).toEqual(expected);
+ });
+
+ it('should flatten health data with subsystems at level 1, main system has additional error', () => {
+ const data = {
+ 'status': 'UP',
+ 'db': {
+ 'status': 'UP',
+ 'database': 'H2',
+ 'hello': '1'
+ },
+ 'mail': {
+ 'status': 'UP',
+ 'error': 'mail.a.b.c'
+ },
+ 'system': {
+ 'status': 'DOWN',
+ 'error': 'show me',
+ 'subsystem1': {
+ 'status': 'UP',
+ 'property1': 'system.subsystem1.property1'
+ },
+ 'subsystem2': {
+ 'status': 'DOWN',
+ 'error': 'system.subsystem1.error',
+ 'property2': 'system.subsystem2.property2'
+ }
+ }
+ };
+ const expected = [
+ {
+ 'name': 'db',
+ 'error': undefined,
+ 'status': 'UP',
+ 'details': {
+ 'database': 'H2',
+ 'hello': '1'
+ }
+ },
+ {
+ 'name': 'mail',
+ 'error': 'mail.a.b.c',
+ 'status': 'UP'
+ },
+ {
+ 'name': 'system',
+ 'error': 'show me',
+ 'status': 'DOWN'
+ },
+ {
+ 'name': 'system.subsystem1',
+ 'error': undefined,
+ 'status': 'UP',
+ 'details': {
+ 'property1': 'system.subsystem1.property1'
+ }
+ },
+ {
+ 'name': 'system.subsystem2',
+ 'error': 'system.subsystem1.error',
+ 'status': 'DOWN',
+ 'details': {
+ 'property2': 'system.subsystem2.property2'
+ }
+ }
+ ];
+ expect(service.transformHealthData(data)).toEqual(expected);
+ });
+ });
+});
diff --git a/jhipster/src/test/javascript/spec/app/entities/comment/comment-detail.component.spec.ts b/jhipster/src/test/javascript/spec/app/entities/comment/comment-detail.component.spec.ts
new file mode 100644
index 000000000000..b7c6b77b8c02
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/entities/comment/comment-detail.component.spec.ts
@@ -0,0 +1,79 @@
+import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing';
+import { MockBackend } from '@angular/http/testing';
+import { Http, BaseRequestOptions } from '@angular/http';
+import { OnInit } from '@angular/core';
+import { DatePipe } from '@angular/common';
+import { ActivatedRoute } from '@angular/router';
+import { Observable } from 'rxjs/Rx';
+import { DateUtils, DataUtils } from 'ng-jhipster';
+import { JhiLanguageService } from 'ng-jhipster';
+import { MockLanguageService } from '../../../helpers/mock-language.service';
+import { MockActivatedRoute } from '../../../helpers/mock-route.service';
+import { CommentDetailComponent } from '../../../../../../main/webapp/app/entities/comment/comment-detail.component';
+import { CommentService } from '../../../../../../main/webapp/app/entities/comment/comment.service';
+import { Comment } from '../../../../../../main/webapp/app/entities/comment/comment.model';
+
+describe('Component Tests', () => {
+
+ describe('Comment Management Detail Component', () => {
+ let comp: CommentDetailComponent;
+ let fixture: ComponentFixture;
+ let service: CommentService;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [CommentDetailComponent],
+ providers: [
+ MockBackend,
+ BaseRequestOptions,
+ DateUtils,
+ DataUtils,
+ DatePipe,
+ {
+ provide: ActivatedRoute,
+ useValue: new MockActivatedRoute({id: 123})
+ },
+ {
+ provide: Http,
+ useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => {
+ return new Http(backendInstance, defaultOptions);
+ },
+ deps: [MockBackend, BaseRequestOptions]
+ },
+ {
+ provide: JhiLanguageService,
+ useClass: MockLanguageService
+ },
+ CommentService
+ ]
+ }).overrideComponent(CommentDetailComponent, {
+ set: {
+ template: ''
+ }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CommentDetailComponent);
+ comp = fixture.componentInstance;
+ service = fixture.debugElement.injector.get(CommentService);
+ });
+
+
+ describe('OnInit', () => {
+ it('Should call load all on init', () => {
+ // GIVEN
+
+ spyOn(service, 'find').and.returnValue(Observable.of(new Comment(10)));
+
+ // WHEN
+ comp.ngOnInit();
+
+ // THEN
+ expect(service.find).toHaveBeenCalledWith(123);
+ expect(comp.comment).toEqual(jasmine.objectContaining({id:10}));
+ });
+ });
+ });
+
+});
diff --git a/jhipster/src/test/javascript/spec/app/entities/post/post-detail.component.spec.ts b/jhipster/src/test/javascript/spec/app/entities/post/post-detail.component.spec.ts
new file mode 100644
index 000000000000..3ccb9cf6ad68
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/app/entities/post/post-detail.component.spec.ts
@@ -0,0 +1,79 @@
+import { ComponentFixture, TestBed, async, inject } from '@angular/core/testing';
+import { MockBackend } from '@angular/http/testing';
+import { Http, BaseRequestOptions } from '@angular/http';
+import { OnInit } from '@angular/core';
+import { DatePipe } from '@angular/common';
+import { ActivatedRoute } from '@angular/router';
+import { Observable } from 'rxjs/Rx';
+import { DateUtils, DataUtils } from 'ng-jhipster';
+import { JhiLanguageService } from 'ng-jhipster';
+import { MockLanguageService } from '../../../helpers/mock-language.service';
+import { MockActivatedRoute } from '../../../helpers/mock-route.service';
+import { PostDetailComponent } from '../../../../../../main/webapp/app/entities/post/post-detail.component';
+import { PostService } from '../../../../../../main/webapp/app/entities/post/post.service';
+import { Post } from '../../../../../../main/webapp/app/entities/post/post.model';
+
+describe('Component Tests', () => {
+
+ describe('Post Management Detail Component', () => {
+ let comp: PostDetailComponent;
+ let fixture: ComponentFixture;
+ let service: PostService;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [PostDetailComponent],
+ providers: [
+ MockBackend,
+ BaseRequestOptions,
+ DateUtils,
+ DataUtils,
+ DatePipe,
+ {
+ provide: ActivatedRoute,
+ useValue: new MockActivatedRoute({id: 123})
+ },
+ {
+ provide: Http,
+ useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => {
+ return new Http(backendInstance, defaultOptions);
+ },
+ deps: [MockBackend, BaseRequestOptions]
+ },
+ {
+ provide: JhiLanguageService,
+ useClass: MockLanguageService
+ },
+ PostService
+ ]
+ }).overrideComponent(PostDetailComponent, {
+ set: {
+ template: ''
+ }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PostDetailComponent);
+ comp = fixture.componentInstance;
+ service = fixture.debugElement.injector.get(PostService);
+ });
+
+
+ describe('OnInit', () => {
+ it('Should call load all on init', () => {
+ // GIVEN
+
+ spyOn(service, 'find').and.returnValue(Observable.of(new Post(10)));
+
+ // WHEN
+ comp.ngOnInit();
+
+ // THEN
+ expect(service.find).toHaveBeenCalledWith(123);
+ expect(comp.post).toEqual(jasmine.objectContaining({id:10}));
+ });
+ });
+ });
+
+});
diff --git a/jhipster/src/test/javascript/spec/entry.ts b/jhipster/src/test/javascript/spec/entry.ts
new file mode 100644
index 000000000000..64edbafb93c4
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/entry.ts
@@ -0,0 +1,19 @@
+///
+import 'core-js';
+import 'zone.js/dist/zone';
+import 'zone.js/dist/long-stack-trace-zone';
+import 'zone.js/dist/async-test';
+import 'zone.js/dist/fake-async-test';
+import 'zone.js/dist/sync-test';
+import 'zone.js/dist/proxy';
+import 'zone.js/dist/jasmine-patch';
+import 'rxjs';
+import 'intl/locale-data/jsonp/en-US.js';
+import { TestBed } from '@angular/core/testing';
+import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
+
+TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
+
+declare let require: any;
+const testsContext: any = require.context('./', true, /\.spec/);
+testsContext.keys().forEach(testsContext);
diff --git a/jhipster/src/test/javascript/spec/helpers/mock-account.service.ts b/jhipster/src/test/javascript/spec/helpers/mock-account.service.ts
new file mode 100644
index 000000000000..e21c10a37004
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/helpers/mock-account.service.ts
@@ -0,0 +1,26 @@
+import { SpyObject } from './spyobject';
+import { AccountService } from '../../../../main/webapp/app/shared/auth/account.service';
+import Spy = jasmine.Spy;
+
+export class MockAccountService extends SpyObject {
+
+ getSpy: Spy;
+ saveSpy: Spy;
+ fakeResponse: any;
+
+ constructor() {
+ super(AccountService);
+
+ this.fakeResponse = null;
+ this.getSpy = this.spy('get').andReturn(this);
+ this.saveSpy = this.spy('save').andReturn(this);
+ }
+
+ subscribe(callback: any) {
+ callback(this.fakeResponse);
+ }
+
+ setResponse(json: any): void {
+ this.fakeResponse = json;
+ }
+}
diff --git a/jhipster/src/test/javascript/spec/helpers/mock-language.service.ts b/jhipster/src/test/javascript/spec/helpers/mock-language.service.ts
new file mode 100644
index 000000000000..0c7dec92f18e
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/helpers/mock-language.service.ts
@@ -0,0 +1,26 @@
+import { SpyObject } from './spyobject';
+import { JhiLanguageService } from 'ng-jhipster';
+import Spy = jasmine.Spy;
+
+export class MockLanguageService extends SpyObject {
+
+ getCurrentSpy: Spy;
+ fakeResponse: any;
+
+ constructor() {
+ super(JhiLanguageService);
+
+ this.fakeResponse = 'en';
+ this.getCurrentSpy = this.spy('getCurrent').andReturn(Promise.resolve(this.fakeResponse));
+ }
+
+ init() {}
+
+ changeLanguage(languageKey: string) {}
+
+ setLocations(locations: string[]) {}
+
+ addLocation(location: string) {}
+
+ reload() {}
+}
diff --git a/jhipster/src/test/javascript/spec/helpers/mock-principal.service.ts b/jhipster/src/test/javascript/spec/helpers/mock-principal.service.ts
new file mode 100644
index 000000000000..89b932b83c1b
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/helpers/mock-principal.service.ts
@@ -0,0 +1,20 @@
+import { SpyObject } from './spyobject';
+import { Principal } from '../../../../main/webapp/app/shared/auth/principal.service';
+import Spy = jasmine.Spy;
+
+export class MockPrincipal extends SpyObject {
+
+ identitySpy: Spy;
+ fakeResponse: any;
+
+ constructor() {
+ super(Principal);
+
+ this.fakeResponse = {};
+ this.identitySpy = this.spy('identity').andReturn(Promise.resolve(this.fakeResponse));
+ }
+
+ setResponse(json: any): void {
+ this.fakeResponse = json;
+ }
+}
diff --git a/jhipster/src/test/javascript/spec/helpers/mock-route.service.ts b/jhipster/src/test/javascript/spec/helpers/mock-route.service.ts
new file mode 100644
index 000000000000..3ddb29172108
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/helpers/mock-route.service.ts
@@ -0,0 +1,15 @@
+import { ActivatedRoute, Params } from '@angular/router';
+import { Observable } from 'rxjs';
+
+export class MockActivatedRoute extends ActivatedRoute {
+
+ constructor(parameters?: any) {
+ super();
+ this.queryParams = Observable.of(parameters);
+ this.params = Observable.of(parameters);
+ }
+}
+
+export class MockRouter {
+ navigate = jasmine.createSpy('navigate');
+}
diff --git a/jhipster/src/test/javascript/spec/helpers/spyobject.ts b/jhipster/src/test/javascript/spec/helpers/spyobject.ts
new file mode 100644
index 000000000000..4db41fb8df3e
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/helpers/spyobject.ts
@@ -0,0 +1,69 @@
+export interface GuinessCompatibleSpy extends jasmine.Spy {
+ /** By chaining the spy with and.returnValue, all calls to the function will return a specific
+ * value. */
+ andReturn(val: any): void;
+ /** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
+ * function. */
+ andCallFake(fn: Function): GuinessCompatibleSpy;
+ /** removes all recorded calls */
+ reset();
+}
+
+export class SpyObject {
+ static stub(object = null, config = null, overrides = null) {
+ if (!(object instanceof SpyObject)) {
+ overrides = config;
+ config = object;
+ object = new SpyObject();
+ }
+
+ let m = {};
+ Object.keys(config).forEach((key) => m[key] = config[key]);
+ Object.keys(overrides).forEach((key) => m[key] = overrides[key]);
+ Object.keys(m).forEach((key) => {
+ object.spy(key).andReturn(m[key]);
+ });
+ return object;
+ }
+
+ constructor(type = null) {
+ if (type) {
+ Object.keys(type.prototype).forEach((prop) => {
+ let m = null;
+ try {
+ m = type.prototype[prop];
+ } catch (e) {
+ // As we are creating spys for abstract classes,
+ // these classes might have getters that throw when they are accessed.
+ // As we are only auto creating spys for methods, this
+ // should not matter.
+ }
+ if (typeof m === 'function') {
+ this.spy(prop);
+ }
+ });
+ }
+ }
+
+ spy(name) {
+ if (!this[name]) {
+ this[name] = this._createGuinnessCompatibleSpy(name);
+ }
+ return this[name];
+ }
+
+ prop(name, value) {
+ this[name] = value;
+ }
+
+ /** @internal */
+ _createGuinnessCompatibleSpy(name): GuinessCompatibleSpy {
+ let newSpy: GuinessCompatibleSpy = < any > jasmine.createSpy(name);
+ newSpy.andCallFake = < any > newSpy.and.callFake;
+ newSpy.andReturn = < any > newSpy.and.returnValue;
+ newSpy.reset = < any > newSpy.calls.reset;
+ // revisit return null here (previously needed for rtts_assert).
+ newSpy.and.returnValue(null);
+ return newSpy;
+ }
+}
diff --git a/jhipster/src/test/javascript/spec/test.module.ts b/jhipster/src/test/javascript/spec/test.module.ts
new file mode 100644
index 000000000000..65ce439cb01e
--- /dev/null
+++ b/jhipster/src/test/javascript/spec/test.module.ts
@@ -0,0 +1,24 @@
+import { NgModule } from '@angular/core';
+import { MockBackend } from '@angular/http/testing';
+import { Http, BaseRequestOptions } from '@angular/http';
+import { JhiLanguageService } from 'ng-jhipster';
+import { MockLanguageService } from './helpers/mock-language.service';
+
+@NgModule({
+ providers: [
+ MockBackend,
+ BaseRequestOptions,
+ {
+ provide: JhiLanguageService,
+ useClass: MockLanguageService
+ },
+ {
+ provide: Http,
+ useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => {
+ return new Http(backendInstance, defaultOptions);
+ },
+ deps: [MockBackend, BaseRequestOptions]
+ }
+ ]
+})
+export class BaeldungTestModule {}
diff --git a/jhipster/src/test/resources/config/application.yml b/jhipster/src/test/resources/config/application.yml
new file mode 100644
index 000000000000..a7939c838cf2
--- /dev/null
+++ b/jhipster/src/test/resources/config/application.yml
@@ -0,0 +1,96 @@
+# ===================================================================
+# Spring Boot configuration.
+#
+# This configuration is used for unit/integration tests.
+#
+# More information on profiles: https://jhipster.github.io/profiles/
+# More information on configuration properties: https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+# ===================================================================
+# Standard Spring Boot properties.
+# Full reference is available at:
+# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+# ===================================================================
+
+
+spring:
+ application:
+ name: baeldung
+ jackson:
+ serialization.write_dates_as_timestamps: false
+ cache:
+ type: none
+ datasource:
+ type: com.zaxxer.hikari.HikariDataSource
+ url: jdbc:h2:mem:baeldung;DB_CLOSE_DELAY=-1
+ name:
+ username:
+ password:
+ jpa:
+ database-platform: io.github.jhipster.domain.util.FixedH2Dialect
+ database: H2
+ open-in-view: false
+ show-sql: true
+ hibernate:
+ ddl-auto: none
+ naming:
+ physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
+ implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+ properties:
+ hibernate.id.new_generator_mappings: true
+ hibernate.cache.use_second_level_cache: false
+ hibernate.cache.use_query_cache: false
+ hibernate.generate_statistics: true
+ hibernate.hbm2ddl.auto: validate
+ mail:
+ host: localhost
+ messages:
+ basename: i18n/messages
+ mvc:
+ favicon:
+ enabled: false
+ thymeleaf:
+ mode: XHTML
+
+liquibase:
+ contexts: test
+
+security:
+ basic:
+ enabled: false
+
+server:
+ port: 10344
+ address: localhost
+
+# ===================================================================
+# JHipster specific properties
+#
+# Full reference is available at: https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+jhipster:
+ async:
+ core-pool-size: 2
+ max-pool-size: 50
+ queue-capacity: 10000
+ security:
+ authentication:
+ jwt:
+ secret: e1d4b69d3f953e3fa622121e882e6f459ca20ca4
+ # Token is valid 24 hours
+ token-validity-in-seconds: 86400
+ metrics: # DropWizard Metrics configuration, used by MetricsConfiguration
+ jmx.enabled: true
+
+# ===================================================================
+# Application specific properties
+# Add your own application properties here, see the ApplicationProperties class
+# to have type-safe configuration, like in the JHipsterProperties above
+#
+# More documentation is available at:
+# https://jhipster.github.io/common-application-properties/
+# ===================================================================
+
+application:
diff --git a/jhipster/src/test/resources/logback-test.xml b/jhipster/src/test/resources/logback-test.xml
new file mode 100644
index 000000000000..c0acd0040185
--- /dev/null
+++ b/jhipster/src/test/resources/logback-test.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+