Initial application generated by JHipster-5.8.2
This commit is contained in:
commit
e0b3d2a36d
24
.editorconfig
Normal file
24
.editorconfig
Normal file
@ -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.json]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
148
.gitattributes
vendored
Normal file
148
.gitattributes
vendored
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# This file is inspired by https://github.com/alexkaratarakis/gitattributes
|
||||||
|
#
|
||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# The above will handle all files NOT found below
|
||||||
|
# These files are text and should be normalized (Convert crlf => lf)
|
||||||
|
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.coffee text
|
||||||
|
*.css text
|
||||||
|
*.cql text
|
||||||
|
*.df text
|
||||||
|
*.ejs text
|
||||||
|
*.html text
|
||||||
|
*.java text
|
||||||
|
*.js text
|
||||||
|
*.json text
|
||||||
|
*.less text
|
||||||
|
*.properties text
|
||||||
|
*.sass text
|
||||||
|
*.scss text
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.sql text
|
||||||
|
*.txt text
|
||||||
|
*.ts text
|
||||||
|
*.xml text
|
||||||
|
*.yaml text
|
||||||
|
*.yml text
|
||||||
|
|
||||||
|
# Documents
|
||||||
|
*.doc diff=astextplain
|
||||||
|
*.DOC diff=astextplain
|
||||||
|
*.docx diff=astextplain
|
||||||
|
*.DOCX diff=astextplain
|
||||||
|
*.dot diff=astextplain
|
||||||
|
*.DOT diff=astextplain
|
||||||
|
*.pdf diff=astextplain
|
||||||
|
*.PDF diff=astextplain
|
||||||
|
*.rtf diff=astextplain
|
||||||
|
*.RTF diff=astextplain
|
||||||
|
*.markdown text
|
||||||
|
*.md text
|
||||||
|
*.adoc text
|
||||||
|
*.textile text
|
||||||
|
*.mustache text
|
||||||
|
*.csv text
|
||||||
|
*.tab text
|
||||||
|
*.tsv text
|
||||||
|
*.txt text
|
||||||
|
AUTHORS text
|
||||||
|
CHANGELOG text
|
||||||
|
CHANGES text
|
||||||
|
CONTRIBUTING text
|
||||||
|
COPYING text
|
||||||
|
copyright text
|
||||||
|
*COPYRIGHT* text
|
||||||
|
INSTALL text
|
||||||
|
license text
|
||||||
|
LICENSE text
|
||||||
|
NEWS text
|
||||||
|
readme text
|
||||||
|
*README* text
|
||||||
|
TODO text
|
||||||
|
|
||||||
|
# Graphics
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.tif binary
|
||||||
|
*.tiff binary
|
||||||
|
*.ico binary
|
||||||
|
# SVG treated as an asset (binary) by default. If you want to treat it as text,
|
||||||
|
# comment-out the following line and uncomment the line after.
|
||||||
|
*.svg binary
|
||||||
|
#*.svg text
|
||||||
|
*.eps binary
|
||||||
|
|
||||||
|
# These files are binary and should be left untouched
|
||||||
|
# (binary is a macro for -text -diff)
|
||||||
|
*.class binary
|
||||||
|
*.jar binary
|
||||||
|
*.war binary
|
||||||
|
|
||||||
|
## LINTERS
|
||||||
|
.csslintrc text
|
||||||
|
.eslintrc text
|
||||||
|
.jscsrc text
|
||||||
|
.jshintrc text
|
||||||
|
.jshintignore text
|
||||||
|
.stylelintrc text
|
||||||
|
|
||||||
|
## CONFIGS
|
||||||
|
*.conf text
|
||||||
|
*.config text
|
||||||
|
.editorconfig text
|
||||||
|
.gitattributes text
|
||||||
|
.gitconfig text
|
||||||
|
.gitignore text
|
||||||
|
.htaccess text
|
||||||
|
*.npmignore text
|
||||||
|
|
||||||
|
## HEROKU
|
||||||
|
Procfile text
|
||||||
|
.slugignore text
|
||||||
|
|
||||||
|
## AUDIO
|
||||||
|
*.kar binary
|
||||||
|
*.m4a binary
|
||||||
|
*.mid binary
|
||||||
|
*.midi binary
|
||||||
|
*.mp3 binary
|
||||||
|
*.ogg binary
|
||||||
|
*.ra binary
|
||||||
|
|
||||||
|
## VIDEO
|
||||||
|
*.3gpp binary
|
||||||
|
*.3gp binary
|
||||||
|
*.as binary
|
||||||
|
*.asf binary
|
||||||
|
*.asx binary
|
||||||
|
*.fla binary
|
||||||
|
*.flv binary
|
||||||
|
*.m4v binary
|
||||||
|
*.mng binary
|
||||||
|
*.mov binary
|
||||||
|
*.mp4 binary
|
||||||
|
*.mpeg binary
|
||||||
|
*.mpg binary
|
||||||
|
*.swc binary
|
||||||
|
*.swf binary
|
||||||
|
*.webm binary
|
||||||
|
|
||||||
|
## ARCHIVES
|
||||||
|
*.7z binary
|
||||||
|
*.gz binary
|
||||||
|
*.rar binary
|
||||||
|
*.tar binary
|
||||||
|
*.zip binary
|
||||||
|
|
||||||
|
## FONTS
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.otf binary
|
||||||
|
*.woff binary
|
||||||
|
*.woff2 binary
|
144
.gitignore
vendored
Normal file
144
.gitignore
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
######################
|
||||||
|
# Project Specific
|
||||||
|
######################
|
||||||
|
/build/www/**
|
||||||
|
/src/test/javascript/coverage/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Node
|
||||||
|
######################
|
||||||
|
/node/
|
||||||
|
node_tmp/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log.*
|
||||||
|
/.awcache/*
|
||||||
|
/.cache-loader/*
|
||||||
|
|
||||||
|
######################
|
||||||
|
# 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
|
||||||
|
classes/
|
||||||
|
out/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# 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
|
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
target
|
||||||
|
package-lock.json
|
12
.prettierrc
Normal file
12
.prettierrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Prettier configuration
|
||||||
|
|
||||||
|
printWidth: 140
|
||||||
|
singleQuote: true
|
||||||
|
tabWidth: 4
|
||||||
|
useTabs: false
|
||||||
|
|
||||||
|
# js and ts rules:
|
||||||
|
arrowParens: avoid
|
||||||
|
|
||||||
|
# jsx and tsx rules:
|
||||||
|
jsxBracketSameLine: false
|
38
.yo-rc.json
Normal file
38
.yo-rc.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"generator-jhipster": {
|
||||||
|
"promptValues": {
|
||||||
|
"packageName": "org.hostsharing.hsadminng",
|
||||||
|
"nativeLanguage": "de"
|
||||||
|
},
|
||||||
|
"jhipsterVersion": "5.8.2",
|
||||||
|
"applicationType": "monolith",
|
||||||
|
"baseName": "hsadminNg",
|
||||||
|
"packageName": "org.hostsharing.hsadminng",
|
||||||
|
"packageFolder": "org/hostsharing/hsadminng",
|
||||||
|
"serverPort": "8080",
|
||||||
|
"authenticationType": "jwt",
|
||||||
|
"cacheProvider": "ehcache",
|
||||||
|
"enableHibernateCache": false,
|
||||||
|
"websocket": false,
|
||||||
|
"databaseType": "sql",
|
||||||
|
"devDatabaseType": "h2Memory",
|
||||||
|
"prodDatabaseType": "postgresql",
|
||||||
|
"searchEngine": false,
|
||||||
|
"messageBroker": false,
|
||||||
|
"serviceDiscoveryType": false,
|
||||||
|
"buildTool": "gradle",
|
||||||
|
"enableSwaggerCodegen": true,
|
||||||
|
"jwtSecretKey": "ZDFlMDUzODIzMTUzZDEwZjExN2E5ZjAzY2VhZmYzNDE1YjhlYWUxZGRhMGU3ODZiNjRkNjVlNzEwZjExYWY4YzczM2NlYzI5YWE1OTRkNWM0YThlYjZjZjA5Zjc5YWJkOTgzYjdhZjQxZWQyZGUyYjFlYjI5ZDE3NmE4M2UzYjQ=",
|
||||||
|
"clientFramework": "angularX",
|
||||||
|
"useSass": false,
|
||||||
|
"clientPackageManager": "npm",
|
||||||
|
"testFrameworks": ["cucumber"],
|
||||||
|
"jhiPrefix": "jhi",
|
||||||
|
"entitySuffix": "",
|
||||||
|
"dtoSuffix": "DTO",
|
||||||
|
"otherModules": [],
|
||||||
|
"enableTranslation": true,
|
||||||
|
"nativeLanguage": "de",
|
||||||
|
"languages": ["de", "en"]
|
||||||
|
}
|
||||||
|
}
|
196
README.md
Normal file
196
README.md
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
# hsadminNg
|
||||||
|
|
||||||
|
This application was generated using JHipster 5.8.2, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v5.8.2](https://www.jhipster.tech/documentation-archive/v5.8.2).
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
./gradlew
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Service workers
|
||||||
|
|
||||||
|
Service workers are commented by default, to enable them please uncomment the following code.
|
||||||
|
|
||||||
|
- The service worker registering script in index.html
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register('./service-worker.js').then(function() {
|
||||||
|
console.log('Service Worker Registered');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: workbox creates the respective service worker and dynamically generate the `service-worker.js`
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
### Doing API-First development using openapi-generator
|
||||||
|
|
||||||
|
[OpenAPI-Generator]() is configured for this application. You can generate API code from the `src/main/resources/swagger/api.yml` definition file by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew openApiGenerate
|
||||||
|
```
|
||||||
|
|
||||||
|
Then implements the generated delegate classes with `@Service` classes.
|
||||||
|
|
||||||
|
To edit the `api.yml` definition file, you can use a tool such as [Swagger-Editor](). Start a local instance of the swagger-editor using docker by running: `docker-compose -f src/main/docker/swagger-editor.yml up -d`. The editor will then be reachable at [http://localhost:7742](http://localhost:7742).
|
||||||
|
|
||||||
|
Refer to [Doing API-First development][] for more details.
|
||||||
|
|
||||||
|
## Building for production
|
||||||
|
|
||||||
|
To optimize the hsadminNg application for production, run:
|
||||||
|
|
||||||
|
./gradlew -Pprod clean bootWar
|
||||||
|
|
||||||
|
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 build/libs/*.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:
|
||||||
|
|
||||||
|
./gradlew test
|
||||||
|
|
||||||
|
### Client tests
|
||||||
|
|
||||||
|
Unit tests are run by [Jest][] and written with [Jasmine][]. They're located in [src/test/javascript/](src/test/javascript/) and can be run with:
|
||||||
|
|
||||||
|
npm test
|
||||||
|
|
||||||
|
For more information, refer to the [Running tests page][].
|
||||||
|
|
||||||
|
### Code quality
|
||||||
|
|
||||||
|
Sonar is used to analyse code quality. You can start a local Sonar server (accessible on http://localhost:9001) with:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose -f src/main/docker/sonar.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, run a Sonar analysis:
|
||||||
|
|
||||||
|
```
|
||||||
|
./gradlew -Pprod clean test sonarqube
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, refer to the [Code quality 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 postgresql database in a docker container, run:
|
||||||
|
|
||||||
|
docker-compose -f src/main/docker/postgresql.yml up -d
|
||||||
|
|
||||||
|
To stop it and remove the container, run:
|
||||||
|
|
||||||
|
docker-compose -f src/main/docker/postgresql.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:
|
||||||
|
|
||||||
|
./gradlew bootWar -Pprod jibDockerBuild
|
||||||
|
|
||||||
|
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 (`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 (`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://www.jhipster.tech
|
||||||
|
[jhipster 5.8.2 archive]: https://www.jhipster.tech/documentation-archive/v5.8.2
|
||||||
|
[using jhipster in development]: https://www.jhipster.tech/documentation-archive/v5.8.2/development/
|
||||||
|
[using docker and docker-compose]: https://www.jhipster.tech/documentation-archive/v5.8.2/docker-compose
|
||||||
|
[using jhipster in production]: https://www.jhipster.tech/documentation-archive/v5.8.2/production/
|
||||||
|
[running tests page]: https://www.jhipster.tech/documentation-archive/v5.8.2/running-tests/
|
||||||
|
[code quality page]: https://www.jhipster.tech/documentation-archive/v5.8.2/code-quality/
|
||||||
|
[setting up continuous integration]: https://www.jhipster.tech/documentation-archive/v5.8.2/setting-up-ci/
|
||||||
|
[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/
|
||||||
|
[jest]: https://facebook.github.io/jest/
|
||||||
|
[jasmine]: http://jasmine.github.io/2.0/introduction.html
|
||||||
|
[protractor]: https://angular.github.io/protractor/
|
||||||
|
[leaflet]: http://leafletjs.com/
|
||||||
|
[definitelytyped]: http://definitelytyped.org/
|
||||||
|
[openapi-generator]: https://openapi-generator.tech
|
||||||
|
[swagger-editor]: http://editor.swagger.io
|
||||||
|
[doing api-first development]: https://www.jhipster.tech/documentation-archive/v5.8.2/doing-api-first-development/
|
39
angular.json
Normal file
39
angular.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"hsadmin-ng": {
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src/main/webapp",
|
||||||
|
"projectType": "application",
|
||||||
|
"architect": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultProject": "hsadmin-ng",
|
||||||
|
"cli": {
|
||||||
|
"packageManager": "npm"
|
||||||
|
},
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"inlineStyle": true,
|
||||||
|
"inlineTemplate": false,
|
||||||
|
"spec": false,
|
||||||
|
"prefix": "jhi",
|
||||||
|
"styleExt": "css"
|
||||||
|
},
|
||||||
|
"@schematics/angular:directive": {
|
||||||
|
"spec": false,
|
||||||
|
"prefix": "jhi"
|
||||||
|
},
|
||||||
|
"@schematics/angular:guard": {
|
||||||
|
"spec": false
|
||||||
|
},
|
||||||
|
"@schematics/angular:pipe": {
|
||||||
|
"spec": false
|
||||||
|
},
|
||||||
|
"@schematics/angular:service": {
|
||||||
|
"spec": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
287
build.gradle
Normal file
287
build.gradle
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
import org.gradle.internal.os.OperatingSystem
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "http://repo.spring.io/plugins-release" }
|
||||||
|
maven { url "https://plugins.gradle.org/m2/" }
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "org.springframework.boot:spring-boot-gradle-plugin:${spring_boot_version}"
|
||||||
|
classpath "io.spring.gradle:propdeps-plugin:0.0.10.RELEASE"
|
||||||
|
classpath "org.openapitools:openapi-generator-gradle-plugin:3.3.0"
|
||||||
|
classpath "gradle.plugin.com.gorylenko.gradle-git-properties:gradle-git-properties:1.5.2"
|
||||||
|
//jhipster-needle-gradle-buildscript-dependency - JHipster will add additional gradle build script plugins here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "org.sonarqube" version "2.6.2"
|
||||||
|
id "net.ltgt.apt-eclipse" version "0.19"
|
||||||
|
id "net.ltgt.apt-idea" version "0.19"
|
||||||
|
id "net.ltgt.apt" version "0.19"
|
||||||
|
id "io.spring.dependency-management" version "1.0.6.RELEASE"
|
||||||
|
id "com.moowork.node" version "1.2.0"
|
||||||
|
id 'org.liquibase.gradle' version '2.0.1'
|
||||||
|
//jhipster-needle-gradle-plugins - JHipster will add additional gradle plugins here
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
sourceCompatibility=1.8
|
||||||
|
targetCompatibility=1.8
|
||||||
|
// Until JHipster supports JDK 9
|
||||||
|
assert System.properties['java.specification.version'] == '1.8'
|
||||||
|
|
||||||
|
apply plugin: 'maven'
|
||||||
|
apply plugin: 'org.springframework.boot'
|
||||||
|
apply plugin: 'war'
|
||||||
|
apply plugin: 'propdeps'
|
||||||
|
apply plugin: 'com.moowork.node'
|
||||||
|
apply plugin: 'io.spring.dependency-management'
|
||||||
|
apply plugin: 'idea'
|
||||||
|
|
||||||
|
idea {
|
||||||
|
module {
|
||||||
|
excludeDirs += files('node_modules')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyManagement {
|
||||||
|
imports {
|
||||||
|
mavenBom 'io.github.jhipster:jhipster-dependencies:' + jhipster_dependencies_version
|
||||||
|
//jhipster-needle-gradle-dependency-management - JHipster will add additional dependencies management here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultTasks 'bootRun'
|
||||||
|
|
||||||
|
group = 'org.hostsharing.hsadminng'
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
bootWar {
|
||||||
|
mainClassName = 'org.hostsharing.hsadminng.HsadminNgApp'
|
||||||
|
}
|
||||||
|
|
||||||
|
war {
|
||||||
|
webAppDirName = 'build/www/'
|
||||||
|
enabled = true
|
||||||
|
extension = 'war.original'
|
||||||
|
}
|
||||||
|
|
||||||
|
springBoot {
|
||||||
|
mainClassName = 'org.hostsharing.hsadminng.HsadminNgApp'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.current().isWindows()) {
|
||||||
|
// https://stackoverflow.com/questions/40037487/the-filename-or-extension-is-too-long-error-using-gradle
|
||||||
|
task classpathJar(type: Jar) {
|
||||||
|
dependsOn configurations.runtime
|
||||||
|
appendix = 'classpath'
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
manifest {
|
||||||
|
attributes 'Class-Path': configurations.runtime.files.collect {
|
||||||
|
it.toURI().toURL().toString().replaceFirst(/file:\/+/, '/').replaceAll(' ', '%20')
|
||||||
|
}.join(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bootRun {
|
||||||
|
dependsOn classpathJar
|
||||||
|
doFirst {
|
||||||
|
classpath = files("$buildDir/classes/java/main", "$buildDir/resources/main", classpathJar.archivePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
exclude '**/CucumberTest*'
|
||||||
|
|
||||||
|
// uncomment if the tests reports are not generated
|
||||||
|
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
|
||||||
|
// ignoreFailures true
|
||||||
|
reports.html.enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
task cucumberTest(type: Test) {
|
||||||
|
description = "Execute cucumber BDD tests."
|
||||||
|
group = "verification"
|
||||||
|
include '**/CucumberTest*'
|
||||||
|
|
||||||
|
// uncomment if the tests reports are not generated
|
||||||
|
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
|
||||||
|
// ignoreFailures true
|
||||||
|
reports.html.enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
check.dependsOn cucumberTest
|
||||||
|
task testReport(type: TestReport) {
|
||||||
|
destinationDir = file("$buildDir/reports/tests")
|
||||||
|
reportOn test
|
||||||
|
}
|
||||||
|
|
||||||
|
task cucumberTestReport(type: TestReport) {
|
||||||
|
destinationDir = file("$buildDir/reports/tests")
|
||||||
|
reportOn cucumberTest
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: 'gradle/docker.gradle'
|
||||||
|
apply from: 'gradle/sonar.gradle'
|
||||||
|
apply from: 'gradle/swagger.gradle'
|
||||||
|
//jhipster-needle-gradle-apply-from - JHipster will add additional gradle scripts to be applied here
|
||||||
|
|
||||||
|
if (project.hasProperty('prod')) {
|
||||||
|
apply from: 'gradle/profile_prod.gradle'
|
||||||
|
} else {
|
||||||
|
apply from: 'gradle/profile_dev.gradle'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!project.hasProperty('runList')) {
|
||||||
|
project.ext.runList = 'main'
|
||||||
|
}
|
||||||
|
|
||||||
|
project.ext.diffChangelogFile = 'src/main/resources/config/liquibase/changelog/' + new Date().format('yyyyMMddHHmmss') + '_changelog.xml'
|
||||||
|
|
||||||
|
liquibase {
|
||||||
|
activities {
|
||||||
|
main {
|
||||||
|
driver ''
|
||||||
|
url ''
|
||||||
|
username 'hsadminNg'
|
||||||
|
password ''
|
||||||
|
changeLogFile 'src/main/resources/config/liquibase/master.xml'
|
||||||
|
defaultSchemaName ''
|
||||||
|
logLevel 'debug'
|
||||||
|
classpath 'src/main/resources/'
|
||||||
|
}
|
||||||
|
diffLog {
|
||||||
|
driver ''
|
||||||
|
url ''
|
||||||
|
username 'hsadminNg'
|
||||||
|
password ''
|
||||||
|
changeLogFile project.ext.diffChangelogFile
|
||||||
|
referenceUrl 'hibernate:spring:org.hostsharing.hsadminng.domain?dialect=&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy'
|
||||||
|
defaultSchemaName ''
|
||||||
|
logLevel 'debug'
|
||||||
|
classpath "$buildDir/classes/java/main"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runList = project.ext.runList
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
providedRuntime
|
||||||
|
compile.exclude module: "spring-boot-starter-tomcat"
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
//jhipster-needle-gradle-repositories - JHipster will add additional repositories
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Use ", version: jhipster_dependencies_version, changing: true" if you want
|
||||||
|
// to use a SNAPSHOT release instead of a stable release
|
||||||
|
compile group: "io.github.jhipster", name: "jhipster-framework"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-cache"
|
||||||
|
compile "io.dropwizard.metrics:metrics-core"
|
||||||
|
compile 'io.micrometer:micrometer-registry-prometheus'
|
||||||
|
compile "net.logstash.logback:logstash-logback-encoder"
|
||||||
|
compile "com.fasterxml.jackson.datatype:jackson-datatype-hppc"
|
||||||
|
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
|
||||||
|
compile "com.fasterxml.jackson.datatype:jackson-datatype-hibernate5"
|
||||||
|
compile "com.fasterxml.jackson.core:jackson-annotations"
|
||||||
|
compile "com.fasterxml.jackson.core:jackson-databind"
|
||||||
|
compile "com.fasterxml.jackson.module:jackson-module-afterburner"
|
||||||
|
compile "javax.cache:cache-api"
|
||||||
|
compile "org.hibernate:hibernate-core"
|
||||||
|
compile "com.zaxxer:HikariCP"
|
||||||
|
compile "org.apache.commons:commons-lang3"
|
||||||
|
compile "commons-io:commons-io"
|
||||||
|
compile "javax.transaction:javax.transaction-api"
|
||||||
|
compile "org.ehcache:ehcache"
|
||||||
|
compile "org.hibernate:hibernate-entitymanager"
|
||||||
|
compile "org.hibernate:hibernate-envers"
|
||||||
|
compile "org.hibernate.validator:hibernate-validator"
|
||||||
|
compile "org.liquibase:liquibase-core"
|
||||||
|
compile "com.mattbertolini:liquibase-slf4j"
|
||||||
|
liquibaseRuntime "org.liquibase:liquibase-core"
|
||||||
|
liquibaseRuntime "org.liquibase.ext:liquibase-hibernate5:${liquibase_hibernate5_version}"
|
||||||
|
liquibaseRuntime sourceSets.main.compileClasspath
|
||||||
|
compile "org.springframework.boot:spring-boot-loader-tools"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-mail"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-logging"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-actuator"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-aop"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-security"
|
||||||
|
compile ("org.springframework.boot:spring-boot-starter-web") {
|
||||||
|
exclude module: 'spring-boot-starter-tomcat'
|
||||||
|
}
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-undertow"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
|
||||||
|
compile "org.zalando:problem-spring-web:0.24.0-RC.0"
|
||||||
|
compile "org.springframework.boot:spring-boot-starter-cloud-connectors"
|
||||||
|
compile "org.springframework.security:spring-security-config"
|
||||||
|
compile "org.springframework.security:spring-security-data"
|
||||||
|
compile "org.springframework.security:spring-security-web"
|
||||||
|
compile "io.jsonwebtoken:jjwt-api"
|
||||||
|
runtime "io.jsonwebtoken:jjwt-impl"
|
||||||
|
runtime "io.jsonwebtoken:jjwt-jackson"
|
||||||
|
compile ("io.springfox:springfox-swagger2") {
|
||||||
|
exclude module: 'mapstruct'
|
||||||
|
}
|
||||||
|
compile "io.springfox:springfox-bean-validators"
|
||||||
|
compile "org.postgresql:postgresql"
|
||||||
|
liquibaseRuntime "org.postgresql:postgresql"
|
||||||
|
compile "org.mapstruct:mapstruct-jdk8:${mapstruct_version}"
|
||||||
|
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstruct_version}"
|
||||||
|
annotationProcessor "org.hibernate:hibernate-jpamodelgen"
|
||||||
|
annotationProcessor ("org.springframework.boot:spring-boot-configuration-processor") {
|
||||||
|
exclude group: 'com.vaadin.external.google', module: 'android-json'
|
||||||
|
}
|
||||||
|
testCompile "com.jayway.jsonpath:json-path"
|
||||||
|
testCompile "io.cucumber:cucumber-junit"
|
||||||
|
testCompile "io.cucumber:cucumber-spring"
|
||||||
|
testCompile ("org.springframework.boot:spring-boot-starter-test") {
|
||||||
|
exclude group: 'com.vaadin.external.google', module: 'android-json'
|
||||||
|
}
|
||||||
|
testCompile "org.springframework.security:spring-security-test"
|
||||||
|
testCompile "org.springframework.boot:spring-boot-test"
|
||||||
|
testCompile "org.assertj:assertj-core"
|
||||||
|
testCompile "junit:junit"
|
||||||
|
testCompile "org.mockito:mockito-core"
|
||||||
|
testCompile "com.mattbertolini:liquibase-slf4j"
|
||||||
|
testCompile "org.hamcrest:hamcrest-library"
|
||||||
|
testCompile "com.h2database:h2"
|
||||||
|
liquibaseRuntime "com.h2database:h2"
|
||||||
|
//jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
|
||||||
|
}
|
||||||
|
|
||||||
|
task cleanResources(type: Delete) {
|
||||||
|
delete 'build/resources'
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper {
|
||||||
|
gradleVersion = '4.10.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
task stage(dependsOn: 'bootWar') {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty('nodeInstall')) {
|
||||||
|
node {
|
||||||
|
version = "${node_version}"
|
||||||
|
npmVersion = "${npm_version}"
|
||||||
|
yarnVersion = "${yarn_version}"
|
||||||
|
download = true
|
||||||
|
}
|
||||||
|
}
|
53
gradle.properties
Normal file
53
gradle.properties
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
rootProject.name=hsadmin-ng
|
||||||
|
profile=dev
|
||||||
|
|
||||||
|
# Build properties
|
||||||
|
node_version=10.15.0
|
||||||
|
npm_version=6.4.1
|
||||||
|
yarn_version=1.13.0
|
||||||
|
|
||||||
|
# Dependency versions
|
||||||
|
jhipster_dependencies_version=2.1.1
|
||||||
|
# The spring-boot version should match the one managed by
|
||||||
|
# https://mvnrepository.com/artifact/io.github.jhipster/jhipster-dependencies/${jhipster_dependencies_version}
|
||||||
|
spring_boot_version=2.0.8.RELEASE
|
||||||
|
# The hibernate version should match the one managed by
|
||||||
|
# https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/${spring-boot.version} -->
|
||||||
|
hibernate_version=5.2.17.Final
|
||||||
|
mapstruct_version=1.2.0.Final
|
||||||
|
|
||||||
|
liquibase_hibernate5_version=3.6
|
||||||
|
liquibaseTaskPrefix=liquibase
|
||||||
|
|
||||||
|
# jhipster-needle-gradle-property - JHipster will add additional properties here
|
||||||
|
|
||||||
|
## below are some of the gradle performance improvement settings that can be used as required, these are not enabled by default
|
||||||
|
|
||||||
|
## The Gradle daemon aims to improve the startup and execution time of Gradle.
|
||||||
|
## The daemon is enabled by default in Gradle 3+ setting this to false will disable this.
|
||||||
|
## TODO: disable daemon on CI, since builds should be clean and reliable on servers
|
||||||
|
## https://docs.gradle.org/current/userguide/gradle_daemon.html#sec:ways_to_disable_gradle_daemon
|
||||||
|
## un comment the below line to disable the daemon
|
||||||
|
|
||||||
|
#org.gradle.daemon=false
|
||||||
|
|
||||||
|
## Specifies the JVM arguments used for the daemon process.
|
||||||
|
## The setting is particularly useful for tweaking memory settings.
|
||||||
|
## Default value: -Xmx1024m -XX:MaxPermSize=256m
|
||||||
|
## un comment the below line to override the daemon defaults
|
||||||
|
|
||||||
|
#org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
|
||||||
|
## When configured, Gradle will run in incubating parallel mode.
|
||||||
|
## This option should only be used with decoupled projects. More details, visit
|
||||||
|
## http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
## un comment the below line to enable parallel mode
|
||||||
|
|
||||||
|
#org.gradle.parallel=true
|
||||||
|
|
||||||
|
## Enables new incubating mode that makes Gradle selective when configuring projects.
|
||||||
|
## Only relevant projects are configured which results in faster builds for large multi-projects.
|
||||||
|
## http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:configuration_on_demand
|
||||||
|
## un comment the below line to enable the selective mode
|
||||||
|
|
||||||
|
#org.gradle.configureondemand=true
|
35
gradle/docker.gradle
Normal file
35
gradle/docker.gradle
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "gradle.plugin.com.google.cloud.tools:jib-gradle-plugin:0.9.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: com.google.cloud.tools.jib.gradle.JibPlugin
|
||||||
|
|
||||||
|
jib {
|
||||||
|
from {
|
||||||
|
image = 'openjdk:8-jre-alpine'
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
image = 'hsadminng:latest'
|
||||||
|
}
|
||||||
|
container {
|
||||||
|
entrypoint = ['sh', '-c', 'chmod +x /entrypoint.sh && sync && /entrypoint.sh']
|
||||||
|
ports = ['8080']
|
||||||
|
environment = [
|
||||||
|
SPRING_OUTPUT_ANSI_ENABLED: 'ALWAYS',
|
||||||
|
JHIPSTER_SLEEP: '0'
|
||||||
|
]
|
||||||
|
useCurrentTimestamp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task copyWwwIntoStatic (type: Copy) {
|
||||||
|
from 'build/www/'
|
||||||
|
into 'build/resources/main/static'
|
||||||
|
}
|
||||||
|
|
||||||
|
jibDockerBuild.dependsOn copyWwwIntoStatic
|
63
gradle/profile_dev.gradle
Normal file
63
gradle/profile_dev.gradle
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import org.gradle.internal.os.OperatingSystem
|
||||||
|
|
||||||
|
apply plugin: 'org.springframework.boot'
|
||||||
|
apply plugin: 'com.moowork.node'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile "org.springframework.boot:spring-boot-devtools"
|
||||||
|
compile "com.h2database:h2"
|
||||||
|
}
|
||||||
|
|
||||||
|
def profiles = 'dev'
|
||||||
|
if (project.hasProperty('no-liquibase')) {
|
||||||
|
profiles += ',no-liquibase'
|
||||||
|
}
|
||||||
|
if (project.hasProperty('tls')) {
|
||||||
|
profiles += ',tls'
|
||||||
|
}
|
||||||
|
|
||||||
|
springBoot {
|
||||||
|
buildInfo {
|
||||||
|
properties {
|
||||||
|
time = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bootRun {
|
||||||
|
args = []
|
||||||
|
}
|
||||||
|
|
||||||
|
task webpackBuildDev(type: NpmTask) {
|
||||||
|
inputs.files(fileTree('src/main/webapp/'))
|
||||||
|
|
||||||
|
def webpackDevFiles = fileTree('webpack//')
|
||||||
|
webpackDevFiles.exclude('webpack.prod.js')
|
||||||
|
inputs.files(webpackDevFiles)
|
||||||
|
|
||||||
|
outputs.files(fileTree("build/www/"))
|
||||||
|
|
||||||
|
dependsOn npmInstall
|
||||||
|
|
||||||
|
args = ["run", "webpack:build"]
|
||||||
|
}
|
||||||
|
|
||||||
|
task copyIntoStatic (type: Copy) {
|
||||||
|
from 'build/www/'
|
||||||
|
into 'build/resources/main/static'
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
filesMatching('**/application.yml') {
|
||||||
|
filter {
|
||||||
|
it.replace('#project.version#', version)
|
||||||
|
}
|
||||||
|
filter {
|
||||||
|
it.replace('#spring.profiles.active#', profiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources.dependsOn webpackBuildDev
|
||||||
|
copyIntoStatic.dependsOn processResources
|
||||||
|
bootJar.dependsOn copyIntoStatic
|
63
gradle/profile_prod.gradle
Normal file
63
gradle/profile_prod.gradle
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apply plugin: 'org.springframework.boot'
|
||||||
|
apply plugin: 'com.gorylenko.gradle-git-properties'
|
||||||
|
apply plugin: 'com.moowork.node'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testCompile "com.h2database:h2"
|
||||||
|
}
|
||||||
|
|
||||||
|
def profiles = 'prod'
|
||||||
|
if (project.hasProperty('no-liquibase')) {
|
||||||
|
profiles += ',no-liquibase'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty('swagger')) {
|
||||||
|
profiles += ',swagger'
|
||||||
|
}
|
||||||
|
|
||||||
|
springBoot {
|
||||||
|
buildInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
bootRun {
|
||||||
|
args = []
|
||||||
|
}
|
||||||
|
|
||||||
|
task webpack_test(type: NpmTask, dependsOn: 'npm_install') {
|
||||||
|
args = ["run", "webpack:test"]
|
||||||
|
}
|
||||||
|
|
||||||
|
task webpack(type: NpmTask, dependsOn: 'npm_install') {
|
||||||
|
args = ["run", "webpack:prod"]
|
||||||
|
}
|
||||||
|
|
||||||
|
task copyIntoStatic (type: Copy) {
|
||||||
|
from 'build/www/'
|
||||||
|
into 'build/resources/main/static'
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
filesMatching('**/application.yml') {
|
||||||
|
filter {
|
||||||
|
it.replace('#project.version#', version)
|
||||||
|
}
|
||||||
|
filter {
|
||||||
|
it.replace('#spring.profiles.active#', profiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateGitProperties {
|
||||||
|
onlyIf {
|
||||||
|
!source.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gitProperties {
|
||||||
|
keys = ['git.branch', 'git.commit.id.abbrev', 'git.commit.id.describe']
|
||||||
|
}
|
||||||
|
|
||||||
|
test.dependsOn webpack_test
|
||||||
|
processResources.dependsOn webpack
|
||||||
|
copyIntoStatic.dependsOn processResources
|
||||||
|
bootJar.dependsOn copyIntoStatic
|
47
gradle/sonar.gradle
Normal file
47
gradle/sonar.gradle
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
apply plugin: "org.sonarqube"
|
||||||
|
apply plugin: 'jacoco'
|
||||||
|
|
||||||
|
jacoco {
|
||||||
|
toolVersion = '0.8.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
jacocoTestReport {
|
||||||
|
reports {
|
||||||
|
xml.enabled true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sonarqube {
|
||||||
|
properties {
|
||||||
|
property "sonar.host.url", "http://localhost:9001"
|
||||||
|
property "sonar.exclusions", "src/main/webapp/content/**/*.*,src/main/webapp/i18n/*.js, build/www/**/*.*"
|
||||||
|
|
||||||
|
property "sonar.issue.ignore.multicriteria", "S3437,S4502,S4684,UndocumentedApi,BoldAndItalicTagsCheck"
|
||||||
|
|
||||||
|
// Rule https://sonarcloud.io/coding_rules?open=Web%3ABoldAndItalicTagsCheck&rule_key=Web%3ABoldAndItalicTagsCheck is ignored. Even if we agree that using the "i" tag is an awful practice, this is what is recommended by http://fontawesome.io/examples/
|
||||||
|
property "sonar.issue.ignore.multicriteria.BoldAndItalicTagsCheck.resourceKey", ">src/main/webapp/app/**/*.*"
|
||||||
|
property "sonar.issue.ignore.multicriteria.BoldAndItalicTagsCheck.ruleKey", "Web:BoldAndItalicTagsCheck"
|
||||||
|
|
||||||
|
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS3437&rule_key=squid%3AS3437 is ignored, as a JPA-managed field cannot be transient
|
||||||
|
property "sonar.issue.ignore.multicriteria.S3437.resourceKey", "src/main/java/**/*"
|
||||||
|
property "sonar.issue.ignore.multicriteria.S3437.ruleKey", "squid:S3437"
|
||||||
|
|
||||||
|
// Rule https://sonarcloud.io/coding_rules?open=squid%3AUndocumentedApi&rule_key=squid%3AUndocumentedApi is ignored, as we want to follow "clean code" guidelines and classes, methods and arguments names should be self-explanatory
|
||||||
|
property "sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey", "src/main/java/**/*"
|
||||||
|
property "sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey", "squid:UndocumentedApi"
|
||||||
|
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS4502&rule_key=squid%3AS4502 is ignored, as for JWT tokens we are not subject to CSRF attack
|
||||||
|
property "sonar.issue.ignore.multicriteria.S4502.resourceKey", "src/main/java/**/*"
|
||||||
|
property "sonar.issue.ignore.multicriteria.S4502.ruleKey", "squid:S4502"
|
||||||
|
|
||||||
|
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS4684&rule_key=squid%3AS4684
|
||||||
|
property "sonar.issue.ignore.multicriteria.S4684.resourceKey", "src/main/java/**/*"
|
||||||
|
property "sonar.issue.ignore.multicriteria.S4684.ruleKey", "squid:S4684"
|
||||||
|
|
||||||
|
property "sonar.jacoco.reportPaths", "${project.buildDir}/jacoco/test.exec"
|
||||||
|
property "sonar.java.codeCoveragePlugin", "jacoco"
|
||||||
|
property "sonar.typescript.lcov.reportPaths", "${project.buildDir}/test-results/lcov.info"
|
||||||
|
property "sonar.junit.reportPaths", "${project.buildDir}/test-results"
|
||||||
|
property "sonar.sources", "${project.projectDir}/src/main/"
|
||||||
|
property "sonar.tests", "${project.projectDir}/src/test/"
|
||||||
|
}
|
||||||
|
}
|
28
gradle/swagger.gradle
Normal file
28
gradle/swagger.gradle
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Plugin that provides API-first development using OpenAPI-generator to
|
||||||
|
* generate Spring-MVC endpoint stubs at compile time from an OpenAPI definition file
|
||||||
|
*/
|
||||||
|
apply plugin: 'org.openapi.generator'
|
||||||
|
|
||||||
|
openApiGenerate {
|
||||||
|
generatorName = "spring"
|
||||||
|
inputSpec = "$rootDir/src/main/resources/swagger/api.yml".toString()
|
||||||
|
outputDir = "$buildDir/openapi".toString()
|
||||||
|
apiPackage = "org.hostsharing.hsadminng.web.api"
|
||||||
|
modelPackage = "org.hostsharing.hsadminng.web.api.model"
|
||||||
|
apiFilesConstrainedTo = [""]
|
||||||
|
modelFilesConstrainedTo = [""]
|
||||||
|
supportingFilesConstrainedTo = ["ApiUtil.java"]
|
||||||
|
configOptions = [delegatePattern: "true"]
|
||||||
|
validateSpec = true
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
srcDir file("${project.buildDir.path}/openapi/src/main/java")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileJava.dependsOn("openApiGenerate")
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
3
gradle/zipkin.gradle
Normal file
3
gradle/zipkin.gradle
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
dependencies {
|
||||||
|
compile "org.springframework.cloud:spring-cloud-starter-zipkin"
|
||||||
|
}
|
172
gradlew
vendored
Executable file
172
gradlew
vendored
Executable file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
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
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
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
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
18161
package-lock.json
generated
Normal file
18161
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
126
package.json
Normal file
126
package.json
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
{
|
||||||
|
"name": "hsadmin-ng",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Description for hsadminNg",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"cacheDirectories": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/common": "7.2.4",
|
||||||
|
"@angular/compiler": "7.2.4",
|
||||||
|
"@angular/core": "7.2.4",
|
||||||
|
"@angular/forms": "7.2.4",
|
||||||
|
"@angular/platform-browser": "7.2.4",
|
||||||
|
"@angular/platform-browser-dynamic": "7.2.4",
|
||||||
|
"@angular/router": "7.2.4",
|
||||||
|
"@fortawesome/angular-fontawesome": "0.3.0",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "1.2.14",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "5.7.1",
|
||||||
|
"@ng-bootstrap/ng-bootstrap": "4.0.2",
|
||||||
|
"@ngx-translate/core": "11.0.1",
|
||||||
|
"@ngx-translate/http-loader": "4.0.0",
|
||||||
|
"bootstrap": "4.2.1",
|
||||||
|
"core-js": "2.6.4",
|
||||||
|
"moment": "2.24.0",
|
||||||
|
"ng-jhipster": "0.9.1",
|
||||||
|
"ngx-cookie": "2.0.1",
|
||||||
|
"ngx-infinite-scroll": "7.0.1",
|
||||||
|
"ngx-webstorage": "2.0.1",
|
||||||
|
"rxjs": "6.4.0",
|
||||||
|
"swagger-ui": "2.2.10",
|
||||||
|
"tslib": "1.9.3",
|
||||||
|
"zone.js": "0.8.29"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular/cli": "7.3.1",
|
||||||
|
"@angular/compiler-cli": "7.2.4",
|
||||||
|
"@ngtools/webpack": "7.3.1",
|
||||||
|
"@types/jest": "24.0.0",
|
||||||
|
"@types/node": "10.12.24",
|
||||||
|
"angular-router-loader": "0.8.5",
|
||||||
|
"angular2-template-loader": "0.6.2",
|
||||||
|
"autoprefixer": "9.4.7",
|
||||||
|
"browser-sync": "2.26.3",
|
||||||
|
"browser-sync-webpack-plugin": "2.2.2",
|
||||||
|
"cache-loader": "2.0.1",
|
||||||
|
"codelyzer": "4.5.0",
|
||||||
|
"copy-webpack-plugin": "4.6.0",
|
||||||
|
"css-loader": "2.1.0",
|
||||||
|
"file-loader": "3.0.1",
|
||||||
|
"fork-ts-checker-webpack-plugin": "0.5.2",
|
||||||
|
"friendly-errors-webpack-plugin": "1.7.0",
|
||||||
|
"generator-jhipster": "5.8.2",
|
||||||
|
"html-loader": "0.5.5",
|
||||||
|
"html-webpack-plugin": "3.2.0",
|
||||||
|
"husky": "1.3.1",
|
||||||
|
"jest": "24.1.0",
|
||||||
|
"jest-junit": "6.2.1",
|
||||||
|
"jest-preset-angular": "6.0.2",
|
||||||
|
"jest-sonar-reporter": "2.0.0",
|
||||||
|
"lint-staged": "8.1.3",
|
||||||
|
"merge-jsons-webpack-plugin": "1.0.18",
|
||||||
|
"mini-css-extract-plugin": "0.5.0",
|
||||||
|
"moment-locales-webpack-plugin": "1.0.7",
|
||||||
|
"optimize-css-assets-webpack-plugin": "5.0.1",
|
||||||
|
"prettier": "1.16.4",
|
||||||
|
"reflect-metadata": "0.1.13",
|
||||||
|
"rimraf": "2.6.3",
|
||||||
|
"simple-progress-webpack-plugin": "1.1.2",
|
||||||
|
"style-loader": "0.23.1",
|
||||||
|
"terser-webpack-plugin": "1.2.2",
|
||||||
|
"thread-loader": "2.1.2",
|
||||||
|
"to-string-loader": "1.1.5",
|
||||||
|
"ts-loader": "5.3.3",
|
||||||
|
"tslint": "5.12.1",
|
||||||
|
"tslint-config-prettier": "1.18.0",
|
||||||
|
"tslint-loader": "3.6.0",
|
||||||
|
"typescript": "3.2.4",
|
||||||
|
"postcss-loader": "3.0.0",
|
||||||
|
"webpack": "4.29.3",
|
||||||
|
"webpack-cli": "3.2.3",
|
||||||
|
"webpack-dev-server": "3.1.14",
|
||||||
|
"webpack-merge": "4.2.1",
|
||||||
|
"webpack-notifier": "1.7.0",
|
||||||
|
"webpack-visualizer-plugin": "0.1.11",
|
||||||
|
"workbox-webpack-plugin": "3.6.3",
|
||||||
|
"write-file-webpack-plugin": "4.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.9.0"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"{,src/**/}*.{md,json,ts,css,scss}": [
|
||||||
|
"prettier --write",
|
||||||
|
"git add"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prettier:format": "prettier --write \"{,src/**/}*.{md,json,ts,css,scss}\"",
|
||||||
|
"lint": "tslint --project tsconfig.json -e 'node_modules/**'",
|
||||||
|
"lint:fix": "npm run lint -- --fix",
|
||||||
|
"ngc": "ngc -p tsconfig-aot.json",
|
||||||
|
"cleanup": "rimraf build/{aot,www}",
|
||||||
|
"clean-www": "rimraf build//www/app/{src,build/}",
|
||||||
|
"start": "npm run webpack:dev",
|
||||||
|
"start-tls": "npm run webpack:dev -- --env.tls",
|
||||||
|
"serve": "npm run start",
|
||||||
|
"build": "npm run webpack:prod",
|
||||||
|
"test": "npm run lint && jest --coverage --logHeapUsage -w=2 --config src/test/javascript/jest.conf.js",
|
||||||
|
"test:watch": "npm run test -- --watch",
|
||||||
|
"webpack:dev": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --env.stats=minimal",
|
||||||
|
"webpack:dev-verbose": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --profile --progress --env.stats=normal",
|
||||||
|
"webpack:build:main": "npm run webpack -- --config webpack/webpack.dev.js --env.stats=minimal",
|
||||||
|
"webpack:build": "npm run cleanup && npm run webpack:build:main",
|
||||||
|
"webpack:prod:main": "npm run webpack -- --config webpack/webpack.prod.js --profile",
|
||||||
|
"webpack:prod": "npm run cleanup && npm run webpack:prod:main && npm run clean-www",
|
||||||
|
"webpack:test": "npm run test",
|
||||||
|
"webpack-dev-server": "node --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
|
||||||
|
"webpack": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js"
|
||||||
|
},
|
||||||
|
"jestSonar": {
|
||||||
|
"reportPath": "build/test-results/jest",
|
||||||
|
"reportFile": "TESTS-results-sonar.xml"
|
||||||
|
}
|
||||||
|
}
|
5
postcss.config.js
Normal file
5
postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
require('autoprefixer')
|
||||||
|
]
|
||||||
|
}
|
7
proxy.conf.json
Normal file
7
proxy.conf.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"*": {
|
||||||
|
"target": "http://localhost:8080",
|
||||||
|
"secure": false,
|
||||||
|
"loglevel": "debug"
|
||||||
|
}
|
||||||
|
}
|
1
settings.gradle
Normal file
1
settings.gradle
Normal file
@ -0,0 +1 @@
|
|||||||
|
rootProject.name = 'hsadmin-ng'
|
14
src/main/docker/.dockerignore
Normal file
14
src/main/docker/.dockerignore
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||||
|
classes/
|
||||||
|
generated-sources/
|
||||||
|
generated-test-sources/
|
||||||
|
h2db/
|
||||||
|
maven-archiver/
|
||||||
|
maven-status/
|
||||||
|
reports/
|
||||||
|
surefire-reports/
|
||||||
|
test-classes/
|
||||||
|
test-results/
|
||||||
|
www/
|
||||||
|
!*.jar
|
||||||
|
!*.war
|
20
src/main/docker/Dockerfile
Normal file
20
src/main/docker/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM openjdk:8-jre-alpine
|
||||||
|
|
||||||
|
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
|
||||||
|
JHIPSTER_SLEEP=0 \
|
||||||
|
JAVA_OPTS=""
|
||||||
|
|
||||||
|
# Add a jhipster user to run our application so that it doesn't need to run as root
|
||||||
|
RUN adduser -D -s /bin/sh jhipster
|
||||||
|
WORKDIR /home/jhipster
|
||||||
|
|
||||||
|
ADD entrypoint.sh entrypoint.sh
|
||||||
|
RUN chmod 755 entrypoint.sh && chown jhipster:jhipster entrypoint.sh
|
||||||
|
USER jhipster
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ADD *.war app.war
|
||||||
|
|
15
src/main/docker/app.yml
Normal file
15
src/main/docker/app.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
hsadminng-app:
|
||||||
|
image: hsadminng
|
||||||
|
environment:
|
||||||
|
- _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||||
|
- SPRING_PROFILES_ACTIVE=prod,swagger
|
||||||
|
- SPRING_DATASOURCE_URL=jdbc:postgresql://hsadminng-postgresql:5432/hsadminNg
|
||||||
|
- JHIPSTER_SLEEP=10 # gives time for the database to boot before the application
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
hsadminng-postgresql:
|
||||||
|
extends:
|
||||||
|
file: postgresql.yml
|
||||||
|
service: hsadminng-postgresql
|
4
src/main/docker/entrypoint.sh
Normal file
4
src/main/docker/entrypoint.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
|
||||||
|
exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar "${HOME}/app.war" "$@"
|
11
src/main/docker/postgresql.yml
Normal file
11
src/main/docker/postgresql.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
hsadminng-postgresql:
|
||||||
|
image: postgres:10.4
|
||||||
|
# volumes:
|
||||||
|
# - ~/volumes/jhipster/hsadminNg/postgresql/:/var/lib/postgresql/data/
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=hsadminNg
|
||||||
|
- POSTGRES_PASSWORD=
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
7
src/main/docker/sonar.yml
Normal file
7
src/main/docker/sonar.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
hsadminng-sonar:
|
||||||
|
image: sonarqube:7.1
|
||||||
|
ports:
|
||||||
|
- 9001:9000
|
||||||
|
- 9092:9092
|
6
src/main/docker/swagger-editor.yml
Normal file
6
src/main/docker/swagger-editor.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
swagger-editor:
|
||||||
|
image: swaggerapi/swagger-editor:latest
|
||||||
|
ports:
|
||||||
|
- 7742:8080
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.hostsharing.hsadminng;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.DefaultProfileUtil;
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
|
import org.springframework.boot.web.servlet.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(HsadminNgApp.class);
|
||||||
|
}
|
||||||
|
}
|
98
src/main/java/org/hostsharing/hsadminng/HsadminNgApp.java
Normal file
98
src/main/java/org/hostsharing/hsadminng/HsadminNgApp.java
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package org.hostsharing.hsadminng;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.ApplicationProperties;
|
||||||
|
import org.hostsharing.hsadminng.config.DefaultProfileUtil;
|
||||||
|
|
||||||
|
import io.github.jhipster.config.JHipsterConstants;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
|
||||||
|
public class HsadminNgApp {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(HsadminNgApp.class);
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
public HsadminNgApp(Environment env) {
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes hsadminNg.
|
||||||
|
* <p>
|
||||||
|
* Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile
|
||||||
|
* <p>
|
||||||
|
* You can find more information on how profiles work with JHipster on <a href="https://www.jhipster.tech/profiles/">https://www.jhipster.tech/profiles/</a>.
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void initApplication() {
|
||||||
|
Collection<String> 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
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication app = new SpringApplication(HsadminNgApp.class);
|
||||||
|
DefaultProfileUtil.addDefaultProfile(app);
|
||||||
|
Environment env = app.run(args).getEnvironment();
|
||||||
|
logApplicationStartup(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void logApplicationStartup(Environment env) {
|
||||||
|
String protocol = "http";
|
||||||
|
if (env.getProperty("server.ssl.key-store") != null) {
|
||||||
|
protocol = "https";
|
||||||
|
}
|
||||||
|
String serverPort = env.getProperty("server.port");
|
||||||
|
String contextPath = env.getProperty("server.servlet.context-path");
|
||||||
|
if (StringUtils.isBlank(contextPath)) {
|
||||||
|
contextPath = "/";
|
||||||
|
}
|
||||||
|
String hostAddress = "localhost";
|
||||||
|
try {
|
||||||
|
hostAddress = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
log.warn("The host name could not be determined, using `localhost` as fallback");
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
serverPort,
|
||||||
|
contextPath,
|
||||||
|
protocol,
|
||||||
|
hostAddress,
|
||||||
|
serverPort,
|
||||||
|
contextPath,
|
||||||
|
env.getActiveProfiles());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package org.hostsharing.hsadminng.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(@org.springframework.stereotype.Repository *)" +
|
||||||
|
" || within(@org.springframework.stereotype.Service *)" +
|
||||||
|
" || within(@org.springframework.web.bind.annotation.RestController *)")
|
||||||
|
public void springBeanPointcut() {
|
||||||
|
// Method is empty as this is just a Pointcut, the implementations are in the advices.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointcut that matches all Spring beans in the application's main packages.
|
||||||
|
*/
|
||||||
|
@Pointcut("within(org.hostsharing.hsadminng.repository..*)"+
|
||||||
|
" || within(org.hostsharing.hsadminng.service..*)"+
|
||||||
|
" || within(org.hostsharing.hsadminng.web.rest..*)")
|
||||||
|
public void applicationPackagePointcut() {
|
||||||
|
// Method is empty as this is just a Pointcut, the implementations are in the advices.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advice that logs methods throwing exceptions.
|
||||||
|
*
|
||||||
|
* @param joinPoint join point for advice
|
||||||
|
* @param e exception
|
||||||
|
*/
|
||||||
|
@AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", 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.
|
||||||
|
*
|
||||||
|
* @param joinPoint join point for advice
|
||||||
|
* @return result
|
||||||
|
* @throws Throwable throws IllegalArgumentException
|
||||||
|
*/
|
||||||
|
@Around("applicationPackagePointcut() && springBeanPointcut()")
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties specific to Hsadmin Ng.
|
||||||
|
* <p>
|
||||||
|
* Properties are configured in the application.yml file.
|
||||||
|
* See {@link io.github.jhipster.config.JHipsterProperties} for a good example.
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
|
||||||
|
public class ApplicationProperties {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package org.hostsharing.hsadminng.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 org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||||
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync
|
||||||
|
@EnableScheduling
|
||||||
|
public class AsyncConfiguration implements AsyncConfigurer, SchedulingConfigurer {
|
||||||
|
|
||||||
|
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("hsadmin-ng-Executor-");
|
||||||
|
return new ExceptionHandlingAsyncTaskExecutor(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||||
|
return new SimpleAsyncUncaughtExceptionHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||||
|
taskRegistrar.setScheduler(scheduledTaskExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Executor scheduledTaskExecutor() {
|
||||||
|
return Executors.newScheduledThreadPool(jHipsterProperties.getAsync().getCorePoolSize());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import org.ehcache.config.builders.*;
|
||||||
|
import org.ehcache.jsr107.Eh107Configuration;
|
||||||
|
|
||||||
|
import io.github.jhipster.config.JHipsterProperties;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.context.annotation.*;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableCaching
|
||||||
|
public class CacheConfiguration {
|
||||||
|
|
||||||
|
private final javax.cache.configuration.Configuration<Object, Object> 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(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds())))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JCacheManagerCustomizer cacheManagerCustomizer() {
|
||||||
|
return cm -> {
|
||||||
|
cm.createCache(org.hostsharing.hsadminng.repository.UserRepository.USERS_BY_LOGIN_CACHE, jcacheConfiguration);
|
||||||
|
cm.createCache(org.hostsharing.hsadminng.repository.UserRepository.USERS_BY_EMAIL_CACHE, jcacheConfiguration);
|
||||||
|
// jhipster-needle-ehcache-add-entry
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.hostsharing.hsadminng.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;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD)
|
||||||
|
public class CloudDatabaseConfiguration extends AbstractCloudConfig {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class);
|
||||||
|
|
||||||
|
private static final String CLOUD_CONFIGURATION_HIKARI_PREFIX = "spring.datasource.hikari";
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConfigurationProperties(CLOUD_CONFIGURATION_HIKARI_PREFIX)
|
||||||
|
public DataSource dataSource() {
|
||||||
|
log.info("Configuring JDBC datasource from a cloud provider");
|
||||||
|
return connectionFactory().dataSource();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.hostsharing.hsadminng.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";
|
||||||
|
public static final String DEFAULT_LANGUAGE = "de";
|
||||||
|
|
||||||
|
private Constants() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import io.github.jhipster.config.JHipsterConstants;
|
||||||
|
import io.github.jhipster.config.h2.H2ConfigurationHelper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
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.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableJpaRepositories("org.hostsharing.hsadminng.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 Object h2TCPServer() throws SQLException {
|
||||||
|
String port = getValidPortForH2();
|
||||||
|
log.debug("H2 database is available on port {}", port);
|
||||||
|
return H2ConfigurationHelper.createServer(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getValidPortForH2() {
|
||||||
|
int port = Integer.parseInt(env.getProperty("server.port"));
|
||||||
|
if (port < 10000) {
|
||||||
|
port = 10000 + port;
|
||||||
|
} else {
|
||||||
|
if (port < 63536) {
|
||||||
|
port = port + 2000;
|
||||||
|
} else {
|
||||||
|
port = port - 2000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.valueOf(port);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.hostsharing.hsadminng.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.WebMvcConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the converters to use the ISO format for dates by default.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class DateTimeFormatConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addFormatters(FormatterRegistry registry) {
|
||||||
|
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||||
|
registrar.setUseIsoFormat(true);
|
||||||
|
registrar.registerFormatters(registry);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package org.hostsharing.hsadminng.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 <code>spring.profiles.active</code> set in the environment or as command line argument.
|
||||||
|
* If the value is not available in <code>application.yml</code> then <code>dev</code> 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<String, Object> defProperties = new HashMap<>();
|
||||||
|
/*
|
||||||
|
* The default profile to use when no other profiles are defined
|
||||||
|
* This cannot be set in the <code>application.yml</code> 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.
|
||||||
|
*
|
||||||
|
* @param env spring environment
|
||||||
|
* @return profiles
|
||||||
|
*/
|
||||||
|
public static String[] getActiveProfiles(Environment env) {
|
||||||
|
String[] profiles = env.getActiveProfiles();
|
||||||
|
if (profiles.length == 0) {
|
||||||
|
return env.getDefaultProfiles();
|
||||||
|
}
|
||||||
|
return profiles;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
|
||||||
|
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.zalando.problem.ProblemModule;
|
||||||
|
import org.zalando.problem.violations.ConstraintViolationProblemModule;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class JacksonConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support for Java date and time API.
|
||||||
|
* @return the corresponding Jackson module.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public JavaTimeModule javaTimeModule() {
|
||||||
|
return new JavaTimeModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Jdk8Module jdk8TimeModule() {
|
||||||
|
return new Jdk8Module();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Support for Hibernate types in Jackson.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public Hibernate5Module hibernate5Module() {
|
||||||
|
return new Hibernate5Module();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Jackson Afterburner module to speed up serialization/deserialization.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AfterburnerModule afterburnerModule() {
|
||||||
|
return new AfterburnerModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Module for serialization/deserialization of RFC7807 Problem.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
ProblemModule problemModule() {
|
||||||
|
return new ProblemModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Module for serialization/deserialization of ConstraintViolationProblem.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
ConstraintViolationProblemModule constraintViolationProblemModule() {
|
||||||
|
return new ConstraintViolationProblemModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
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.core.env.Environment;
|
||||||
|
import org.springframework.core.task.TaskExecutor;
|
||||||
|
|
||||||
|
import io.github.jhipster.config.JHipsterConstants;
|
||||||
|
import io.github.jhipster.config.liquibase.AsyncSpringLiquibase;
|
||||||
|
import liquibase.integration.spring.SpringLiquibase;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class LiquibaseConfiguration {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class);
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
|
||||||
|
public LiquibaseConfiguration(Environment env) {
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
|
||||||
|
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
|
||||||
|
liquibase.setShouldRun(false);
|
||||||
|
} else {
|
||||||
|
liquibase.setShouldRun(liquibaseProperties.isEnabled());
|
||||||
|
log.debug("Configuring Liquibase");
|
||||||
|
}
|
||||||
|
return liquibase;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import io.github.jhipster.config.locale.AngularCookieLocaleResolver;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
|
import org.springframework.web.servlet.config.annotation.*;
|
||||||
|
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class LocaleConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,154 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
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.boolex.OnMarkerEvaluator;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.classic.spi.LoggerContextListener;
|
||||||
|
import ch.qos.logback.core.Appender;
|
||||||
|
import ch.qos.logback.core.filter.EvaluatorFilter;
|
||||||
|
import ch.qos.logback.core.spi.ContextAwareBase;
|
||||||
|
import ch.qos.logback.core.spi.FilterReply;
|
||||||
|
import net.logstash.logback.appender.LogstashTcpSocketAppender;
|
||||||
|
import net.logstash.logback.encoder.LogstashEncoder;
|
||||||
|
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 static final String LOGSTASH_APPENDER_NAME = "LOGSTASH";
|
||||||
|
|
||||||
|
private static final String ASYNC_LOGSTASH_APPENDER_NAME = "ASYNC_LOGSTASH";
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(LoggingConfiguration.class);
|
||||||
|
|
||||||
|
private LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||||
|
|
||||||
|
private final String appName;
|
||||||
|
|
||||||
|
private final String serverPort;
|
||||||
|
|
||||||
|
private final JHipsterProperties jHipsterProperties;
|
||||||
|
|
||||||
|
public LoggingConfiguration(@Value("${spring.application.name}") String appName, @Value("${server.port}") String serverPort,
|
||||||
|
JHipsterProperties jHipsterProperties) {
|
||||||
|
this.appName = appName;
|
||||||
|
this.serverPort = serverPort;
|
||||||
|
this.jHipsterProperties = jHipsterProperties;
|
||||||
|
if (jHipsterProperties.getLogging().getLogstash().isEnabled()) {
|
||||||
|
addLogstashAppender(context);
|
||||||
|
addContextListener(context);
|
||||||
|
}
|
||||||
|
if (jHipsterProperties.getMetrics().getLogs().isEnabled()) {
|
||||||
|
setMetricsMarkerLogbackFilter(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addContextListener(LoggerContext context) {
|
||||||
|
LogbackLoggerContextListener loggerContextListener = new LogbackLoggerContextListener();
|
||||||
|
loggerContextListener.setContext(context);
|
||||||
|
context.addListener(loggerContextListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLogstashAppender(LoggerContext context) {
|
||||||
|
log.info("Initializing Logstash logging");
|
||||||
|
|
||||||
|
LogstashTcpSocketAppender logstashAppender = new LogstashTcpSocketAppender();
|
||||||
|
logstashAppender.setName(LOGSTASH_APPENDER_NAME);
|
||||||
|
logstashAppender.setContext(context);
|
||||||
|
String customFields = "{\"app_name\":\"" + appName + "\",\"app_port\":\"" + serverPort + "\"}";
|
||||||
|
|
||||||
|
// More documentation is available at: https://github.com/logstash/logstash-logback-encoder
|
||||||
|
LogstashEncoder logstashEncoder = new LogstashEncoder();
|
||||||
|
// Set the Logstash appender config from JHipster properties
|
||||||
|
logstashAppender.addDestinations(new InetSocketAddress(jHipsterProperties.getLogging().getLogstash().getHost(), jHipsterProperties.getLogging().getLogstash().getPort()));
|
||||||
|
|
||||||
|
ShortenedThrowableConverter throwableConverter = new ShortenedThrowableConverter();
|
||||||
|
throwableConverter.setRootCauseFirst(true);
|
||||||
|
logstashEncoder.setThrowableConverter(throwableConverter);
|
||||||
|
logstashEncoder.setCustomFields(customFields);
|
||||||
|
|
||||||
|
logstashAppender.setEncoder(logstashEncoder);
|
||||||
|
logstashAppender.start();
|
||||||
|
|
||||||
|
// Wrap the appender in an Async appender for performance
|
||||||
|
AsyncAppender asyncLogstashAppender = new AsyncAppender();
|
||||||
|
asyncLogstashAppender.setContext(context);
|
||||||
|
asyncLogstashAppender.setName(ASYNC_LOGSTASH_APPENDER_NAME);
|
||||||
|
asyncLogstashAppender.setQueueSize(jHipsterProperties.getLogging().getLogstash().getQueueSize());
|
||||||
|
asyncLogstashAppender.addAppender(logstashAppender);
|
||||||
|
asyncLogstashAppender.start();
|
||||||
|
|
||||||
|
context.getLogger("ROOT").addAppender(asyncLogstashAppender);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure a log filter to remove "metrics" logs from all appenders except the "LOGSTASH" appender
|
||||||
|
private void setMetricsMarkerLogbackFilter(LoggerContext context) {
|
||||||
|
log.info("Filtering metrics logs from all appenders except the {} appender", LOGSTASH_APPENDER_NAME);
|
||||||
|
OnMarkerEvaluator onMarkerMetricsEvaluator = new OnMarkerEvaluator();
|
||||||
|
onMarkerMetricsEvaluator.setContext(context);
|
||||||
|
onMarkerMetricsEvaluator.addMarker("metrics");
|
||||||
|
onMarkerMetricsEvaluator.start();
|
||||||
|
EvaluatorFilter<ILoggingEvent> metricsFilter = new EvaluatorFilter<>();
|
||||||
|
metricsFilter.setContext(context);
|
||||||
|
metricsFilter.setEvaluator(onMarkerMetricsEvaluator);
|
||||||
|
metricsFilter.setOnMatch(FilterReply.DENY);
|
||||||
|
metricsFilter.start();
|
||||||
|
|
||||||
|
for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) {
|
||||||
|
for (Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders(); it.hasNext();) {
|
||||||
|
Appender<ILoggingEvent> appender = it.next();
|
||||||
|
if (!appender.getName().equals(ASYNC_LOGSTASH_APPENDER_NAME)) {
|
||||||
|
log.debug("Filter metrics logs from the {} appender", appender.getName());
|
||||||
|
appender.setContext(context);
|
||||||
|
appender.addFilter(metricsFilter);
|
||||||
|
appender.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.security.*;
|
||||||
|
import org.hostsharing.hsadminng.security.jwt.*;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanInitializationException;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
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.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||||
|
@Import(SecurityProblemSupport.class)
|
||||||
|
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
private final AuthenticationManagerBuilder authenticationManagerBuilder;
|
||||||
|
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
private final TokenProvider tokenProvider;
|
||||||
|
|
||||||
|
private final CorsFilter corsFilter;
|
||||||
|
|
||||||
|
private final SecurityProblemSupport problemSupport;
|
||||||
|
|
||||||
|
public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
|
||||||
|
this.authenticationManagerBuilder = authenticationManagerBuilder;
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
this.tokenProvider = tokenProvider;
|
||||||
|
this.corsFilter = corsFilter;
|
||||||
|
this.problemSupport = problemSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
try {
|
||||||
|
authenticationManagerBuilder
|
||||||
|
.userDetailsService(userDetailsService)
|
||||||
|
.passwordEncoder(passwordEncoder());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BeanInitializationException("Security configuration failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||||
|
return super.authenticationManagerBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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("/i18n/**")
|
||||||
|
.antMatchers("/content/**")
|
||||||
|
.antMatchers("/h2-console/**")
|
||||||
|
.antMatchers("/swagger-ui/index.html")
|
||||||
|
.antMatchers("/test/**");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.csrf()
|
||||||
|
.disable()
|
||||||
|
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.exceptionHandling()
|
||||||
|
.authenticationEntryPoint(problemSupport)
|
||||||
|
.accessDeniedHandler(problemSupport)
|
||||||
|
.and()
|
||||||
|
.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/**").authenticated()
|
||||||
|
.antMatchers("/management/health").permitAll()
|
||||||
|
.antMatchers("/management/info").permitAll()
|
||||||
|
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||||
|
.and()
|
||||||
|
.apply(securityConfigurerAdapter());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private JWTConfigurer securityConfigurerAdapter() {
|
||||||
|
return new JWTConfigurer(tokenProvider);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,170 @@
|
|||||||
|
package org.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import io.github.jhipster.config.JHipsterConstants;
|
||||||
|
import io.github.jhipster.config.JHipsterProperties;
|
||||||
|
import io.github.jhipster.config.h2.H2ConfigurationHelper;
|
||||||
|
import io.github.jhipster.web.filter.CachingHttpHeadersFilter;
|
||||||
|
import io.undertow.UndertowOptions;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||||
|
import org.springframework.boot.web.server.*;
|
||||||
|
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||||
|
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
||||||
|
import javax.servlet.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static java.net.URLDecoder.decode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of web application with Servlet 3.0 APIs.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer<WebServerFactory> {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WebConfigurer.class);
|
||||||
|
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
private final JHipsterProperties jHipsterProperties;
|
||||||
|
|
||||||
|
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<DispatcherType> disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
|
||||||
|
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(WebServerFactory server) {
|
||||||
|
setMimeMappings(server);
|
||||||
|
// When running in an IDE or with ./gradlew bootRun, set location of the static web assets.
|
||||||
|
setLocationForStaticAssets(server);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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) &&
|
||||||
|
server instanceof UndertowServletWebServerFactory) {
|
||||||
|
|
||||||
|
((UndertowServletWebServerFactory) server)
|
||||||
|
.addBuilderCustomizers(builder ->
|
||||||
|
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMimeMappings(WebServerFactory server) {
|
||||||
|
if (server instanceof ConfigurableServletWebServerFactory) {
|
||||||
|
MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
|
||||||
|
// IE issue, see https://github.com/jhipster/generator-jhipster/pull/711
|
||||||
|
mappings.add("html", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase());
|
||||||
|
// CloudFoundry issue, see https://github.com/cloudfoundry/gorouter/issues/64
|
||||||
|
mappings.add("json", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase());
|
||||||
|
ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
|
||||||
|
servletWebServer.setMimeMappings(mappings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLocationForStaticAssets(WebServerFactory server) {
|
||||||
|
if (server instanceof ConfigurableServletWebServerFactory) {
|
||||||
|
ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
|
||||||
|
File root;
|
||||||
|
String prefixPath = resolvePathPrefix();
|
||||||
|
root = new File(prefixPath + "build/www/");
|
||||||
|
if (root.exists() && root.isDirectory()) {
|
||||||
|
servletWebServer.setDocumentRoot(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve path prefix to static resources.
|
||||||
|
*/
|
||||||
|
private String resolvePathPrefix() {
|
||||||
|
String fullExecutablePath;
|
||||||
|
try {
|
||||||
|
fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8.name());
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
/* try without decoding if this ever happens */
|
||||||
|
fullExecutablePath = this.getClass().getResource("").getPath();
|
||||||
|
}
|
||||||
|
String rootPath = Paths.get(".").toUri().normalize().getPath();
|
||||||
|
String extractedPath = fullExecutablePath.replace(rootPath, "");
|
||||||
|
int extractionEndIndex = extractedPath.indexOf("build/");
|
||||||
|
if (extractionEndIndex <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return extractedPath.substring(0, extractionEndIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the caching HTTP Headers Filter.
|
||||||
|
*/
|
||||||
|
private void initCachingHttpHeadersFilter(ServletContext servletContext,
|
||||||
|
EnumSet<DispatcherType> disps) {
|
||||||
|
log.debug("Registering Caching HTTP Headers Filter");
|
||||||
|
FilterRegistration.Dynamic cachingHttpHeadersFilter =
|
||||||
|
servletContext.addFilter("cachingHttpHeadersFilter",
|
||||||
|
new CachingHttpHeadersFilter(jHipsterProperties));
|
||||||
|
|
||||||
|
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/i18n/*");
|
||||||
|
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/content/*");
|
||||||
|
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/app/*");
|
||||||
|
cachingHttpHeadersFilter.setAsyncSupported(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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("/management/**", config);
|
||||||
|
source.registerCorsConfiguration("/v2/api-docs", config);
|
||||||
|
}
|
||||||
|
return new CorsFilter(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes H2 console.
|
||||||
|
*/
|
||||||
|
private void initH2Console(ServletContext servletContext) {
|
||||||
|
log.debug("Initialize H2 console");
|
||||||
|
H2ConfigurationHelper.initH2Console(servletContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package org.hostsharing.hsadminng.config.audit;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.PersistentAuditEvent;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
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<AuditEvent> convertToAuditEvent(Iterable<PersistentAuditEvent> persistentAuditEvents) {
|
||||||
|
if (persistentAuditEvents == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<AuditEvent> 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) {
|
||||||
|
if (persistentAuditEvent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new AuditEvent(persistentAuditEvent.getAuditEventDate(), 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<String, Object> convertDataToObjects(Map<String, String> data) {
|
||||||
|
Map<String, Object> results = new HashMap<>();
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
for (Map.Entry<String, String> 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<String, String> convertDataToStrings(Map<String, Object> data) {
|
||||||
|
Map<String, String> results = new HashMap<>();
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||||
|
// Extract the data that will be saved.
|
||||||
|
if (entry.getValue() instanceof WebAuthenticationDetails) {
|
||||||
|
WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) entry.getValue();
|
||||||
|
results.put("remoteAddress", authenticationDetails.getRemoteAddress());
|
||||||
|
results.put("sessionId", authenticationDetails.getSessionId());
|
||||||
|
} else {
|
||||||
|
results.put(entry.getKey(), Objects.toString(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Audit specific code.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.config.audit;
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Spring Framework configuration files.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.config;
|
@ -0,0 +1,79 @@
|
|||||||
|
package org.hostsharing.hsadminng.domain;
|
||||||
|
|
||||||
|
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.io.Serializable;
|
||||||
|
import java.time.Instant;
|
||||||
|
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", updatable = false)
|
||||||
|
@JsonIgnore
|
||||||
|
private Instant createdDate = Instant.now();
|
||||||
|
|
||||||
|
@LastModifiedBy
|
||||||
|
@Column(name = "last_modified_by", length = 50)
|
||||||
|
@JsonIgnore
|
||||||
|
private String lastModifiedBy;
|
||||||
|
|
||||||
|
@LastModifiedDate
|
||||||
|
@Column(name = "last_modified_date")
|
||||||
|
@JsonIgnore
|
||||||
|
private Instant lastModifiedDate = Instant.now();
|
||||||
|
|
||||||
|
public String getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(String createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getCreatedDate() {
|
||||||
|
return createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedDate(Instant createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastModifiedBy() {
|
||||||
|
return lastModifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastModifiedBy(String lastModifiedBy) {
|
||||||
|
this.lastModifiedBy = lastModifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getLastModifiedDate() {
|
||||||
|
return lastModifiedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastModifiedDate(Instant lastModifiedDate) {
|
||||||
|
this.lastModifiedDate = lastModifiedDate;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package org.hostsharing.hsadminng.domain;
|
||||||
|
|
||||||
|
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")
|
||||||
|
public class Authority implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Size(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;
|
||||||
|
|
||||||
|
return !(name != null ? !name.equals(authority.name) : authority.name != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name != null ? name.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Authority{" +
|
||||||
|
"name='" + name + '\'' +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
package org.hostsharing.hsadminng.domain;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Objects;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
@SequenceGenerator(name = "sequenceGenerator")
|
||||||
|
@Column(name = "event_id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String principal;
|
||||||
|
|
||||||
|
@Column(name = "event_date")
|
||||||
|
private Instant 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<String, String> 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 Instant getAuditEventDate() {
|
||||||
|
return auditEventDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuditEventDate(Instant auditEventDate) {
|
||||||
|
this.auditEventDate = auditEventDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuditEventType() {
|
||||||
|
return auditEventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuditEventType(String auditEventType) {
|
||||||
|
this.auditEventType = auditEventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(Map<String, String> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistentAuditEvent persistentAuditEvent = (PersistentAuditEvent) o;
|
||||||
|
return !(persistentAuditEvent.getId() == null || getId() == null) && Objects.equals(getId(), persistentAuditEvent.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PersistentAuditEvent{" +
|
||||||
|
"principal='" + principal + '\'' +
|
||||||
|
", auditEventDate=" + auditEventDate +
|
||||||
|
", auditEventType='" + auditEventType + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
232
src/main/java/org/hostsharing/hsadminng/domain/User.java
Normal file
232
src/main/java/org/hostsharing/hsadminng/domain/User.java
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
package org.hostsharing.hsadminng.domain;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.Constants;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hibernate.annotations.BatchSize;
|
||||||
|
import javax.validation.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.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "jhi_user")
|
||||||
|
|
||||||
|
public class User extends AbstractAuditingEntity implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||||
|
@SequenceGenerator(name = "sequenceGenerator")
|
||||||
|
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, nullable = false)
|
||||||
|
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(min = 5, max = 254)
|
||||||
|
@Column(length = 254, unique = true)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Column(nullable = false)
|
||||||
|
private boolean activated = false;
|
||||||
|
|
||||||
|
@Size(min = 2, max = 6)
|
||||||
|
@Column(name = "lang_key", length = 6)
|
||||||
|
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)
|
||||||
|
@JsonIgnore
|
||||||
|
private String resetKey;
|
||||||
|
|
||||||
|
@Column(name = "reset_date")
|
||||||
|
private Instant resetDate = null;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@ManyToMany
|
||||||
|
@JoinTable(
|
||||||
|
name = "jhi_user_authority",
|
||||||
|
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
|
||||||
|
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})
|
||||||
|
|
||||||
|
@BatchSize(size = 20)
|
||||||
|
private Set<Authority> 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 = StringUtils.lowerCase(login, 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 Instant getResetDate() {
|
||||||
|
return resetDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResetDate(Instant resetDate) {
|
||||||
|
this.resetDate = resetDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLangKey() {
|
||||||
|
return langKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLangKey(String langKey) {
|
||||||
|
this.langKey = langKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Authority> getAuthorities() {
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorities(Set<Authority> 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;
|
||||||
|
return !(user.getId() == null || getId() == null) && Objects.equals(getId(), user.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "User{" +
|
||||||
|
"login='" + login + '\'' +
|
||||||
|
", firstName='" + firstName + '\'' +
|
||||||
|
", lastName='" + lastName + '\'' +
|
||||||
|
", email='" + email + '\'' +
|
||||||
|
", imageUrl='" + imageUrl + '\'' +
|
||||||
|
", activated='" + activated + '\'' +
|
||||||
|
", langKey='" + langKey + '\'' +
|
||||||
|
", activationKey='" + activationKey + '\'' +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* JPA domain objects.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.domain;
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.hostsharing.hsadminng.repository;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.Authority;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the Authority entity.
|
||||||
|
*/
|
||||||
|
public interface AuthorityRepository extends JpaRepository<Authority, String> {
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package org.hostsharing.hsadminng.repository;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.Constants;
|
||||||
|
import org.hostsharing.hsadminng.config.audit.AuditEventConverter;
|
||||||
|
import org.hostsharing.hsadminng.domain.PersistentAuditEvent;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
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.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of Spring Boot's AuditEventRepository.
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public class CustomAuditEventRepository implements AuditEventRepository {
|
||||||
|
|
||||||
|
private static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be the same as in Liquibase migration.
|
||||||
|
*/
|
||||||
|
protected static final int EVENT_DATA_COLUMN_MAX_LENGTH = 255;
|
||||||
|
|
||||||
|
private final PersistenceAuditEventRepository persistenceAuditEventRepository;
|
||||||
|
|
||||||
|
private final AuditEventConverter auditEventConverter;
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
public CustomAuditEventRepository(PersistenceAuditEventRepository persistenceAuditEventRepository,
|
||||||
|
AuditEventConverter auditEventConverter) {
|
||||||
|
|
||||||
|
this.persistenceAuditEventRepository = persistenceAuditEventRepository;
|
||||||
|
this.auditEventConverter = auditEventConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AuditEvent> find(String principal, Instant after, String type) {
|
||||||
|
Iterable<PersistentAuditEvent> persistentAuditEvents =
|
||||||
|
persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfterAndAuditEventType(principal, after, 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());
|
||||||
|
persistentAuditEvent.setAuditEventDate(event.getTimestamp());
|
||||||
|
Map<String, String> eventData = auditEventConverter.convertDataToStrings(event.getData());
|
||||||
|
persistentAuditEvent.setData(truncate(eventData));
|
||||||
|
persistenceAuditEventRepository.save(persistentAuditEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate event data that might exceed column length.
|
||||||
|
*/
|
||||||
|
private Map<String, String> truncate(Map<String, String> data) {
|
||||||
|
Map<String, String> results = new HashMap<>();
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
for (Map.Entry<String, String> entry : data.entrySet()) {
|
||||||
|
String value = entry.getValue();
|
||||||
|
if (value != null) {
|
||||||
|
int length = value.length();
|
||||||
|
if (length > EVENT_DATA_COLUMN_MAX_LENGTH) {
|
||||||
|
value = value.substring(0, EVENT_DATA_COLUMN_MAX_LENGTH);
|
||||||
|
log.warn("Event data for {} too long ({}) has been truncated to {}. Consider increasing column width.",
|
||||||
|
entry.getKey(), length, EVENT_DATA_COLUMN_MAX_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.put(entry.getKey(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package org.hostsharing.hsadminng.repository;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.PersistentAuditEvent;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the PersistentAuditEvent entity.
|
||||||
|
*/
|
||||||
|
public interface PersistenceAuditEventRepository extends JpaRepository<PersistentAuditEvent, Long> {
|
||||||
|
|
||||||
|
List<PersistentAuditEvent> findByPrincipal(String principal);
|
||||||
|
|
||||||
|
List<PersistentAuditEvent> findByAuditEventDateAfter(Instant after);
|
||||||
|
|
||||||
|
List<PersistentAuditEvent> findByPrincipalAndAuditEventDateAfter(String principal, Instant after);
|
||||||
|
|
||||||
|
List<PersistentAuditEvent> findByPrincipalAndAuditEventDateAfterAndAuditEventType(String principal, Instant after, String type);
|
||||||
|
|
||||||
|
Page<PersistentAuditEvent> findAllByAuditEventDateBetween(Instant fromDate, Instant toDate, Pageable pageable);
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package org.hostsharing.hsadminng.repository;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.User;
|
||||||
|
|
||||||
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
|
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 org.springframework.stereotype.Repository;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data JPA repository for the User entity.
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
|
||||||
|
String USERS_BY_LOGIN_CACHE = "usersByLogin";
|
||||||
|
|
||||||
|
String USERS_BY_EMAIL_CACHE = "usersByEmail";
|
||||||
|
|
||||||
|
Optional<User> findOneByActivationKey(String activationKey);
|
||||||
|
|
||||||
|
List<User> findAllByActivatedIsFalseAndCreatedDateBefore(Instant dateTime);
|
||||||
|
|
||||||
|
Optional<User> findOneByResetKey(String resetKey);
|
||||||
|
|
||||||
|
Optional<User> findOneByEmailIgnoreCase(String email);
|
||||||
|
|
||||||
|
Optional<User> findOneByLogin(String login);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = "authorities")
|
||||||
|
Optional<User> findOneWithAuthoritiesById(Long id);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = "authorities")
|
||||||
|
@Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
|
||||||
|
Optional<User> findOneWithAuthoritiesByLogin(String login);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = "authorities")
|
||||||
|
@Cacheable(cacheNames = USERS_BY_EMAIL_CACHE)
|
||||||
|
Optional<User> findOneWithAuthoritiesByEmail(String email);
|
||||||
|
|
||||||
|
Page<User> findAllByLoginNot(Pageable pageable, String login);
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Spring Data JPA repositories.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.repository;
|
@ -0,0 +1,16 @@
|
|||||||
|
package org.hostsharing.hsadminng.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() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package org.hostsharing.hsadminng.security;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.User;
|
||||||
|
import org.hostsharing.hsadminng.repository.UserRepository;
|
||||||
|
import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (new EmailValidator().isValid(login, null)) {
|
||||||
|
return userRepository.findOneWithAuthoritiesByEmail(login)
|
||||||
|
.map(user -> createSpringSecurityUser(login, user))
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database"));
|
||||||
|
}
|
||||||
|
|
||||||
|
String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
|
||||||
|
return userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin)
|
||||||
|
.map(user -> createSpringSecurityUser(lowercaseLogin, user))
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {
|
||||||
|
if (!user.getActivated()) {
|
||||||
|
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
|
||||||
|
}
|
||||||
|
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
|
||||||
|
.map(authority -> new SimpleGrantedAuthority(authority.getName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new org.springframework.security.core.userdetails.User(user.getLogin(),
|
||||||
|
user.getPassword(),
|
||||||
|
grantedAuthorities);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
package org.hostsharing.hsadminng.security;
|
||||||
|
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Optional<String> getCurrentUserLogin() {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
return Optional.ofNullable(securityContext.getAuthentication())
|
||||||
|
.map(authentication -> {
|
||||||
|
if (authentication.getPrincipal() instanceof UserDetails) {
|
||||||
|
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
|
||||||
|
return springSecurityUser.getUsername();
|
||||||
|
} else if (authentication.getPrincipal() instanceof String) {
|
||||||
|
return (String) authentication.getPrincipal();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the JWT of the current user.
|
||||||
|
*
|
||||||
|
* @return the JWT of the current user
|
||||||
|
*/
|
||||||
|
public static Optional<String> getCurrentUserJWT() {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
return Optional.ofNullable(securityContext.getAuthentication())
|
||||||
|
.filter(authentication -> authentication.getCredentials() instanceof String)
|
||||||
|
.map(authentication -> (String) authentication.getCredentials());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user is authenticated.
|
||||||
|
*
|
||||||
|
* @return true if the user is authenticated, false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean isAuthenticated() {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
return Optional.ofNullable(securityContext.getAuthentication())
|
||||||
|
.map(authentication -> authentication.getAuthorities().stream()
|
||||||
|
.noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS)))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the current user has a specific authority (security role).
|
||||||
|
* <p>
|
||||||
|
* 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();
|
||||||
|
return Optional.ofNullable(securityContext.getAuthentication())
|
||||||
|
.map(authentication -> authentication.getAuthorities().stream()
|
||||||
|
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.hostsharing.hsadminng.security;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.Constants;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.AuditorAware;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of AuditorAware based on Spring Security.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SpringSecurityAuditorAware implements AuditorAware<String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> getCurrentAuditor() {
|
||||||
|
return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM_ACCOUNT));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package org.hostsharing.hsadminng.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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.hostsharing.hsadminng.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<DefaultSecurityFilterChain, HttpSecurity> {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package org.hostsharing.hsadminng.security.jwt;
|
||||||
|
|
||||||
|
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 javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is
|
||||||
|
* found.
|
||||||
|
*/
|
||||||
|
public class JWTFilter extends GenericFilterBean {
|
||||||
|
|
||||||
|
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
|
|
||||||
|
private TokenProvider tokenProvider;
|
||||||
|
|
||||||
|
public JWTFilter(TokenProvider tokenProvider) {
|
||||||
|
this.tokenProvider = tokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveToken(HttpServletRequest request){
|
||||||
|
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
|
||||||
|
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||||
|
return bearerToken.substring(7);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
package org.hostsharing.hsadminng.security.jwt;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.Key;
|
||||||
|
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 org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import io.github.jhipster.config.JHipsterProperties;
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import io.jsonwebtoken.io.Decoders;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TokenProvider {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(TokenProvider.class);
|
||||||
|
|
||||||
|
private static final String AUTHORITIES_KEY = "auth";
|
||||||
|
|
||||||
|
private Key key;
|
||||||
|
|
||||||
|
private long tokenValidityInMilliseconds;
|
||||||
|
|
||||||
|
private long tokenValidityInMillisecondsForRememberMe;
|
||||||
|
|
||||||
|
private final JHipsterProperties jHipsterProperties;
|
||||||
|
|
||||||
|
public TokenProvider(JHipsterProperties jHipsterProperties) {
|
||||||
|
this.jHipsterProperties = jHipsterProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
byte[] keyBytes;
|
||||||
|
String secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret();
|
||||||
|
if (!StringUtils.isEmpty(secret)) {
|
||||||
|
log.warn("Warning: the JWT key used is not Base64-encoded. " +
|
||||||
|
"We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security.");
|
||||||
|
keyBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||||
|
} else {
|
||||||
|
log.debug("Using a Base64-encoded JWT secret key");
|
||||||
|
keyBytes = Decoders.BASE64.decode(jHipsterProperties.getSecurity().getAuthentication().getJwt().getBase64Secret());
|
||||||
|
}
|
||||||
|
this.key = Keys.hmacShaKeyFor(keyBytes);
|
||||||
|
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(key, SignatureAlgorithm.HS512)
|
||||||
|
.setExpiration(validity)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Authentication getAuthentication(String token) {
|
||||||
|
Claims claims = Jwts.parser()
|
||||||
|
.setSigningKey(key)
|
||||||
|
.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, token, authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateToken(String authToken) {
|
||||||
|
try {
|
||||||
|
Jwts.parser().setSigningKey(key).parseClaimsJws(authToken);
|
||||||
|
return true;
|
||||||
|
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
|
||||||
|
log.info("Invalid JWT signature.");
|
||||||
|
log.trace("Invalid JWT signature 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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Spring Security configuration.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.security;
|
@ -0,0 +1,51 @@
|
|||||||
|
package org.hostsharing.hsadminng.service;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.audit.AuditEventConverter;
|
||||||
|
import org.hostsharing.hsadminng.repository.PersistenceAuditEventRepository;
|
||||||
|
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.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for managing audit events.
|
||||||
|
* <p>
|
||||||
|
* 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<AuditEvent> findAll(Pageable pageable) {
|
||||||
|
return persistenceAuditEventRepository.findAll(pageable)
|
||||||
|
.map(auditEventConverter::convertToAuditEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page<AuditEvent> findByDates(Instant fromDate, Instant toDate, Pageable pageable) {
|
||||||
|
return persistenceAuditEventRepository.findAllByAuditEventDateBetween(fromDate, toDate, pageable)
|
||||||
|
.map(auditEventConverter::convertToAuditEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<AuditEvent> find(Long id) {
|
||||||
|
return Optional.ofNullable(persistenceAuditEventRepository.findById(id))
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.map(auditEventConverter::convertToAuditEvent);
|
||||||
|
}
|
||||||
|
}
|
105
src/main/java/org/hostsharing/hsadminng/service/MailService.java
Normal file
105
src/main/java/org/hostsharing/hsadminng/service/MailService.java
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package org.hostsharing.hsadminng.service;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.User;
|
||||||
|
|
||||||
|
import io.github.jhipster.config.JHipsterProperties;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.mail.internet.MimeMessage;
|
||||||
|
|
||||||
|
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.spring5.SpringTemplateEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for sending emails.
|
||||||
|
* <p>
|
||||||
|
* We use the @Async annotation to send emails 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 email[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, StandardCharsets.UTF_8.name());
|
||||||
|
message.setTo(to);
|
||||||
|
message.setFrom(jHipsterProperties.getMail().getFrom());
|
||||||
|
message.setSubject(subject);
|
||||||
|
message.setText(content, isHtml);
|
||||||
|
javaMailSender.send(mimeMessage);
|
||||||
|
log.debug("Sent email to User '{}'", to);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.warn("Email could not be sent to user '{}'", to, e);
|
||||||
|
} else {
|
||||||
|
log.warn("Email could not be sent to user '{}': {}", to, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendEmailFromTemplate(User user, String templateName, String titleKey) {
|
||||||
|
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(templateName, context);
|
||||||
|
String subject = messageSource.getMessage(titleKey, null, locale);
|
||||||
|
sendEmail(user.getEmail(), subject, content, false, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendActivationEmail(User user) {
|
||||||
|
log.debug("Sending activation email to '{}'", user.getEmail());
|
||||||
|
sendEmailFromTemplate(user, "mail/activationEmail", "email.activation.title");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendCreationEmail(User user) {
|
||||||
|
log.debug("Sending creation email to '{}'", user.getEmail());
|
||||||
|
sendEmailFromTemplate(user, "mail/creationEmail", "email.activation.title");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void sendPasswordResetMail(User user) {
|
||||||
|
log.debug("Sending password reset email to '{}'", user.getEmail());
|
||||||
|
sendEmailFromTemplate(user, "mail/passwordResetEmail", "email.reset.title");
|
||||||
|
}
|
||||||
|
}
|
294
src/main/java/org/hostsharing/hsadminng/service/UserService.java
Normal file
294
src/main/java/org/hostsharing/hsadminng/service/UserService.java
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
package org.hostsharing.hsadminng.service;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.Constants;
|
||||||
|
import org.hostsharing.hsadminng.domain.Authority;
|
||||||
|
import org.hostsharing.hsadminng.domain.User;
|
||||||
|
import org.hostsharing.hsadminng.repository.AuthorityRepository;
|
||||||
|
import org.hostsharing.hsadminng.repository.UserRepository;
|
||||||
|
import org.hostsharing.hsadminng.security.AuthoritiesConstants;
|
||||||
|
import org.hostsharing.hsadminng.security.SecurityUtils;
|
||||||
|
import org.hostsharing.hsadminng.service.dto.UserDTO;
|
||||||
|
import org.hostsharing.hsadminng.service.util.RandomUtil;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.errors.*;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
|
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.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
private final CacheManager cacheManager;
|
||||||
|
|
||||||
|
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthorityRepository authorityRepository, CacheManager cacheManager) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.authorityRepository = authorityRepository;
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> 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);
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
log.debug("Activated user: {}", user);
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> completePasswordReset(String newPassword, String key) {
|
||||||
|
log.debug("Reset user password for reset key {}", key);
|
||||||
|
return userRepository.findOneByResetKey(key)
|
||||||
|
.filter(user -> user.getResetDate().isAfter(Instant.now().minusSeconds(86400)))
|
||||||
|
.map(user -> {
|
||||||
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
|
user.setResetKey(null);
|
||||||
|
user.setResetDate(null);
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> requestPasswordReset(String mail) {
|
||||||
|
return userRepository.findOneByEmailIgnoreCase(mail)
|
||||||
|
.filter(User::getActivated)
|
||||||
|
.map(user -> {
|
||||||
|
user.setResetKey(RandomUtil.generateResetKey());
|
||||||
|
user.setResetDate(Instant.now());
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public User registerUser(UserDTO userDTO, String password) {
|
||||||
|
userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).ifPresent(existingUser -> {
|
||||||
|
boolean removed = removeNonActivatedUser(existingUser);
|
||||||
|
if (!removed) {
|
||||||
|
throw new LoginAlreadyUsedException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).ifPresent(existingUser -> {
|
||||||
|
boolean removed = removeNonActivatedUser(existingUser);
|
||||||
|
if (!removed) {
|
||||||
|
throw new EmailAlreadyUsedException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
User newUser = new User();
|
||||||
|
String encryptedPassword = passwordEncoder.encode(password);
|
||||||
|
newUser.setLogin(userDTO.getLogin().toLowerCase());
|
||||||
|
// new user gets initially a generated password
|
||||||
|
newUser.setPassword(encryptedPassword);
|
||||||
|
newUser.setFirstName(userDTO.getFirstName());
|
||||||
|
newUser.setLastName(userDTO.getLastName());
|
||||||
|
newUser.setEmail(userDTO.getEmail().toLowerCase());
|
||||||
|
newUser.setImageUrl(userDTO.getImageUrl());
|
||||||
|
newUser.setLangKey(userDTO.getLangKey());
|
||||||
|
// new user is not active
|
||||||
|
newUser.setActivated(false);
|
||||||
|
// new user gets registration key
|
||||||
|
newUser.setActivationKey(RandomUtil.generateActivationKey());
|
||||||
|
Set<Authority> authorities = new HashSet<>();
|
||||||
|
authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add);
|
||||||
|
newUser.setAuthorities(authorities);
|
||||||
|
userRepository.save(newUser);
|
||||||
|
this.clearUserCaches(newUser);
|
||||||
|
log.debug("Created Information for User: {}", newUser);
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean removeNonActivatedUser(User existingUser){
|
||||||
|
if (existingUser.getActivated()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
userRepository.delete(existingUser);
|
||||||
|
userRepository.flush();
|
||||||
|
this.clearUserCaches(existingUser);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User createUser(UserDTO userDTO) {
|
||||||
|
User user = new User();
|
||||||
|
user.setLogin(userDTO.getLogin().toLowerCase());
|
||||||
|
user.setFirstName(userDTO.getFirstName());
|
||||||
|
user.setLastName(userDTO.getLastName());
|
||||||
|
user.setEmail(userDTO.getEmail().toLowerCase());
|
||||||
|
user.setImageUrl(userDTO.getImageUrl());
|
||||||
|
if (userDTO.getLangKey() == null) {
|
||||||
|
user.setLangKey(Constants.DEFAULT_LANGUAGE); // default language
|
||||||
|
} else {
|
||||||
|
user.setLangKey(userDTO.getLangKey());
|
||||||
|
}
|
||||||
|
String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword());
|
||||||
|
user.setPassword(encryptedPassword);
|
||||||
|
user.setResetKey(RandomUtil.generateResetKey());
|
||||||
|
user.setResetDate(Instant.now());
|
||||||
|
user.setActivated(true);
|
||||||
|
if (userDTO.getAuthorities() != null) {
|
||||||
|
Set<Authority> authorities = userDTO.getAuthorities().stream()
|
||||||
|
.map(authorityRepository::findById)
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
user.setAuthorities(authorities);
|
||||||
|
}
|
||||||
|
userRepository.save(user);
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
log.debug("Created Information for User: {}", user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update basic information (first name, last name, email, language) for the current user.
|
||||||
|
*
|
||||||
|
* @param firstName first name of user
|
||||||
|
* @param lastName last name of user
|
||||||
|
* @param email email id of user
|
||||||
|
* @param langKey language key
|
||||||
|
* @param imageUrl image URL of user
|
||||||
|
*/
|
||||||
|
public void updateUser(String firstName, String lastName, String email, String langKey, String imageUrl) {
|
||||||
|
SecurityUtils.getCurrentUserLogin()
|
||||||
|
.flatMap(userRepository::findOneByLogin)
|
||||||
|
.ifPresent(user -> {
|
||||||
|
user.setFirstName(firstName);
|
||||||
|
user.setLastName(lastName);
|
||||||
|
user.setEmail(email.toLowerCase());
|
||||||
|
user.setLangKey(langKey);
|
||||||
|
user.setImageUrl(imageUrl);
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
log.debug("Changed Information for User: {}", user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all information for a specific user, and return the modified user.
|
||||||
|
*
|
||||||
|
* @param userDTO user to update
|
||||||
|
* @return updated user
|
||||||
|
*/
|
||||||
|
public Optional<UserDTO> updateUser(UserDTO userDTO) {
|
||||||
|
return Optional.of(userRepository
|
||||||
|
.findById(userDTO.getId()))
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.map(user -> {
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
user.setLogin(userDTO.getLogin().toLowerCase());
|
||||||
|
user.setFirstName(userDTO.getFirstName());
|
||||||
|
user.setLastName(userDTO.getLastName());
|
||||||
|
user.setEmail(userDTO.getEmail().toLowerCase());
|
||||||
|
user.setImageUrl(userDTO.getImageUrl());
|
||||||
|
user.setActivated(userDTO.isActivated());
|
||||||
|
user.setLangKey(userDTO.getLangKey());
|
||||||
|
Set<Authority> managedAuthorities = user.getAuthorities();
|
||||||
|
managedAuthorities.clear();
|
||||||
|
userDTO.getAuthorities().stream()
|
||||||
|
.map(authorityRepository::findById)
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::get)
|
||||||
|
.forEach(managedAuthorities::add);
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
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);
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
log.debug("Deleted User: {}", user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changePassword(String currentClearTextPassword, String newPassword) {
|
||||||
|
SecurityUtils.getCurrentUserLogin()
|
||||||
|
.flatMap(userRepository::findOneByLogin)
|
||||||
|
.ifPresent(user -> {
|
||||||
|
String currentEncryptedPassword = user.getPassword();
|
||||||
|
if (!passwordEncoder.matches(currentClearTextPassword, currentEncryptedPassword)) {
|
||||||
|
throw new InvalidPasswordException();
|
||||||
|
}
|
||||||
|
String encryptedPassword = passwordEncoder.encode(newPassword);
|
||||||
|
user.setPassword(encryptedPassword);
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
log.debug("Changed password for User: {}", user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Page<UserDTO> getAllManagedUsers(Pageable pageable) {
|
||||||
|
return userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER).map(UserDTO::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<User> getUserWithAuthoritiesByLogin(String login) {
|
||||||
|
return userRepository.findOneWithAuthoritiesByLogin(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<User> getUserWithAuthorities(Long id) {
|
||||||
|
return userRepository.findOneWithAuthoritiesById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<User> getUserWithAuthorities() {
|
||||||
|
return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not activated users should be automatically deleted after 3 days.
|
||||||
|
* <p>
|
||||||
|
* This is scheduled to get fired everyday, at 01:00 (am).
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0 0 1 * * ?")
|
||||||
|
public void removeNotActivatedUsers() {
|
||||||
|
userRepository
|
||||||
|
.findAllByActivatedIsFalseAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS))
|
||||||
|
.forEach(user -> {
|
||||||
|
log.debug("Deleting not activated user {}", user.getLogin());
|
||||||
|
userRepository.delete(user);
|
||||||
|
this.clearUserCaches(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a list of all the authorities
|
||||||
|
*/
|
||||||
|
public List<String> getAuthorities() {
|
||||||
|
return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearUserCaches(User user) {
|
||||||
|
Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)).evict(user.getLogin());
|
||||||
|
Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evict(user.getEmail());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.hostsharing.hsadminng.service.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DTO representing a password change required data - current and new password.
|
||||||
|
*/
|
||||||
|
public class PasswordChangeDTO {
|
||||||
|
private String currentPassword;
|
||||||
|
private String newPassword;
|
||||||
|
|
||||||
|
public PasswordChangeDTO() {
|
||||||
|
// Empty constructor needed for Jackson.
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordChangeDTO(String currentPassword, String newPassword) {
|
||||||
|
this.currentPassword = currentPassword;
|
||||||
|
this.newPassword = newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentPassword() {
|
||||||
|
|
||||||
|
return currentPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentPassword(String currentPassword) {
|
||||||
|
this.currentPassword = currentPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewPassword() {
|
||||||
|
return newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewPassword(String newPassword) {
|
||||||
|
this.newPassword = newPassword;
|
||||||
|
}
|
||||||
|
}
|
199
src/main/java/org/hostsharing/hsadminng/service/dto/UserDTO.java
Normal file
199
src/main/java/org/hostsharing/hsadminng/service/dto/UserDTO.java
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package org.hostsharing.hsadminng.service.dto;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.Constants;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.Authority;
|
||||||
|
import org.hostsharing.hsadminng.domain.User;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Email;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DTO representing a user, with his authorities.
|
||||||
|
*/
|
||||||
|
public class UserDTO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@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 = 254)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Size(max = 256)
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
private boolean activated = false;
|
||||||
|
|
||||||
|
@Size(min = 2, max = 6)
|
||||||
|
private String langKey;
|
||||||
|
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
private Instant createdDate;
|
||||||
|
|
||||||
|
private String lastModifiedBy;
|
||||||
|
|
||||||
|
private Instant lastModifiedDate;
|
||||||
|
|
||||||
|
private Set<String> authorities;
|
||||||
|
|
||||||
|
public UserDTO() {
|
||||||
|
// Empty constructor needed for Jackson.
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDTO(User user) {
|
||||||
|
this.id = user.getId();
|
||||||
|
this.login = user.getLogin();
|
||||||
|
this.firstName = user.getFirstName();
|
||||||
|
this.lastName = user.getLastName();
|
||||||
|
this.email = user.getEmail();
|
||||||
|
this.activated = user.getActivated();
|
||||||
|
this.imageUrl = user.getImageUrl();
|
||||||
|
this.langKey = user.getLangKey();
|
||||||
|
this.createdBy = user.getCreatedBy();
|
||||||
|
this.createdDate = user.getCreatedDate();
|
||||||
|
this.lastModifiedBy = user.getLastModifiedBy();
|
||||||
|
this.lastModifiedDate = user.getLastModifiedDate();
|
||||||
|
this.authorities = user.getAuthorities().stream()
|
||||||
|
.map(Authority::getName)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 isActivated() {
|
||||||
|
return activated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivated(boolean activated) {
|
||||||
|
this.activated = activated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLangKey() {
|
||||||
|
return langKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLangKey(String langKey) {
|
||||||
|
this.langKey = langKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(String createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getCreatedDate() {
|
||||||
|
return createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedDate(Instant createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastModifiedBy() {
|
||||||
|
return lastModifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastModifiedBy(String lastModifiedBy) {
|
||||||
|
this.lastModifiedBy = lastModifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getLastModifiedDate() {
|
||||||
|
return lastModifiedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastModifiedDate(Instant lastModifiedDate) {
|
||||||
|
this.lastModifiedDate = lastModifiedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getAuthorities() {
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorities(Set<String> authorities) {
|
||||||
|
this.authorities = 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 +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Data Transfer Objects.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.service.dto;
|
@ -0,0 +1,81 @@
|
|||||||
|
package org.hostsharing.hsadminng.service.mapper;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.Authority;
|
||||||
|
import org.hostsharing.hsadminng.domain.User;
|
||||||
|
import org.hostsharing.hsadminng.service.dto.UserDTO;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapper for the entity User and its DTO called UserDTO.
|
||||||
|
*
|
||||||
|
* Normal mappers are generated using MapStruct, this one is hand-coded as MapStruct
|
||||||
|
* support is still in beta, and requires a manual step with an IDE.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class UserMapper {
|
||||||
|
|
||||||
|
public List<UserDTO> usersToUserDTOs(List<User> users) {
|
||||||
|
return users.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(this::userToUserDTO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDTO userToUserDTO(User user) {
|
||||||
|
return new UserDTO(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<User> userDTOsToUsers(List<UserDTO> userDTOs) {
|
||||||
|
return userDTOs.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(this::userDTOToUser)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public User userDTOToUser(UserDTO userDTO) {
|
||||||
|
if (userDTO == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
User user = new User();
|
||||||
|
user.setId(userDTO.getId());
|
||||||
|
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<Authority> authorities = this.authoritiesFromStrings(userDTO.getAuthorities());
|
||||||
|
user.setAuthorities(authorities);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Set<Authority> authoritiesFromStrings(Set<String> authoritiesAsString) {
|
||||||
|
Set<Authority> authorities = new HashSet<>();
|
||||||
|
|
||||||
|
if(authoritiesAsString != null){
|
||||||
|
authorities = authoritiesAsString.stream().map(string -> {
|
||||||
|
Authority auth = new Authority();
|
||||||
|
auth.setName(string);
|
||||||
|
return auth;
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User userFromId(Long id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
User user = new User();
|
||||||
|
user.setId(id);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* MapStruct mappers for mapping domain objects and Data Transfer Objects.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.service.mapper;
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Service layer beans.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.service;
|
@ -0,0 +1,41 @@
|
|||||||
|
package org.hostsharing.hsadminng.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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,179 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest;
|
||||||
|
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.domain.User;
|
||||||
|
import org.hostsharing.hsadminng.repository.UserRepository;
|
||||||
|
import org.hostsharing.hsadminng.security.SecurityUtils;
|
||||||
|
import org.hostsharing.hsadminng.service.MailService;
|
||||||
|
import org.hostsharing.hsadminng.service.UserService;
|
||||||
|
import org.hostsharing.hsadminng.service.dto.PasswordChangeDTO;
|
||||||
|
import org.hostsharing.hsadminng.service.dto.UserDTO;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.errors.*;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.vm.KeyAndPasswordVM;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.vm.ManagedUserVM;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
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
|
||||||
|
* @throws InvalidPasswordException 400 (Bad Request) if the password is incorrect
|
||||||
|
* @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already used
|
||||||
|
* @throws LoginAlreadyUsedException 400 (Bad Request) if the login is already used
|
||||||
|
*/
|
||||||
|
@PostMapping("/register")
|
||||||
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
|
public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) {
|
||||||
|
if (!checkPasswordLength(managedUserVM.getPassword())) {
|
||||||
|
throw new InvalidPasswordException();
|
||||||
|
}
|
||||||
|
User user = userService.registerUser(managedUserVM, managedUserVM.getPassword());
|
||||||
|
mailService.sendActivationEmail(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /activate : activate the registered user.
|
||||||
|
*
|
||||||
|
* @param key the activation key
|
||||||
|
* @throws RuntimeException 500 (Internal Server Error) if the user couldn't be activated
|
||||||
|
*/
|
||||||
|
@GetMapping("/activate")
|
||||||
|
public void activateAccount(@RequestParam(value = "key") String key) {
|
||||||
|
Optional<User> user = userService.activateRegistration(key);
|
||||||
|
if (!user.isPresent()) {
|
||||||
|
throw new InternalServerErrorException("No user was found for this activation key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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")
|
||||||
|
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 current user
|
||||||
|
* @throws RuntimeException 500 (Internal Server Error) if the user couldn't be returned
|
||||||
|
*/
|
||||||
|
@GetMapping("/account")
|
||||||
|
public UserDTO getAccount() {
|
||||||
|
return userService.getUserWithAuthorities()
|
||||||
|
.map(UserDTO::new)
|
||||||
|
.orElseThrow(() -> new InternalServerErrorException("User could not be found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /account : update the current user information.
|
||||||
|
*
|
||||||
|
* @param userDTO the current user information
|
||||||
|
* @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already used
|
||||||
|
* @throws RuntimeException 500 (Internal Server Error) if the user login wasn't found
|
||||||
|
*/
|
||||||
|
@PostMapping("/account")
|
||||||
|
public void saveAccount(@Valid @RequestBody UserDTO userDTO) {
|
||||||
|
String userLogin = SecurityUtils.getCurrentUserLogin().orElseThrow(() -> new InternalServerErrorException("Current user login not found"));
|
||||||
|
Optional<User> existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail());
|
||||||
|
if (existingUser.isPresent() && (!existingUser.get().getLogin().equalsIgnoreCase(userLogin))) {
|
||||||
|
throw new EmailAlreadyUsedException();
|
||||||
|
}
|
||||||
|
Optional<User> user = userRepository.findOneByLogin(userLogin);
|
||||||
|
if (!user.isPresent()) {
|
||||||
|
throw new InternalServerErrorException("User could not be found");
|
||||||
|
}
|
||||||
|
userService.updateUser(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(),
|
||||||
|
userDTO.getLangKey(), userDTO.getImageUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /account/change-password : changes the current user's password
|
||||||
|
*
|
||||||
|
* @param passwordChangeDto current and new password
|
||||||
|
* @throws InvalidPasswordException 400 (Bad Request) if the new password is incorrect
|
||||||
|
*/
|
||||||
|
@PostMapping(path = "/account/change-password")
|
||||||
|
public void changePassword(@RequestBody PasswordChangeDTO passwordChangeDto) {
|
||||||
|
if (!checkPasswordLength(passwordChangeDto.getNewPassword())) {
|
||||||
|
throw new InvalidPasswordException();
|
||||||
|
}
|
||||||
|
userService.changePassword(passwordChangeDto.getCurrentPassword(), passwordChangeDto.getNewPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /account/reset-password/init : Send an email to reset the password of the user
|
||||||
|
*
|
||||||
|
* @param mail the mail of the user
|
||||||
|
* @throws EmailNotFoundException 400 (Bad Request) if the email address is not registered
|
||||||
|
*/
|
||||||
|
@PostMapping(path = "/account/reset-password/init")
|
||||||
|
public void requestPasswordReset(@RequestBody String mail) {
|
||||||
|
mailService.sendPasswordResetMail(
|
||||||
|
userService.requestPasswordReset(mail)
|
||||||
|
.orElseThrow(EmailNotFoundException::new)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /account/reset-password/finish : Finish to reset the password of the user
|
||||||
|
*
|
||||||
|
* @param keyAndPassword the generated key and the new password
|
||||||
|
* @throws InvalidPasswordException 400 (Bad Request) if the password is incorrect
|
||||||
|
* @throws RuntimeException 500 (Internal Server Error) if the password could not be reset
|
||||||
|
*/
|
||||||
|
@PostMapping(path = "/account/reset-password/finish")
|
||||||
|
public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) {
|
||||||
|
if (!checkPasswordLength(keyAndPassword.getNewPassword())) {
|
||||||
|
throw new InvalidPasswordException();
|
||||||
|
}
|
||||||
|
Optional<User> user =
|
||||||
|
userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey());
|
||||||
|
|
||||||
|
if (!user.isPresent()) {
|
||||||
|
throw new InternalServerErrorException("No user was found for this reset key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkPasswordLength(String password) {
|
||||||
|
return !StringUtils.isEmpty(password) &&
|
||||||
|
password.length() >= ManagedUserVM.PASSWORD_MIN_LENGTH &&
|
||||||
|
password.length() <= ManagedUserVM.PASSWORD_MAX_LENGTH;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.service.AuditEventService;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.util.PaginationUtil;
|
||||||
|
|
||||||
|
import io.github.jhipster.web.util.ResponseUtil;
|
||||||
|
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.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
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<List<AuditEvent>> getAll(Pageable pageable) {
|
||||||
|
Page<AuditEvent> 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<List<AuditEvent>> getByDates(
|
||||||
|
@RequestParam(value = "fromDate") LocalDate fromDate,
|
||||||
|
@RequestParam(value = "toDate") LocalDate toDate,
|
||||||
|
Pageable pageable) {
|
||||||
|
|
||||||
|
Page<AuditEvent> page = auditEventService.findByDates(
|
||||||
|
fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant(),
|
||||||
|
toDate.atStartOfDay(ZoneId.systemDefault()).plusDays(1).toInstant(),
|
||||||
|
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<AuditEvent> get(@PathVariable Long id) {
|
||||||
|
return ResponseUtil.wrapOrNotFound(auditEventService.find(id));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.web.rest.vm.LoggerVM;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
|
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")
|
||||||
|
public List<LoggerVM> getList() {
|
||||||
|
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||||
|
return context.getLoggerList()
|
||||||
|
.stream()
|
||||||
|
.map(LoggerVM::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/logs")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
public void changeLevel(@RequestBody LoggerVM jsonLogger) {
|
||||||
|
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||||
|
context.getLogger(jsonLogger.getName()).setLevel(Level.valueOf(jsonLogger.getLevel()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.security.jwt.JWTFilter;
|
||||||
|
import org.hostsharing.hsadminng.security.jwt.TokenProvider;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.vm.LoginVM;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
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.context.SecurityContextHolder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller to authenticate users.
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api")
|
||||||
|
public class UserJWTController {
|
||||||
|
|
||||||
|
private final TokenProvider tokenProvider;
|
||||||
|
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
public UserJWTController(TokenProvider tokenProvider, AuthenticationManager authenticationManager) {
|
||||||
|
this.tokenProvider = tokenProvider;
|
||||||
|
this.authenticationManager = authenticationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/authenticate")
|
||||||
|
public ResponseEntity<JWTToken> authorize(@Valid @RequestBody LoginVM loginVM) {
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken =
|
||||||
|
new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());
|
||||||
|
|
||||||
|
Authentication authentication = this.authenticationManager.authenticate(authenticationToken);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe();
|
||||||
|
String jwt = tokenProvider.createToken(authentication, rememberMe);
|
||||||
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
|
||||||
|
return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object to return as body in JWT Authentication.
|
||||||
|
*/
|
||||||
|
static class JWTToken {
|
||||||
|
|
||||||
|
private String idToken;
|
||||||
|
|
||||||
|
JWTToken(String idToken) {
|
||||||
|
this.idToken = idToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("id_token")
|
||||||
|
String getIdToken() {
|
||||||
|
return idToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setIdToken(String idToken) {
|
||||||
|
this.idToken = idToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,183 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.config.Constants;
|
||||||
|
import org.hostsharing.hsadminng.domain.User;
|
||||||
|
import org.hostsharing.hsadminng.repository.UserRepository;
|
||||||
|
import org.hostsharing.hsadminng.security.AuthoritiesConstants;
|
||||||
|
import org.hostsharing.hsadminng.service.MailService;
|
||||||
|
import org.hostsharing.hsadminng.service.UserService;
|
||||||
|
import org.hostsharing.hsadminng.service.dto.UserDTO;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.errors.EmailAlreadyUsedException;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.errors.LoginAlreadyUsedException;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.util.HeaderUtil;
|
||||||
|
import org.hostsharing.hsadminng.web.rest.util.PaginationUtil;
|
||||||
|
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.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST controller for managing users.
|
||||||
|
* <p>
|
||||||
|
* This class accesses the User entity, and needs to fetch its collection of authorities.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* We use a View Model and a DTO for 3 reasons:
|
||||||
|
* <ul>
|
||||||
|
* <li>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.</li>
|
||||||
|
* <li> 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).</li>
|
||||||
|
* <li> As this manages users, for security reasons, we'd rather have a DTO layer.</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 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 final UserService userService;
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
private final MailService mailService;
|
||||||
|
|
||||||
|
public UserResource(UserService userService, UserRepository userRepository, MailService mailService) {
|
||||||
|
|
||||||
|
this.userService = userService;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.mailService = mailService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /users : Creates a new user.
|
||||||
|
* <p>
|
||||||
|
* 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 userDTO 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
|
||||||
|
* @throws BadRequestAlertException 400 (Bad Request) if the login or email is already in use
|
||||||
|
*/
|
||||||
|
@PostMapping("/users")
|
||||||
|
@PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||||
|
public ResponseEntity<User> createUser(@Valid @RequestBody UserDTO userDTO) throws URISyntaxException {
|
||||||
|
log.debug("REST request to save User : {}", userDTO);
|
||||||
|
|
||||||
|
if (userDTO.getId() != null) {
|
||||||
|
throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists");
|
||||||
|
// Lowercase the user login before comparing with database
|
||||||
|
} else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) {
|
||||||
|
throw new LoginAlreadyUsedException();
|
||||||
|
} else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) {
|
||||||
|
throw new EmailAlreadyUsedException();
|
||||||
|
} else {
|
||||||
|
User newUser = userService.createUser(userDTO);
|
||||||
|
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 userDTO the user to update
|
||||||
|
* @return the ResponseEntity with status 200 (OK) and with body the updated user
|
||||||
|
* @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already in use
|
||||||
|
* @throws LoginAlreadyUsedException 400 (Bad Request) if the login is already in use
|
||||||
|
*/
|
||||||
|
@PutMapping("/users")
|
||||||
|
@PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||||
|
public ResponseEntity<UserDTO> updateUser(@Valid @RequestBody UserDTO userDTO) {
|
||||||
|
log.debug("REST request to update User : {}", userDTO);
|
||||||
|
Optional<User> existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail());
|
||||||
|
if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) {
|
||||||
|
throw new EmailAlreadyUsedException();
|
||||||
|
}
|
||||||
|
existingUser = userRepository.findOneByLogin(userDTO.getLogin().toLowerCase());
|
||||||
|
if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) {
|
||||||
|
throw new LoginAlreadyUsedException();
|
||||||
|
}
|
||||||
|
Optional<UserDTO> updatedUser = userService.updateUser(userDTO);
|
||||||
|
|
||||||
|
return ResponseUtil.wrapOrNotFound(updatedUser,
|
||||||
|
HeaderUtil.createAlert("userManagement.updated", userDTO.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")
|
||||||
|
public ResponseEntity<List<UserDTO>> getAllUsers(Pageable pageable) {
|
||||||
|
final Page<UserDTO> page = userService.getAllManagedUsers(pageable);
|
||||||
|
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/users");
|
||||||
|
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a string list of the all of the roles
|
||||||
|
*/
|
||||||
|
@GetMapping("/users/authorities")
|
||||||
|
@PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||||
|
public List<String> getAuthorities() {
|
||||||
|
return userService.getAuthorities();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 + "}")
|
||||||
|
public ResponseEntity<UserDTO> 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 + "}")
|
||||||
|
@PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||||
|
public ResponseEntity<Void> 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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
import org.zalando.problem.AbstractThrowableProblem;
|
||||||
|
import org.zalando.problem.Status;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BadRequestAlertException extends AbstractThrowableProblem {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final String entityName;
|
||||||
|
|
||||||
|
private final String errorKey;
|
||||||
|
|
||||||
|
public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) {
|
||||||
|
this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) {
|
||||||
|
super(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey));
|
||||||
|
this.entityName = entityName;
|
||||||
|
this.errorKey = errorKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntityName() {
|
||||||
|
return entityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorKey() {
|
||||||
|
return errorKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> getAlertParameters(String entityName, String errorKey) {
|
||||||
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
|
parameters.put("message", "error." + errorKey);
|
||||||
|
parameters.put("params", entityName);
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
import org.zalando.problem.AbstractThrowableProblem;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.zalando.problem.Status.BAD_REQUEST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom, parameterized exception, which can be translated on the client side.
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* throw new CustomParameterizedException("myCustomError", "hello", "world");
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Can be translated with:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* "error.myCustomError" : "The server says {{param0}} to {{param1}}"
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class CustomParameterizedException extends AbstractThrowableProblem {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private static final String PARAM = "param";
|
||||||
|
|
||||||
|
public CustomParameterizedException(String message, String... params) {
|
||||||
|
this(message, toParamMap(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomParameterizedException(String message, Map<String, Object> paramMap) {
|
||||||
|
super(ErrorConstants.PARAMETERIZED_TYPE, "Parameterized Exception", BAD_REQUEST, null, null, null, toProblemParameters(message, paramMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Object> toParamMap(String... params) {
|
||||||
|
Map<String, Object> paramMap = new HashMap<>();
|
||||||
|
if (params != null && params.length > 0) {
|
||||||
|
for (int i = 0; i < params.length; i++) {
|
||||||
|
paramMap.put(PARAM + i, params[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paramMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Object> toProblemParameters(String message, Map<String, Object> paramMap) {
|
||||||
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
|
parameters.put("message", message);
|
||||||
|
parameters.put("params", paramMap);
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
public class EmailAlreadyUsedException extends BadRequestAlertException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public EmailAlreadyUsedException() {
|
||||||
|
super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
import org.zalando.problem.AbstractThrowableProblem;
|
||||||
|
import org.zalando.problem.Status;
|
||||||
|
|
||||||
|
public class EmailNotFoundException extends AbstractThrowableProblem {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public EmailNotFoundException() {
|
||||||
|
super(ErrorConstants.EMAIL_NOT_FOUND_TYPE, "Email address not registered", Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
public final class ErrorConstants {
|
||||||
|
|
||||||
|
public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure";
|
||||||
|
public static final String ERR_VALIDATION = "error.validation";
|
||||||
|
public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem";
|
||||||
|
public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message");
|
||||||
|
public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation");
|
||||||
|
public static final URI PARAMETERIZED_TYPE = URI.create(PROBLEM_BASE_URL + "/parameterized");
|
||||||
|
public static final URI ENTITY_NOT_FOUND_TYPE = URI.create(PROBLEM_BASE_URL + "/entity-not-found");
|
||||||
|
public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password");
|
||||||
|
public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used");
|
||||||
|
public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used");
|
||||||
|
public static final URI EMAIL_NOT_FOUND_TYPE = URI.create(PROBLEM_BASE_URL + "/email-not-found");
|
||||||
|
|
||||||
|
private ErrorConstants() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.web.rest.util.HeaderUtil;
|
||||||
|
|
||||||
|
import org.springframework.dao.ConcurrencyFailureException;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.context.request.NativeWebRequest;
|
||||||
|
import org.zalando.problem.DefaultProblem;
|
||||||
|
import org.zalando.problem.Problem;
|
||||||
|
import org.zalando.problem.ProblemBuilder;
|
||||||
|
import org.zalando.problem.Status;
|
||||||
|
import org.zalando.problem.spring.web.advice.ProblemHandling;
|
||||||
|
import org.zalando.problem.violations.ConstraintViolationProblem;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller advice to translate the server side exceptions to client-friendly json structures.
|
||||||
|
* The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807)
|
||||||
|
*/
|
||||||
|
@ControllerAdvice
|
||||||
|
public class ExceptionTranslator implements ProblemHandling {
|
||||||
|
|
||||||
|
private static final String FIELD_ERRORS_KEY = "fieldErrors";
|
||||||
|
private static final String MESSAGE_KEY = "message";
|
||||||
|
private static final String PATH_KEY = "path";
|
||||||
|
private static final String VIOLATIONS_KEY = "violations";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-process the Problem payload to add the message key for the front-end if needed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) {
|
||||||
|
if (entity == null) {
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
Problem problem = entity.getBody();
|
||||||
|
if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) {
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
ProblemBuilder builder = Problem.builder()
|
||||||
|
.withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
|
||||||
|
.withStatus(problem.getStatus())
|
||||||
|
.withTitle(problem.getTitle())
|
||||||
|
.with(PATH_KEY, request.getNativeRequest(HttpServletRequest.class).getRequestURI());
|
||||||
|
|
||||||
|
if (problem instanceof ConstraintViolationProblem) {
|
||||||
|
builder
|
||||||
|
.with(VIOLATIONS_KEY, ((ConstraintViolationProblem) problem).getViolations())
|
||||||
|
.with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION);
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
.withCause(((DefaultProblem) problem).getCause())
|
||||||
|
.withDetail(problem.getDetail())
|
||||||
|
.withInstance(problem.getInstance());
|
||||||
|
problem.getParameters().forEach(builder::with);
|
||||||
|
if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) {
|
||||||
|
builder.with(MESSAGE_KEY, "error.http." + problem.getStatus().getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) {
|
||||||
|
BindingResult result = ex.getBindingResult();
|
||||||
|
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
|
||||||
|
.map(f -> new FieldErrorVM(f.getObjectName(), f.getField(), f.getCode()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
Problem problem = Problem.builder()
|
||||||
|
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
|
||||||
|
.withTitle("Method argument not valid")
|
||||||
|
.withStatus(defaultConstraintViolationStatus())
|
||||||
|
.with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION)
|
||||||
|
.with(FIELD_ERRORS_KEY, fieldErrors)
|
||||||
|
.build();
|
||||||
|
return create(ex, problem, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler
|
||||||
|
public ResponseEntity<Problem> handleNoSuchElementException(NoSuchElementException ex, NativeWebRequest request) {
|
||||||
|
Problem problem = Problem.builder()
|
||||||
|
.withStatus(Status.NOT_FOUND)
|
||||||
|
.with(MESSAGE_KEY, ErrorConstants.ENTITY_NOT_FOUND_TYPE)
|
||||||
|
.build();
|
||||||
|
return create(ex, problem, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler
|
||||||
|
public ResponseEntity<Problem> handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) {
|
||||||
|
return create(ex, request, HeaderUtil.createFailureAlert(ex.getEntityName(), ex.getErrorKey(), ex.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler
|
||||||
|
public ResponseEntity<Problem> handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) {
|
||||||
|
Problem problem = Problem.builder()
|
||||||
|
.withStatus(Status.CONFLICT)
|
||||||
|
.with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE)
|
||||||
|
.build();
|
||||||
|
return create(ex, problem, request);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package org.hostsharing.hsadminng.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
import org.zalando.problem.AbstractThrowableProblem;
|
||||||
|
import org.zalando.problem.Status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple exception with a message, that returns an Internal Server Error code.
|
||||||
|
*/
|
||||||
|
public class InternalServerErrorException extends AbstractThrowableProblem {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public InternalServerErrorException(String message) {
|
||||||
|
super(ErrorConstants.DEFAULT_TYPE, message, Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
import org.zalando.problem.AbstractThrowableProblem;
|
||||||
|
import org.zalando.problem.Status;
|
||||||
|
|
||||||
|
public class InvalidPasswordException extends AbstractThrowableProblem {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public InvalidPasswordException() {
|
||||||
|
super(ErrorConstants.INVALID_PASSWORD_TYPE, "Incorrect password", Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
||||||
|
|
||||||
|
public class LoginAlreadyUsedException extends BadRequestAlertException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public LoginAlreadyUsedException() {
|
||||||
|
super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Specific errors used with Zalando's "problem-spring-web" library.
|
||||||
|
*
|
||||||
|
* More information on https://github.com/zalando/problem-spring-web
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.web.rest.errors;
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Spring MVC REST controllers.
|
||||||
|
*/
|
||||||
|
package org.hostsharing.hsadminng.web.rest;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user