Initial version of sisvietnamvn generated by generator-jhipster@9.1.0
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java/.devcontainer/base.Dockerfile
|
||||
|
||||
# [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 17, 17-bullseye, 17-buster
|
||||
ARG VARIANT="17"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT}
|
||||
|
||||
# [Option] Install Maven
|
||||
ARG INSTALL_MAVEN="false"
|
||||
ARG MAVEN_VERSION=""
|
||||
# [Option] Install Gradle
|
||||
ARG INSTALL_GRADLE="false"
|
||||
ARG GRADLE_VERSION=""
|
||||
RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \
|
||||
&& if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi
|
||||
|
||||
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
|
||||
ARG NODE_VERSION="none"
|
||||
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment this line to install global node packages.
|
||||
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
|
||||
@@ -0,0 +1,49 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java
|
||||
{
|
||||
"name": "Sisvietnamvn",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
// Update the VARIANT arg to pick a Java version: 17, 19
|
||||
// Append -bullseye or -buster to pin to an OS version.
|
||||
// Use the -bullseye variants on local arm64/Apple Silicon.
|
||||
"VARIANT": "21-bullseye",
|
||||
// Options
|
||||
// maven and gradle wrappers are used by default, we don't need them installed globally
|
||||
// "INSTALL_MAVEN": "false",
|
||||
// "INSTALL_GRADLE": "true",
|
||||
"NODE_VERSION": "24.16.0"
|
||||
}
|
||||
},
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"java.jdt.ls.java.home": "/docker-java-home"
|
||||
},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": ["vscjava.vscode-java-pack", "pivotal.vscode-boot-dev-pack", "esbenp.prettier-vscode"]
|
||||
}
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [8080],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "java -version",
|
||||
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode",
|
||||
|
||||
// Mount the workspace folder to the same path inside the container, so docker-in-docker containers work correctly. See https://github.com/jhipster/generator-jhipster/issues/31239
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind",
|
||||
"workspaceFolder": "${localWorkspaceFolder}",
|
||||
|
||||
"features": {
|
||||
"docker-in-docker": "latest",
|
||||
"docker-from-docker": "latest"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Generated by jhipster:java:bootstrap generator
|
||||
[*.java]
|
||||
indent_size = 4
|
||||
+149
-3
@@ -1,3 +1,149 @@
|
||||
/gradlew text eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.jar binary
|
||||
# This file is inspired by https://github.com/alexkaratarakis/gitattributes
|
||||
#
|
||||
# Auto detect text files and perform LF normalization
|
||||
# https://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
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 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
|
||||
*.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
|
||||
|
||||
+151
-35
@@ -1,37 +1,153 @@
|
||||
HELP.md
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
######################
|
||||
# Node
|
||||
######################
|
||||
/node/
|
||||
node_tmp/
|
||||
node_modules/
|
||||
npm-debug.log.*
|
||||
/.awcache/*
|
||||
/.cache-loader/*
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
######################
|
||||
# SASS
|
||||
######################
|
||||
.sass-cache/
|
||||
|
||||
######################
|
||||
# Eclipse
|
||||
######################
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
.factorypath
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/**
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
# STS-specific
|
||||
/.sts4-cache/*
|
||||
|
||||
######################
|
||||
# IntelliJ
|
||||
######################
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
*.ids
|
||||
*.orig
|
||||
classes/
|
||||
out/
|
||||
|
||||
######################
|
||||
# Visual Studio Code
|
||||
######################
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
######################
|
||||
# Maven
|
||||
######################
|
||||
/log/
|
||||
/target/
|
||||
|
||||
######################
|
||||
# Gradle
|
||||
######################
|
||||
.gradle/
|
||||
/build/
|
||||
/buildSrc/.gradle/
|
||||
/buildSrc/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
|
||||
|
||||
######################
|
||||
# Code coverage
|
||||
######################
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
.git
|
||||
|
||||
# Generated by jhipster:java-simple-application:gradle
|
||||
build
|
||||
gradle
|
||||
.gradle
|
||||
@@ -0,0 +1,13 @@
|
||||
printWidth: 140
|
||||
singleQuote: true
|
||||
tabWidth: 2
|
||||
useTabs: false
|
||||
arrowParens: avoid
|
||||
bracketSameLine: false
|
||||
plugins:
|
||||
- prettier-plugin-packagejson
|
||||
- prettier-plugin-java
|
||||
overrides:
|
||||
- files: "*.java"
|
||||
options:
|
||||
tabWidth: 4
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"generator-jhipster": {
|
||||
"applicationType": "monolith",
|
||||
"authenticationType": "jwt",
|
||||
"baseName": "sisvietnamvn",
|
||||
"buildTool": "gradle",
|
||||
"cacheProvider": "ehcache",
|
||||
"clientFramework": "no",
|
||||
"creationTimestamp": 1717387200000,
|
||||
"databaseType": "sql",
|
||||
"defaultCommand": "app",
|
||||
"devDatabaseType": "h2Disk",
|
||||
"enableHibernateCache": true,
|
||||
"enableSwaggerCodegen": false,
|
||||
"enableTranslation": false,
|
||||
"entities": [],
|
||||
"jhiPrefix": "jhi",
|
||||
"jhipsterVersion": "9.1.0",
|
||||
"jwtSecretKey": "ZWRjOTBhYjJiYzg5N2JhMTUyMTBiYzZhNzJjMWUwNmYzOTM3MjJmZGE3Zjc3NGE0ODhjMDZlYzRjNmFiODFhMDNkNmFjNmYwYzY4NmMwNGRkMDMwY2Q0NTllNDlhYzEyMjg0NmY2NzM5MWM1M2IxOTdkMDNhZTBhZDkzYWJjMWI=",
|
||||
"messageBroker": "no",
|
||||
"monorepository": true,
|
||||
"nativeLanguage": "en",
|
||||
"packageFolder": "com/sisvietnamvn/web/",
|
||||
"packageName": "com.sisvietnamvn.web",
|
||||
"prodDatabaseType": "oracle",
|
||||
"searchEngine": "no",
|
||||
"serverPort": 8080,
|
||||
"skipClient": true,
|
||||
"skipCommitHook": true,
|
||||
"skipUserManagement": false,
|
||||
"testFrameworks": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Getting Started
|
||||
|
||||
### Reference Documentation
|
||||
For further reference, please consider the following sections:
|
||||
|
||||
* [Official Gradle documentation](https://docs.gradle.org)
|
||||
* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/4.0.6/gradle-plugin)
|
||||
* [Create an OCI image](https://docs.spring.io/spring-boot/4.0.6/gradle-plugin/packaging-oci-image.html)
|
||||
* [Spring Web](https://docs.spring.io/spring-boot/4.0.6/reference/web/servlet.html)
|
||||
* [Thymeleaf](https://docs.spring.io/spring-boot/4.0.6/reference/web/servlet.html#web.servlet.spring-mvc.template-engines)
|
||||
* [Spring Boot DevTools](https://docs.spring.io/spring-boot/4.0.6/reference/using/devtools.html)
|
||||
|
||||
### Guides
|
||||
The following guides illustrate how to use some features concretely:
|
||||
|
||||
* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
|
||||
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
|
||||
* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/)
|
||||
* [Handling Form Submission](https://spring.io/guides/gs/handling-form-submission/)
|
||||
|
||||
### Additional Links
|
||||
These additional references should also help you:
|
||||
|
||||
* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle)
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# sisvietnamvn
|
||||
|
||||
This application was generated using JHipster 9.1.0, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v9.1.0](https://www.jhipster.tech/documentation-archive/v9.1.0).
|
||||
|
||||
## Project Structure
|
||||
|
||||
Node is required for generation and recommended for development. `package.json` is always generated for a better development experience with prettier, commit hooks, scripts and so on.
|
||||
|
||||
In the project root, JHipster generates configuration files for tools like git, prettier, eslint, husky, and others that are well known and you can find references in the web.
|
||||
|
||||
`/src/*` structure follows default Java structure.
|
||||
|
||||
- `.yo-rc.json` - Yeoman configuration file
|
||||
JHipster configuration is stored in this file at `generator-jhipster` key. You may find `generator-jhipster-*` for specific blueprints configuration.
|
||||
- `.yo-resolve` (optional) - Yeoman conflict resolver
|
||||
Allows to use a specific action when conflicts are found skipping prompts for files that matches a pattern. Each line should match `[pattern] [action]` with pattern been a [Minimatch](https://github.com/isaacs/minimatch#minimatch) pattern and action been one of skip (default if omitted) or force. Lines starting with `#` are considered comments and are ignored.
|
||||
- `.jhipster/*.json` - JHipster entity configuration files
|
||||
- `/src/main/docker` - Docker configurations for the application and services that the application depends on
|
||||
|
||||
## Development
|
||||
|
||||
To start your application in the dev profile, run:
|
||||
|
||||
```bash
|
||||
./gradlew
|
||||
```
|
||||
|
||||
For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][].
|
||||
|
||||
## Building for production
|
||||
|
||||
### Packaging as jar
|
||||
|
||||
To build the final jar and optimize the sisvietnamvn application for production, run:
|
||||
|
||||
```bash
|
||||
./gradlew -Pprod clean bootJar
|
||||
```
|
||||
|
||||
To ensure everything worked, run:
|
||||
|
||||
```bash
|
||||
java -jar build/libs/*.jar
|
||||
```
|
||||
|
||||
Refer to [Using JHipster in production][] for more details.
|
||||
|
||||
### Packaging as war
|
||||
|
||||
To package your application as a war in order to deploy it to an application server, run:
|
||||
|
||||
```bash
|
||||
./gradlew -Pprod -Pwar clean bootWar
|
||||
```
|
||||
|
||||
### JHipster Control Center
|
||||
|
||||
JHipster Control Center can help you manage and control your application(s). You can start a local control center server (accessible on http://localhost:7419) with:
|
||||
|
||||
```bash
|
||||
docker compose -f src/main/docker/jhipster-control-center.yml up
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Spring Boot tests
|
||||
|
||||
To launch your application's tests, run:
|
||||
|
||||
```bash
|
||||
./gradlew test integrationTest jacocoTestReport
|
||||
```
|
||||
|
||||
## Others
|
||||
|
||||
### Code quality using Sonar
|
||||
|
||||
Sonar is used to analyse code quality. You can start a local Sonar server (accessible on http://localhost:9001) with:
|
||||
|
||||
```bash
|
||||
docker compose -f src/main/docker/sonar.yml up -d
|
||||
```
|
||||
|
||||
Note: we have turned off forced authentication redirect for UI in [src/main/docker/sonar.yml](src/main/docker/sonar.yml) for out of the box experience while trying out SonarQube, for real use cases turn it back on.
|
||||
|
||||
You can run a Sonar analysis with using the [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) or by using the gradle plugin.
|
||||
|
||||
Then, run a Sonar analysis:
|
||||
|
||||
```bash
|
||||
./gradlew -Pprod clean check jacocoTestReport sonarqube -Dsonar.login=admin -Dsonar.password=admin
|
||||
```
|
||||
|
||||
Additionally, Instead of passing `sonar.password` and `sonar.login` as CLI arguments, these parameters can be configured from [sonar-project.properties](sonar-project.properties) as shown below:
|
||||
|
||||
```bash
|
||||
sonar.login=admin
|
||||
sonar.password=admin
|
||||
```
|
||||
|
||||
For more information, refer to the [Code quality page][].
|
||||
|
||||
### Docker Compose support
|
||||
|
||||
JHipster generates a number of Docker Compose configuration files in the [src/main/docker/](src/main/docker/) folder to launch required third party services.
|
||||
|
||||
For example, to start required services in Docker containers, run:
|
||||
|
||||
```bash
|
||||
docker compose -f src/main/docker/services.yml up -d
|
||||
```
|
||||
|
||||
To stop and remove the containers, run:
|
||||
|
||||
```bash
|
||||
docker compose -f src/main/docker/services.yml down
|
||||
```
|
||||
|
||||
[Spring Docker Compose Integration](https://docs.spring.io/spring-boot/reference/features/dev-services.html) is enabled by default. It's possible to disable it in `application.yml`:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
...
|
||||
docker:
|
||||
compose:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
npm run java:docker
|
||||
```
|
||||
|
||||
Or build an arm64 Docker image when using an arm64 processor OS, i.e., Apple Silicon chips (M\*), running:
|
||||
|
||||
```bash
|
||||
npm run java:docker:arm64
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
docker compose -f src/main/docker/app.yml up -d
|
||||
```
|
||||
|
||||
For more information refer to [Docker and Docker-Compose](https://www.jhipster.tech/documentation-archive/v9.1.0/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](https://www.jhipster.tech/documentation-archive/v9.1.0/setting-up-ci/) page for more information.
|
||||
|
||||
## References
|
||||
|
||||
- [JHipster Homepage and latest documentation](https://www.jhipster.tech/)
|
||||
- [JHipster 9.1.0 archive](https://www.jhipster.tech/documentation-archive/v9.1.0)
|
||||
- [Using JHipster in development](https://www.jhipster.tech/documentation-archive/v9.1.0/development/)
|
||||
- [Using Docker and Docker-Compose](https://www.jhipster.tech/documentation-archive/v9.1.0/docker-compose)
|
||||
- [Using JHipster in production](https://www.jhipster.tech/documentation-archive/v9.1.0/production/)
|
||||
- [Running tests page](https://www.jhipster.tech/documentation-archive/v9.1.0/running-tests/)
|
||||
- [Code quality page](https://www.jhipster.tech/documentation-archive/v9.1.0/code-quality/)
|
||||
- [Setting up Continuous Integration](https://www.jhipster.tech/documentation-archive/v9.1.0/setting-up-ci/)
|
||||
- [Node.js](https://nodejs.org/)
|
||||
- [NPM](https://www.npmjs.com/)
|
||||
+93
-20
@@ -1,32 +1,105 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '4.0.6'
|
||||
id 'io.spring.dependency-management' version '1.1.7'
|
||||
id "java"
|
||||
id "maven-publish"
|
||||
id "idea"
|
||||
id "eclipse"
|
||||
alias(libs.plugins.gradle.git.properties)
|
||||
alias(libs.plugins.spring.boot)
|
||||
id "jhipster.docker-conventions"
|
||||
id "jhipster.code-quality-conventions"
|
||||
id "org.liquibase.gradle"
|
||||
// jhipster-needle-gradle-plugins - JHipster will add additional gradle plugins here
|
||||
}
|
||||
|
||||
group = 'com.example'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
group = "com.sisvietnamvn.web"
|
||||
version = "0.0.1-SNAPSHOT"
|
||||
|
||||
description = ""
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
sourceCompatibility=21
|
||||
targetCompatibility=21
|
||||
}
|
||||
assert System.properties["java.specification.version"] == "21" || "25"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
// Local maven repository is required for libraries built locally with maven like development jhipster-bom.
|
||||
// mavenLocal()
|
||||
// jhipster-needle-gradle-repositories - JHipster will add additional repositories
|
||||
}
|
||||
|
||||
apply from: "gradle/spring-boot.gradle"
|
||||
apply from: "gradle/liquibase.gradle"
|
||||
// jhipster-needle-gradle-apply-from - JHipster will add additional gradle scripts to be applied here
|
||||
|
||||
idea {
|
||||
module {
|
||||
excludeDirs += files("node_modules")
|
||||
}
|
||||
}
|
||||
|
||||
eclipse {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs += ["build/generated/sources/annotationProcessor/java/main"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-thymeleaf-test'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
implementation libs.jhipster.framework
|
||||
implementation libs.mapstruct
|
||||
implementation libs.springdoc.openapi.starter.webmvc.api
|
||||
implementation "org.springframework.boot:spring-boot-h2console"
|
||||
implementation "org.springframework.boot:spring-boot-jackson2"
|
||||
implementation "org.springframework.boot:spring-boot-starter"
|
||||
implementation "org.springframework.boot:spring-boot-starter-actuator"
|
||||
implementation "org.springframework.boot:spring-boot-starter-aspectj"
|
||||
implementation "org.springframework.boot:spring-boot-starter-cache"
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
implementation "org.springframework.boot:spring-boot-starter-jackson"
|
||||
implementation "org.springframework.boot:spring-boot-starter-liquibase"
|
||||
implementation "org.springframework.boot:spring-boot-starter-mail"
|
||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server"
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
|
||||
implementation "org.springframework.boot:spring-boot-starter-tomcat"
|
||||
implementation "org.springframework.boot:spring-boot-starter-validation"
|
||||
implementation "org.springframework.boot:spring-boot-starter-web"
|
||||
implementation "org.springframework.security:spring-security-data"
|
||||
implementation "com.fasterxml.jackson.datatype:jackson-datatype-hppc"
|
||||
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
|
||||
implementation "com.zaxxer:HikariCP"
|
||||
implementation "io.micrometer:micrometer-registry-prometheus"
|
||||
implementation "javax.cache:cache-api"
|
||||
implementation "org.apache.commons:commons-lang3"
|
||||
implementation "org.ehcache:ehcache::jakarta"
|
||||
implementation "org.hibernate.orm:hibernate-core"
|
||||
implementation "org.hibernate.orm:hibernate-jcache"
|
||||
implementation "org.hibernate.validator:hibernate-validator"
|
||||
implementation "tools.jackson.datatype:jackson-datatype-hibernate7"
|
||||
implementation "tools.jackson.module:jackson-module-jaxb-annotations"
|
||||
testImplementation(libs.archunit.junit5.api) {
|
||||
exclude group: 'org.slf4j', module: 'slf4j-api'
|
||||
}
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-jackson-test"
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-security-test"
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-test"
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-webmvc-test"
|
||||
testImplementation "org.springframework.boot:spring-boot-test"
|
||||
testImplementation "org.springframework.security:spring-security-test"
|
||||
testImplementation "org.testcontainers:testcontainers"
|
||||
testImplementation "org.testcontainers:testcontainers-jdbc"
|
||||
testImplementation "org.testcontainers:testcontainers-junit-jupiter"
|
||||
testRuntimeOnly(libs.archunit.junit5.engine) {
|
||||
exclude group: 'org.slf4j', module: 'slf4j-api'
|
||||
}
|
||||
annotationProcessor libs.mapstruct.processor
|
||||
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
||||
annotationProcessor "org.glassfish.jaxb:jaxb-runtime"
|
||||
annotationProcessor "org.hibernate.orm:hibernate-processor"
|
||||
// jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation libs.jib.plugin
|
||||
implementation libs.modernizer.plugin
|
||||
implementation libs.nohttp.plugin
|
||||
implementation libs.sonarqube.plugin
|
||||
implementation libs.spotless.plugin
|
||||
// jhipster-needle-gradle-dependency - JHipster will add additional dependencies for convention plugins here
|
||||
// jhipster-needle-gradle-build-src-dependency - Deprecated: JHipster will add additional dependencies for convention plugins here
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[versions]
|
||||
# jhipster-needle-gradle-dependency-catalog-version - JHipster will add additional versions for convention plugins here
|
||||
# jhipster-needle-gradle-build-src-dependency-catalog-version - Deprecated: JHipster will add additional versions for convention plugins here
|
||||
|
||||
[libraries]
|
||||
jib-plugin = { module = "com.google.cloud.tools:jib-gradle-plugin", version = "3.5.3" }
|
||||
modernizer-plugin = { module = "com.github.andygoossens:gradle-modernizer-plugin", version = "1.13.0" }
|
||||
nohttp-plugin = { module = "io.spring.nohttp:nohttp-gradle", version = "0.0.11" }
|
||||
sonarqube-plugin = { module = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin", version = "7.3.0.8198" }
|
||||
spotless-plugin = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "8.5.1" }
|
||||
# jhipster-needle-gradle-dependency-catalog-libraries - JHipster will add additional libraries versions
|
||||
|
||||
[plugins]
|
||||
# jhipster-needle-gradle-dependency-catalog-plugins - JHipster will add additional plugins versions
|
||||
@@ -0,0 +1,63 @@
|
||||
plugins {
|
||||
id "jacoco"
|
||||
id "org.sonarqube"
|
||||
id "com.diffplug.spotless"
|
||||
id "com.github.andygoossens.gradle-modernizer-plugin"
|
||||
id "io.spring.nohttp"
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "${libs.versions.jacoco.get()}"
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
executionData(tasks.withType(Test))
|
||||
classDirectories.from = files(sourceSets.main.output.classesDirs)
|
||||
sourceDirectories.from = files(sourceSets.main.java.srcDirs)
|
||||
|
||||
reports {
|
||||
xml.required = true
|
||||
}
|
||||
}
|
||||
|
||||
file("sonar-project.properties").withReader {
|
||||
Properties sonarProperties = new Properties()
|
||||
sonarProperties.load(it)
|
||||
|
||||
sonarProperties.each { key, value ->
|
||||
sonarqube {
|
||||
properties {
|
||||
property(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spotless {
|
||||
java {
|
||||
target = 'src/*/java/**/*.java'
|
||||
// removeUnusedImports()
|
||||
}
|
||||
}
|
||||
|
||||
modernizer {
|
||||
failOnViolations = true
|
||||
includeTestClasses = true
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "${libs.versions.checkstyle.get()}"
|
||||
configFile = file("checkstyle.xml")
|
||||
checkstyleTest.enabled = false
|
||||
}
|
||||
|
||||
nohttp {
|
||||
source.include("build.gradle", "README.md")
|
||||
}
|
||||
|
||||
// workaround for https://github.com/checkstyle/checkstyle/issues/14123
|
||||
configurations.checkstyle {
|
||||
resolutionStrategy.capabilitiesResolution.withCapability("com.google.collections:google-collections") {
|
||||
select("com.google.guava:guava:0")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
id "com.google.cloud.tools.jib"
|
||||
}
|
||||
|
||||
jib {
|
||||
configurationName = "productionRuntimeClasspath"
|
||||
from {
|
||||
image = "eclipse-temurin:25-jre-noble"
|
||||
platforms {
|
||||
platform {
|
||||
architecture = "${findProperty('jibArchitecture') ?: 'amd64'}"
|
||||
os = "linux"
|
||||
}
|
||||
}
|
||||
}
|
||||
to {
|
||||
image = "sisvietnamvn:latest"
|
||||
}
|
||||
container {
|
||||
entrypoint = ["bash", "-c", "/entrypoint.sh"]
|
||||
ports = ["8080"]
|
||||
environment = [
|
||||
SPRING_OUTPUT_ANSI_ENABLED: "ALWAYS",
|
||||
JHIPSTER_SLEEP: "0"
|
||||
]
|
||||
creationTime = "USE_CURRENT_TIMESTAMP"
|
||||
user = 1000
|
||||
}
|
||||
extraDirectories {
|
||||
paths = file("src/main/docker/jib")
|
||||
permissions = ["/entrypoint.sh": "755"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
|
||||
<!-- Configure checker to use UTF-8 encoding -->
|
||||
<property name="charset" value="UTF-8"/>
|
||||
<!-- Configure checker to run on files with these extensions -->
|
||||
<property name="fileExtensions" value=""/>
|
||||
<!-- For detailed checkstyle configuration, see https://github.com/spring-io/nohttp/tree/master/nohttp-checkstyle -->
|
||||
<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck">
|
||||
</module>
|
||||
<module name="TreeWalker">
|
||||
<module name="MissingJavadocMethod">
|
||||
<!-- Disable Javadoc requirement as we want to follow "clean code" guidelines and classes, methods and arguments names should be self-explanatory -->
|
||||
<property name="allowMissingPropertyJavadoc" value="true"/>
|
||||
</module>
|
||||
</module>
|
||||
<!-- Allow suppression with comments
|
||||
// CHECKSTYLE:OFF
|
||||
... ignored content ...
|
||||
// CHECKSTYLE:ON
|
||||
-->
|
||||
<module name="SuppressWithPlainTextCommentFilter"/>
|
||||
</module>
|
||||
@@ -0,0 +1,39 @@
|
||||
rootProject.name=sisvietnamvn
|
||||
profile=dev
|
||||
|
||||
liquibaseVersion=5.0.2
|
||||
liquibaseTaskPrefix=liquibase
|
||||
liquibasePluginVersion=3.1.0
|
||||
# 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.
|
||||
## https://docs.gradle.org/current/userguide/gradle_daemon.html#sec:ways_to_disable_gradle_daemon
|
||||
## uncomment 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
|
||||
## uncomment 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
|
||||
## uncomment 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
|
||||
## uncomment the below line to enable the selective mode
|
||||
|
||||
#org.gradle.configureondemand=true
|
||||
|
||||
org.gradle.warning.mode=all
|
||||
@@ -0,0 +1,20 @@
|
||||
[versions]
|
||||
mapstruct = "1.6.3"
|
||||
archunit-junit5 = "1.4.2"
|
||||
checkstyle = "13.4.2"
|
||||
jacoco = "0.8.14"
|
||||
# jhipster-needle-gradle-dependency-catalog-version - JHipster will add additional versions for convention plugins here
|
||||
|
||||
[libraries]
|
||||
jhipster-framework = { module = "tech.jhipster:jhipster-framework", version = "9.1.0" }
|
||||
springdoc-openapi-starter-webmvc-api = { module = "org.springdoc:springdoc-openapi-starter-webmvc-api", version = "3.0.3" }
|
||||
archunit-junit5-api = { module = "com.tngtech.archunit:archunit-junit5-api", version.ref = "archunit-junit5" }
|
||||
archunit-junit5-engine = { module = "com.tngtech.archunit:archunit-junit5-engine", version.ref = "archunit-junit5" }
|
||||
mapstruct = { module = "org.mapstruct:mapstruct", version.ref = "mapstruct" }
|
||||
mapstruct-processor = { module = "org.mapstruct:mapstruct-processor", version.ref = "mapstruct" }
|
||||
# jhipster-needle-gradle-dependency-catalog-libraries - JHipster will add additional libraries versions
|
||||
|
||||
[plugins]
|
||||
gradle-git-properties = { id = "com.gorylenko.gradle-git-properties", version = "3.0.3" }
|
||||
spring-boot = { id = "org.springframework.boot", version = "4.0.6" }
|
||||
# jhipster-needle-gradle-dependency-catalog-plugins - JHipster will add additional plugins versions
|
||||
@@ -0,0 +1,53 @@
|
||||
configurations {
|
||||
liquibaseRuntime.extendsFrom sourceSets.main.compileClasspath
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.liquibase:liquibase-core"
|
||||
liquibaseRuntime "org.liquibase:liquibase-core"
|
||||
// Dependency required to parse options. Refer to https://github.com/liquibase/liquibase-gradle-plugin/tree/Release_2.2.0#news.
|
||||
liquibaseRuntime "info.picocli:picocli:4.7.7"
|
||||
if (project.hasProperty("prod") || project.hasProperty("gae")) {
|
||||
liquibaseRuntime "com.oracle.database.jdbc:ojdbc8"
|
||||
}
|
||||
liquibaseRuntime "org.liquibase.ext:liquibase-hibernate6:${liquibaseVersion}"
|
||||
// jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
|
||||
}
|
||||
|
||||
project.ext.diffChangelogFile = "src/main/resources/config/liquibase/changelog/" + new Date().format("yyyyMMddHHmmss") + "_changelog.xml"
|
||||
if (!project.hasProperty("runList")) {
|
||||
project.ext.runList = "main"
|
||||
}
|
||||
|
||||
liquibase {
|
||||
activities {
|
||||
main {
|
||||
driver "org.h2.Driver"
|
||||
url "jdbc:h2:file:./build/h2db/db/sisvietnamvn"
|
||||
username "sisvietnamvn"
|
||||
changelogFile "src/main/resources/config/liquibase/master.xml"
|
||||
logLevel "debug"
|
||||
classpath "src/main/resources/"
|
||||
}
|
||||
diffLog {
|
||||
driver "org.h2.Driver"
|
||||
url "jdbc:h2:file:./build/h2db/db/sisvietnamvn"
|
||||
username "sisvietnamvn"
|
||||
changelogFile project.ext.diffChangelogFile
|
||||
referenceUrl "hibernate:spring:com.sisvietnamvn.web.domain?dialect=org.hibernate.dialect.H2Dialect&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.hibernate.SpringImplicitNamingStrategy"
|
||||
logLevel "debug"
|
||||
classpath layout.buildDirectory.dir("classes/java/main").get().asFile.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
runList = project.ext.runList
|
||||
}
|
||||
|
||||
tasks.named('liquibaseDiff').configure { dependsOn(tasks.named('compileJava')) }
|
||||
tasks.named('liquibaseDiffChangelog').configure { dependsOn(tasks.named('compileJava')) }
|
||||
|
||||
ext {
|
||||
if (project.hasProperty("no-liquibase")) {
|
||||
springProfiles += ",no-liquibase"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
dependencies {
|
||||
developmentOnly "org.springframework.boot:spring-boot-devtools"
|
||||
implementation "com.h2database:h2"
|
||||
// jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
|
||||
}
|
||||
|
||||
springBoot {
|
||||
buildInfo {
|
||||
excludes = ['time']
|
||||
}
|
||||
}
|
||||
|
||||
bootRun {
|
||||
args = ["--spring.profiles.active=${springProfiles}"]
|
||||
}
|
||||
|
||||
|
||||
processResources {
|
||||
inputs.property('version', version)
|
||||
inputs.property('springProfiles', springProfiles)
|
||||
filesMatching("**/application.yml") {
|
||||
filter {
|
||||
it.replace("@project.version@", version)
|
||||
}
|
||||
filter {
|
||||
it.replace("@spring.profiles.active@", springProfiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def integrationTest = tasks.register('integrationTest', Test) {
|
||||
maxHeapSize = "1G"
|
||||
useJUnitPlatform()
|
||||
description = "Execute integration tests."
|
||||
group = "verification"
|
||||
include "**/*IT*", "**/*IntTest*"
|
||||
testLogging {
|
||||
events = ['FAILED', 'SKIPPED']
|
||||
exceptionFormat = "full"
|
||||
}
|
||||
systemProperty('spring.profiles.active', springTestProfiles)
|
||||
// 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.required = false
|
||||
dependsOn(tasks.named('test'))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
dependencies {
|
||||
implementation "com.oracle.database.jdbc:ojdbc8"
|
||||
testImplementation "org.testcontainers:testcontainers-oracle-xe"
|
||||
// jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
|
||||
}
|
||||
|
||||
ext {
|
||||
if (project.hasProperty("api-docs")) {
|
||||
springProfiles += ",api-docs"
|
||||
}
|
||||
}
|
||||
|
||||
springBoot {
|
||||
buildInfo()
|
||||
}
|
||||
|
||||
bootRun {
|
||||
args = ["--spring.profiles.active=${springProfiles}"]
|
||||
}
|
||||
|
||||
|
||||
processResources {
|
||||
inputs.property('version', version)
|
||||
inputs.property('springProfiles', springProfiles)
|
||||
filesMatching("**/application.yml") {
|
||||
filter {
|
||||
it.replace("@project.version@", version)
|
||||
}
|
||||
filter {
|
||||
it.replace("@spring.profiles.active@", springProfiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def integrationTest = tasks.register('integrationTest', Test) {
|
||||
maxHeapSize = "1G"
|
||||
useJUnitPlatform()
|
||||
description = "Execute integration tests."
|
||||
group = "verification"
|
||||
include "**/*IT*", "**/*IntTest*"
|
||||
testLogging {
|
||||
events = ['FAILED', 'SKIPPED']
|
||||
exceptionFormat = "full"
|
||||
}
|
||||
systemProperty('spring.profiles.active', springTestProfiles)
|
||||
// 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.required = false
|
||||
dependsOn(tasks.named('test'))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
ext {
|
||||
if (project.hasProperty("prod")) {
|
||||
springProfiles = "prod"
|
||||
springTestProfiles = "test,testprod"
|
||||
} else {
|
||||
springProfiles = "dev"
|
||||
springTestProfiles = "test,testdev"
|
||||
}
|
||||
if (project.hasProperty("tls")) {
|
||||
springProfiles += ",tls"
|
||||
}
|
||||
if (project.hasProperty("e2e")) {
|
||||
springProfiles += ",e2e"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
// 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("war")) {
|
||||
apply from: "gradle/war.gradle"
|
||||
}
|
||||
|
||||
|
||||
defaultTasks "bootRun"
|
||||
|
||||
springBoot {
|
||||
mainClass = "com.sisvietnamvn.web.SisvietnamvnApp"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
exclude "**/*IT*", "**/*IntTest*"
|
||||
testLogging {
|
||||
events = ['FAILED', 'SKIPPED']
|
||||
exceptionFormat = "full"
|
||||
}
|
||||
jvmArgs += '-Xmx512m'
|
||||
|
||||
systemProperty('spring.profiles.active', springTestProfiles)
|
||||
|
||||
// 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.required = false
|
||||
}
|
||||
|
||||
tasks.named('check').configure { dependsOn(tasks.named('integrationTest')) }
|
||||
|
||||
tasks.register('testReport', TestReport) {
|
||||
destinationDirectory = layout.buildDirectory.dir("reports/tests")
|
||||
testResults.from(test)
|
||||
}
|
||||
|
||||
tasks.register('integrationTestReport', TestReport) {
|
||||
destinationDirectory = layout.buildDirectory.dir("reports/tests")
|
||||
testResults.from(tasks.named('integrationTest'))
|
||||
}
|
||||
|
||||
gitProperties {
|
||||
failOnNoGitDirectory = false
|
||||
keys = ["git.branch", "git.commit.id.abbrev", "git.commit.id.describe"]
|
||||
}
|
||||
|
||||
tasks.named('generateGitProperties') {
|
||||
outputs.doNotCacheIf("Task is always executed") { true }
|
||||
}
|
||||
|
||||
tasks.register('cleanResources', Delete) {
|
||||
delete "build/resources"
|
||||
}
|
||||
|
||||
tasks.named('compileJava').configure { dependsOn(tasks.named('processResources')) }
|
||||
tasks.named('processResources').configure { dependsOn(tasks.named('bootBuildInfo')) }
|
||||
@@ -0,0 +1,12 @@
|
||||
apply plugin: "war"
|
||||
|
||||
bootWar {
|
||||
mainClass = "com.sisvietnamvn.web.SisvietnamvnApp"
|
||||
includes = ["WEB-INF/**", "META-INF/**"]
|
||||
}
|
||||
|
||||
war {
|
||||
enabled = true
|
||||
archiveExtension = "war.original"
|
||||
includes = ["WEB-INF/**", "META-INF/**"]
|
||||
}
|
||||
BIN
Binary file not shown.
+1
-3
@@ -1,7 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
implementation "io.micrometer:micrometer-tracing-bridge-brave"
|
||||
implementation "io.zipkin.reporter2:zipkin-reporter-brave"
|
||||
}
|
||||
Vendored
+15
-18
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015 the original authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,8 +15,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -57,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -85,8 +83,7 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -114,6 +111,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -132,13 +130,10 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
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
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -146,7 +141,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -154,7 +149,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -171,6 +166,7 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -202,15 +198,16 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+12
-13
@@ -13,8 +13,6 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -45,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
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
|
||||
|
||||
@@ -59,21 +57,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
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
|
||||
|
||||
: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%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
Generated
+7382
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "sisvietnamvn",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Description for sisvietnamvn",
|
||||
"license": "UNLICENSED",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"app:start": "./gradlew",
|
||||
"app:up": "docker compose -f src/main/docker/app.yml up --wait",
|
||||
"backend:build-cache": "npm run backend:info && npm run backend:nohttp:test && npm run ci:e2e:package -- -x webapp -x webapp_test",
|
||||
"backend:doc:test": "./gradlew javadoc ",
|
||||
"backend:info": "./gradlew -v",
|
||||
"backend:nohttp:test": "./gradlew checkstyleNohttp ",
|
||||
"backend:start": "./gradlew ",
|
||||
"backend:unit:test": "./gradlew test integrationTest -Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.com.sisvietnamvn.web=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF",
|
||||
"ci:backend:test": "npm run backend:info && npm run backend:doc:test && npm run backend:nohttp:test && npm run backend:unit:test -- -P$npm_package_config_default_environment",
|
||||
"ci:e2e:package": "npm run java:$npm_package_config_packaging:$npm_package_config_default_environment -- -Pe2e -Denforcer.skip=true",
|
||||
"postci:e2e:package": "cp build/libs/*.$npm_package_config_packaging e2e.$npm_package_config_packaging",
|
||||
"ci:e2e:prepare": "npm run ci:e2e:prepare:docker",
|
||||
"ci:e2e:prepare:docker": "npm run services:up --if-present && docker ps -a",
|
||||
"preci:e2e:server:start": "npm run services:db:await --if-present && npm run services:others:await --if-present",
|
||||
"ci:e2e:server:start": "java -jar e2e.$npm_package_config_packaging --spring.profiles.active=e2e,secret-samples,$npm_package_config_default_environment -Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.com.sisvietnamvn.web=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF --logging.level.org.springframework.web=ERROR",
|
||||
"ci:e2e:teardown": "npm run ci:e2e:teardown:docker --if-present",
|
||||
"ci:server:await": "echo \"Waiting for server at port $npm_package_config_backend_port to start\" && wait-on -t 180000 http-get://127.0.0.1:$npm_package_config_backend_port/management/health && echo \"Server at port $npm_package_config_backend_port started\"",
|
||||
"docker:db:up": "echo \"Docker for db oracle not configured for application sisvietnamvn\"",
|
||||
"java:docker": "./gradlew bootJar -Pprod jibDockerBuild",
|
||||
"java:docker:arm64": "npm run java:docker -- -PjibArchitecture=arm64",
|
||||
"java:docker:dev": "npm run java:docker -- -Pdev,webapp",
|
||||
"java:docker:prod": "npm run java:docker -- -Pprod",
|
||||
"java:jar": "./gradlew bootJar -x test -x integrationTest",
|
||||
"java:jar:dev": "npm run java:jar -- -Pdev,webapp",
|
||||
"java:jar:prod": "npm run java:jar -- -Pprod",
|
||||
"java:war": "./gradlew bootWar -Pwar -x test -x integrationTest",
|
||||
"java:war:dev": "npm run java:war -- -Pdev,webapp",
|
||||
"java:war:prod": "npm run java:war -- -Pprod"
|
||||
},
|
||||
"config": {
|
||||
"backend_port": 8080,
|
||||
"default_environment": "prod",
|
||||
"packaging": "jar"
|
||||
},
|
||||
"devDependencies": {
|
||||
"generator-jhipster": "9.1.0",
|
||||
"prettier": "3.8.3",
|
||||
"prettier-plugin-java": "2.9.6",
|
||||
"prettier-plugin-packagejson": "3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.16.0"
|
||||
}
|
||||
}
|
||||
@@ -1 +1,20 @@
|
||||
rootProject.name = 'sisvietnamvn'
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
// jhipster-needle-gradle-plugin-management-repositories - JHipster will add additional entries here
|
||||
}
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.liquibase:liquibase-core:${liquibaseVersion}"
|
||||
}
|
||||
}
|
||||
plugins {
|
||||
id "org.liquibase.gradle" version "${liquibasePluginVersion}"
|
||||
// jhipster-needle-gradle-plugin-management-plugins - JHipster will add additional entries here
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "sisvietnamvn"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
sonar.projectKey = sisvietnamvn
|
||||
sonar.projectName = sisvietnamvn generated by jhipster
|
||||
|
||||
# Typescript tests files must be inside sources and tests, otherwise `INFO: Test execution data ignored for 80 unknown
|
||||
# files, including:` is shown.
|
||||
sonar.sources = src
|
||||
sonar.tests = src
|
||||
sonar.host.url = http://localhost:9001
|
||||
sonar.sourceEncoding = UTF-8
|
||||
|
||||
sonar.test.inclusions = src/test/**/*.*
|
||||
sonar.junit.reportPaths = build/test-results/test,build/test-results/integrationTest
|
||||
sonar.java.codeCoveragePlugin = jacoco
|
||||
sonar.coverage.jacoco.xmlReportPaths = build/reports/jacoco/test/jacocoTestReport.xml
|
||||
|
||||
sonar.issue.ignore.multicriteria = S7027-domain, S3437, S7027-dto, UndocumentedApi, S125, S4502, S4684, S5145, S6437, S6437-2, S6418, S1192
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-7027 is ignored for entities
|
||||
sonar.issue.ignore.multicriteria.S7027-domain.resourceKey = src/main/java/com/sisvietnamvn/web/domain/**/*
|
||||
sonar.issue.ignore.multicriteria.S7027-domain.ruleKey = javaarchitecture:S7027
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-3437 is ignored
|
||||
sonar.issue.ignore.multicriteria.S3437.resourceKey = src/main/java/com/sisvietnamvn/web/**/*
|
||||
sonar.issue.ignore.multicriteria.S3437.ruleKey = squid:S3437
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-7027 is ignored for dtos
|
||||
sonar.issue.ignore.multicriteria.S7027-dto.resourceKey = src/main/java/com/sisvietnamvn/web/service/dto/**/*
|
||||
sonar.issue.ignore.multicriteria.S7027-dto.ruleKey = javaarchitecture:S7027
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-1176 is ignored, as we want to follow "clean code" guidelines and
|
||||
# classes, methods and arguments names should be self-explanatory
|
||||
sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey = src/main/java/com/sisvietnamvn/web/**/*
|
||||
sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey = squid:UndocumentedApi
|
||||
# Rule https://rules.sonarsource.com/xml/RSPEC-125 is ignored, we provide commented examples
|
||||
sonar.issue.ignore.multicriteria.S125.resourceKey = src/main/resources/logback-spring.xml
|
||||
sonar.issue.ignore.multicriteria.S125.ruleKey = xml:S125
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-4502 is ignored, as for JWT tokens we are not subject to CSRF attack
|
||||
sonar.issue.ignore.multicriteria.S4502.resourceKey = src/main/java/com/sisvietnamvn/web/config/SecurityConfiguration.java
|
||||
sonar.issue.ignore.multicriteria.S4502.ruleKey = java:S4502
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-4684
|
||||
sonar.issue.ignore.multicriteria.S4684.resourceKey = src/main/java/com/sisvietnamvn/web/web/rest/**/*
|
||||
sonar.issue.ignore.multicriteria.S4684.ruleKey = java:S4684
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-5145 is ignored, as we use log filter to format log messages
|
||||
sonar.issue.ignore.multicriteria.S5145.resourceKey = src/main/java/com/sisvietnamvn/web/**/*
|
||||
sonar.issue.ignore.multicriteria.S5145.ruleKey = javasecurity:S5145
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-6437 is ignored, hardcoded passwords are provided for development
|
||||
# purposes
|
||||
sonar.issue.ignore.multicriteria.S6437.resourceKey = src/main/resources/config/application-secret-samples.yml
|
||||
sonar.issue.ignore.multicriteria.S6437.ruleKey = java:S6437
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-6437 is ignored, hardcoded passwords are provided for development
|
||||
# purposes
|
||||
sonar.issue.ignore.multicriteria.S6437-2.resourceKey = src/main/resources/config/application-tls.yml
|
||||
sonar.issue.ignore.multicriteria.S6437-2.ruleKey = java:S6437
|
||||
# Rule yaml:S6418 is ignored, hardcoded passwords are provided for development and testing purposes
|
||||
sonar.issue.ignore.multicriteria.S6418.resourceKey = src/main/resources/config/application-secret-samples.yml
|
||||
sonar.issue.ignore.multicriteria.S6418.ruleKey = yaml:S6418
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-1192, there is no easy way to avoid this issue
|
||||
sonar.issue.ignore.multicriteria.S1192.resourceKey = src/main/java/com/sisvietnamvn/web/config/CacheConfiguration.java
|
||||
sonar.issue.ignore.multicriteria.S1192.ruleKey = java:S1192
|
||||
@@ -0,0 +1,26 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sisvietnamvn
|
||||
services:
|
||||
app:
|
||||
image: sisvietnamvn
|
||||
environment:
|
||||
- _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||
- SPRING_PROFILES_ACTIVE=prod,api-docs,secret-samples
|
||||
- MANAGEMENT_PROMETHEUS_METRICS_EXPORT_ENABLED=true
|
||||
- SPRING_DATASOURCE_URL=jdbc:oracle:thin:@oracle:1521:sisvietnamvn
|
||||
- SPRING_DATASOURCE_USERNAME=system
|
||||
- SPRING_DATASOURCE_PASSWORD=oracle
|
||||
- SPRING_LIQUIBASE_URL=jdbc:oracle:thin:@oracle:1521:sisvietnamvn
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD-SHELL',
|
||||
'bash -c ''exec 3<>/dev/tcp/127.0.0.1/8080; printf "GET /management/health HTTP/1.0\r\nHost: localhost\r\n\r\n" >&3; grep -q "\"status\":\"UP\"" <&3''',
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 40
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'Prometheus'
|
||||
orgId: 1
|
||||
folder: ''
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /etc/grafana/provisioning/dashboards
|
||||
@@ -0,0 +1,50 @@
|
||||
apiVersion: 1
|
||||
|
||||
# list of datasources that should be deleted from the database
|
||||
deleteDatasources:
|
||||
- name: Prometheus
|
||||
orgId: 1
|
||||
|
||||
# list of datasources to insert/update depending
|
||||
# whats available in the database
|
||||
datasources:
|
||||
# <string, required> name of the datasource. Required
|
||||
- name: Prometheus
|
||||
# <string, required> datasource type. Required
|
||||
type: prometheus
|
||||
# <string, required> access mode. direct or proxy. Required
|
||||
access: proxy
|
||||
# <int> org id. will default to orgId 1 if not specified
|
||||
orgId: 1
|
||||
# <string> url
|
||||
# On MacOS, replace localhost by host.docker.internal
|
||||
url: http://localhost:9090
|
||||
# <string> database password, if used
|
||||
password:
|
||||
# <string> database user, if used
|
||||
user:
|
||||
# <string> database name, if used
|
||||
database:
|
||||
# <bool> enable/disable basic auth
|
||||
basicAuth: false
|
||||
# <string> basic auth username
|
||||
basicAuthUser: admin
|
||||
# <string> basic auth password
|
||||
basicAuthPassword: admin
|
||||
# <bool> enable/disable with credentials headers
|
||||
withCredentials:
|
||||
# <bool> mark as default datasource. Max one per org
|
||||
isDefault: true
|
||||
# <map> fields that will be converted to json and stored in json_data
|
||||
jsonData:
|
||||
graphiteVersion: '1.1'
|
||||
tlsAuth: false
|
||||
tlsAuthWithCACert: false
|
||||
# <string> json object of data that will be encrypted.
|
||||
secureJsonData:
|
||||
tlsCACert: '...'
|
||||
tlsClientCert: '...'
|
||||
tlsClientKey: '...'
|
||||
version: 1
|
||||
# <bool> allow users to edit datasources from the UI.
|
||||
editable: true
|
||||
@@ -0,0 +1,51 @@
|
||||
## How to use JHCC docker compose
|
||||
# To allow JHCC to reach JHipster application from a docker container note that we set the host as host.docker.internal
|
||||
# To reach the application from a browser, you need to add '127.0.0.1 host.docker.internal' to your hosts file.
|
||||
### Discovery mode
|
||||
# JHCC supports 3 kinds of discovery mode: Consul, Eureka and static
|
||||
# In order to use one, please set SPRING_PROFILES_ACTIVE to one (and only one) of this values: consul,eureka,static
|
||||
### Discovery properties
|
||||
# According to the discovery mode choose as Spring profile, you have to set the right properties
|
||||
# please note that current properties are set to run JHCC with default values, personalize them if needed
|
||||
# and remove those from other modes. You can only have one mode active.
|
||||
#### Eureka
|
||||
# - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://admin:admin@host.docker.internal:8761/eureka/
|
||||
#### Consul
|
||||
# - SPRING_CLOUD_CONSUL_HOST=host.docker.internal
|
||||
# - SPRING_CLOUD_CONSUL_PORT=8500
|
||||
#### Static
|
||||
# Add instances to "MyApp"
|
||||
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_0_URI=http://host.docker.internal:8081
|
||||
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_1_URI=http://host.docker.internal:8082
|
||||
# Or add a new application named MyNewApp
|
||||
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYNEWAPP_0_URI=http://host.docker.internal:8080
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
|
||||
#### IMPORTANT
|
||||
# If you choose Consul or Eureka mode:
|
||||
# Do not forget to remove the prefix "127.0.0.1" in front of their port in order to expose them.
|
||||
# This is required because JHCC needs to communicate with Consul or Eureka.
|
||||
# - In Consul mode, the ports are in the consul.yml file.
|
||||
# - In Eureka mode, the ports are in the jhipster-registry.yml file.
|
||||
|
||||
name: sisvietnamvn
|
||||
services:
|
||||
jhipster-control-center:
|
||||
image: 'jhipster/jhipster-control-center:v0.5.0'
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
# Patch /etc/hosts to support resolving host.docker.internal to the internal IP address used by the host in all OSes
|
||||
- echo "`ip route | grep default | cut -d ' ' -f3` host.docker.internal" | tee -a /etc/hosts > /dev/null && java -jar /jhipster-control-center.jar
|
||||
environment:
|
||||
- _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||
- SPRING_PROFILES_ACTIVE=prod,api-docs,static
|
||||
- SPRING_SECURITY_USER_PASSWORD=admin
|
||||
# The token should have the same value than the one declared in you Spring configuration under the jhipster.security.authentication.jwt.base64-secret configuration's entry
|
||||
- JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET=ZWRjOTBhYjJiYzg5N2JhMTUyMTBiYzZhNzJjMWUwNmYzOTM3MjJmZGE3Zjc3NGE0ODhjMDZlYzRjNmFiODFhMDNkNmFjNmYwYzY4NmMwNGRkMDMwY2Q0NTllNDlhYzEyMjg0NmY2NzM5MWM1M2IxOTdkMDNhZTBhZDkzYWJjMWI=
|
||||
- SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_SISVIETNAMVN_0_URI=http://host.docker.internal:8080
|
||||
- LOGGING_FILE_NAME=/tmp/jhipster-control-center.log
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:7419:7419
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
|
||||
|
||||
# usage: file_env VAR [DEFAULT]
|
||||
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
|
||||
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
|
||||
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
|
||||
file_env() {
|
||||
local var="$1"
|
||||
local file_var="${var}_FILE"
|
||||
local def="${2:-}"
|
||||
if [[ ${!var:-} && ${!file_var:-} ]]; then
|
||||
echo >&2 "error: both $var and $file_var are set (but are exclusive)"
|
||||
exit 1
|
||||
fi
|
||||
local val="$def"
|
||||
if [[ ${!var:-} ]]; then
|
||||
val="${!var}"
|
||||
elif [[ ${!file_var:-} ]]; then
|
||||
val="$(< "${!file_var}")"
|
||||
fi
|
||||
|
||||
if [[ -n $val ]]; then
|
||||
export "$var"="$val"
|
||||
fi
|
||||
|
||||
unset "$file_var"
|
||||
return 0
|
||||
}
|
||||
|
||||
file_env 'SPRING_DATASOURCE_URL'
|
||||
file_env 'SPRING_DATASOURCE_USERNAME'
|
||||
file_env 'SPRING_DATASOURCE_PASSWORD'
|
||||
file_env 'SPRING_LIQUIBASE_URL'
|
||||
file_env 'SPRING_LIQUIBASE_USER'
|
||||
file_env 'SPRING_LIQUIBASE_PASSWORD'
|
||||
file_env 'JHIPSTER_REGISTRY_PASSWORD'
|
||||
|
||||
exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -cp /app/resources/:/app/classes/:/app/libs/* "com.sisvietnamvn.web.SisvietnamvnApp" "$@"
|
||||
@@ -0,0 +1,31 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sisvietnamvn
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:v3.11.3
|
||||
volumes:
|
||||
- ./prometheus/:/etc/prometheus/
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:9090:9090
|
||||
# On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and
|
||||
# grafana/provisioning/datasources/datasource.yml
|
||||
network_mode: 'host' # to test locally running service
|
||||
grafana:
|
||||
image: grafana/grafana:13.0.1
|
||||
volumes:
|
||||
- ./grafana/provisioning/:/etc/grafana/provisioning/
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
- GF_INSTALL_PLUGINS=grafana-piechart-panel
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:3000:3000
|
||||
# On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and
|
||||
# grafana/provisioning/datasources/datasource.yml
|
||||
network_mode: 'host' # to test locally running service
|
||||
@@ -0,0 +1,31 @@
|
||||
# Sample global config for monitoring JHipster applications
|
||||
global:
|
||||
scrape_interval: 15s # By default, scrape targets every 15 seconds.
|
||||
evaluation_interval: 15s # By default, scrape targets every 15 seconds.
|
||||
# scrape_timeout is set to the global default (10s).
|
||||
|
||||
# Attach these labels to any time series or alerts when communicating with
|
||||
# external systems (federation, remote storage, Alertmanager).
|
||||
external_labels:
|
||||
monitor: 'jhipster'
|
||||
|
||||
# A scrape configuration containing exactly one endpoint to scrape:
|
||||
# Here it's Prometheus itself.
|
||||
scrape_configs:
|
||||
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
|
||||
- job_name: 'prometheus'
|
||||
|
||||
# Override the global default and scrape targets from this job every 5 seconds.
|
||||
scrape_interval: 5s
|
||||
|
||||
# scheme defaults to 'http' enable https in case your application is server via https
|
||||
#scheme: https
|
||||
# basic auth is not needed by default. See https://www.jhipster.tech/monitoring/#configuring-metrics-forwarding for details
|
||||
#basic_auth:
|
||||
# username: admin
|
||||
# password: admin
|
||||
metrics_path: /management/prometheus
|
||||
static_configs:
|
||||
- targets:
|
||||
# On MacOS, replace localhost by host.docker.internal
|
||||
- localhost:8080
|
||||
@@ -0,0 +1,15 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sisvietnamvn
|
||||
services:
|
||||
sonar:
|
||||
container_name: sonarqube
|
||||
image: sonarqube:26.5.0.122743-community
|
||||
# Forced authentication redirect for UI is turned off for out of the box experience while trying out SonarQube
|
||||
# For real use cases delete SONAR_FORCEAUTHENTICATION variable or set SONAR_FORCEAUTHENTICATION=true
|
||||
environment:
|
||||
- SONAR_FORCEAUTHENTICATION=false
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:9001:9000
|
||||
- 127.0.0.1:9000:9000
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sisvietnamvn.web;
|
||||
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
import tech.jhipster.config.DefaultProfileUtil;
|
||||
|
||||
/**
|
||||
* This is a helper Java class that provides an alternative to creating a {@code 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(SisvietnamvnApp.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.sisvietnamvn.web;
|
||||
|
||||
import jakarta.annotation.Generated;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Generated(value = "JHipster", comments = "Generated by JHipster 9.1.0")
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ ElementType.TYPE })
|
||||
public @interface GeneratedByJHipster {}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.sisvietnamvn.web;
|
||||
|
||||
import com.sisvietnamvn.web.config.ApplicationProperties;
|
||||
import com.sisvietnamvn.web.config.CRLFLogConverter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
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.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.h2console.autoconfigure.H2ConsoleAutoConfiguration;
|
||||
import org.springframework.boot.liquibase.autoconfigure.LiquibaseProperties;
|
||||
import org.springframework.core.env.Environment;
|
||||
import tech.jhipster.config.DefaultProfileUtil;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
|
||||
@SpringBootApplication(exclude = { H2ConsoleAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class })
|
||||
public class SisvietnamvnApp {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SisvietnamvnApp.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public SisvietnamvnApp(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes sisvietnamvn.
|
||||
* <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 = List.of(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) {
|
||||
var app = new SpringApplication(SisvietnamvnApp.class);
|
||||
DefaultProfileUtil.addDefaultProfile(app);
|
||||
Environment env = app.run(args).getEnvironment();
|
||||
logApplicationStartup(env);
|
||||
}
|
||||
|
||||
private static void logApplicationStartup(Environment env) {
|
||||
String protocol = Optional.ofNullable(env.getProperty("server.ssl.key-store"))
|
||||
.map(key -> "https")
|
||||
.orElse("http");
|
||||
String applicationName = env.getProperty("spring.application.name");
|
||||
String serverPort = env.getProperty("server.port");
|
||||
String contextPath = Optional.ofNullable(env.getProperty("server.servlet.context-path"))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElse("/");
|
||||
var 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(
|
||||
CRLFLogConverter.CRLF_SAFE_MARKER,
|
||||
"""
|
||||
|
||||
----------------------------------------------------------
|
||||
\tApplication '{}' is running! Access URLs:
|
||||
\tLocal: \t\t{}://localhost:{}{}
|
||||
\tExternal: \t{}://{}:{}{}
|
||||
\tProfile(s): \t{}
|
||||
----------------------------------------------------------""",
|
||||
applicationName,
|
||||
protocol,
|
||||
serverPort,
|
||||
contextPath,
|
||||
protocol,
|
||||
hostAddress,
|
||||
serverPort,
|
||||
contextPath,
|
||||
env.getActiveProfiles().length == 0 ? env.getDefaultProfiles() : env.getActiveProfiles()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.sisvietnamvn.web;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SisvietnamvnApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SisvietnamvnApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.sisvietnamvn.web.aop.logging;
|
||||
|
||||
import java.util.Arrays;
|
||||
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 org.springframework.core.env.Profiles;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
|
||||
/**
|
||||
* 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 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(com.sisvietnamvn.web.repository..*)
|
||||
|| within(com.sisvietnamvn.web.service..*)
|
||||
|| within(com.sisvietnamvn.web.web.rest..*)
|
||||
"""
|
||||
)
|
||||
public void applicationPackagePointcut() {
|
||||
// Method is empty as this is just a Pointcut, the implementations are in the advices.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the {@link Logger} associated to the given {@link JoinPoint}.
|
||||
*
|
||||
* @param joinPoint join point we want the logger for.
|
||||
* @return {@link Logger} associated to the given {@link JoinPoint}.
|
||||
*/
|
||||
private Logger logger(JoinPoint joinPoint) {
|
||||
return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
|
||||
logger(joinPoint).error(
|
||||
"Exception in {}() with cause = '{}' and exception = '{}'",
|
||||
joinPoint.getSignature().getName(),
|
||||
e.getCause() != null ? e.getCause() : "NULL",
|
||||
e.getMessage(),
|
||||
e
|
||||
);
|
||||
} else {
|
||||
logger(joinPoint).error(
|
||||
"Exception in {}() with cause = {}",
|
||||
joinPoint.getSignature().getName(),
|
||||
e.getCause() != null ? String.valueOf(e.getCause()) : "NULL"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advice that logs when a method is entered and exited.
|
||||
*
|
||||
* @param joinPoint join point for advice.
|
||||
* @return result.
|
||||
* @throws Throwable throws {@link IllegalArgumentException}.
|
||||
*/
|
||||
@Around("applicationPackagePointcut() && springBeanPointcut()")
|
||||
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
var log = logger(joinPoint);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Enter: {}() with argument[s] = {}", joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
|
||||
}
|
||||
try {
|
||||
Object result = joinPoint.proceed();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Exit: {}() with result = {}", joinPoint.getSignature().getName(), result);
|
||||
}
|
||||
return result;
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Illegal argument: {} in {}()", Arrays.toString(joinPoint.getArgs()), joinPoint.getSignature().getName());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Logging aspect.
|
||||
*/
|
||||
package com.sisvietnamvn.web.aop.logging;
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties specific to Sisvietnamvn.
|
||||
* <p>
|
||||
* Properties are configured in the {@code application.yml} file.
|
||||
* See {@link tech.jhipster.config.JHipsterProperties} for a good example.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
|
||||
public class ApplicationProperties {
|
||||
|
||||
private final Liquibase liquibase = new Liquibase();
|
||||
|
||||
// jhipster-needle-application-properties-property
|
||||
|
||||
public Liquibase getLiquibase() {
|
||||
return liquibase;
|
||||
}
|
||||
|
||||
// jhipster-needle-application-properties-property-getter
|
||||
|
||||
public static class Liquibase {
|
||||
|
||||
private Boolean asyncStart = true;
|
||||
|
||||
public Boolean getAsyncStart() {
|
||||
return asyncStart;
|
||||
}
|
||||
|
||||
public void setAsyncStart(Boolean asyncStart) {
|
||||
this.asyncStart = asyncStart;
|
||||
}
|
||||
}
|
||||
|
||||
// jhipster-needle-application-properties-property-class
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import tech.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@Profile("!testdev & !testprod")
|
||||
public class AsyncConfiguration implements AsyncConfigurer {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AsyncConfiguration.class);
|
||||
|
||||
private final TaskExecutionProperties taskExecutionProperties;
|
||||
|
||||
public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) {
|
||||
this.taskExecutionProperties = taskExecutionProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean(name = "taskExecutor")
|
||||
public Executor getAsyncExecutor() {
|
||||
LOG.debug("Creating Async Task Executor");
|
||||
var executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize());
|
||||
executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize());
|
||||
executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity());
|
||||
executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix());
|
||||
return new ExceptionHandlingAsyncTaskExecutor(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new SimpleAsyncUncaughtExceptionHandler();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.pattern.CompositeConverter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
import org.springframework.boot.ansi.AnsiColor;
|
||||
import org.springframework.boot.ansi.AnsiElement;
|
||||
import org.springframework.boot.ansi.AnsiOutput;
|
||||
import org.springframework.boot.ansi.AnsiStyle;
|
||||
|
||||
/**
|
||||
* Log filter to prevent attackers from forging log entries by submitting input containing CRLF characters.
|
||||
* CRLF characters are replaced with a red colored _ character.
|
||||
*
|
||||
* @see <a href="https://owasp.org/www-community/attacks/Log_Injection">Log Forging Description</a>
|
||||
* @see <a href="https://github.com/jhipster/generator-jhipster/issues/14949">JHipster issue</a>
|
||||
*/
|
||||
public class CRLFLogConverter extends CompositeConverter<ILoggingEvent> {
|
||||
|
||||
public static final Marker CRLF_SAFE_MARKER = MarkerFactory.getMarker("CRLF_SAFE");
|
||||
|
||||
private static final String[] SAFE_LOGS = {
|
||||
"org.hibernate",
|
||||
"org.springframework.boot.autoconfigure",
|
||||
"org.springframework.boot.diagnostics",
|
||||
};
|
||||
private static final Map<String, AnsiElement> ELEMENTS;
|
||||
|
||||
static {
|
||||
Map<String, AnsiElement> ansiElements = new HashMap<>();
|
||||
ansiElements.put("faint", AnsiStyle.FAINT);
|
||||
ansiElements.put("red", AnsiColor.RED);
|
||||
ansiElements.put("green", AnsiColor.GREEN);
|
||||
ansiElements.put("yellow", AnsiColor.YELLOW);
|
||||
ansiElements.put("blue", AnsiColor.BLUE);
|
||||
ansiElements.put("magenta", AnsiColor.MAGENTA);
|
||||
ansiElements.put("cyan", AnsiColor.CYAN);
|
||||
ELEMENTS = Collections.unmodifiableMap(ansiElements);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String transform(ILoggingEvent event, String in) {
|
||||
AnsiElement element = ELEMENTS.get(getFirstOption());
|
||||
List<Marker> markers = event.getMarkerList();
|
||||
if ((markers != null && !markers.isEmpty() && markers.getFirst().contains(CRLF_SAFE_MARKER)) || isLoggerSafe(event)) {
|
||||
return in;
|
||||
}
|
||||
String replacement = element == null ? "_" : toAnsiString("_", element);
|
||||
return in.replaceAll("[\n\r\t]", replacement);
|
||||
}
|
||||
|
||||
protected boolean isLoggerSafe(ILoggingEvent event) {
|
||||
for (String safeLogger : SAFE_LOGS) {
|
||||
if (event.getLoggerName().startsWith(safeLogger)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected String toAnsiString(String in, AnsiElement element) {
|
||||
return AnsiOutput.toString(element, in);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.ehcache.config.builders.*;
|
||||
import org.ehcache.jsr107.Eh107Configuration;
|
||||
import org.hibernate.cache.jcache.ConfigSettings;
|
||||
import org.springframework.boot.cache.autoconfigure.JCacheManagerCustomizer;
|
||||
import org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfiguration {
|
||||
|
||||
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;
|
||||
|
||||
public CacheConfiguration(JHipsterProperties jHipsterProperties) {
|
||||
var 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 HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cacheManager) {
|
||||
return hibernateProperties -> hibernateProperties.put(ConfigSettings.CACHE_MANAGER, cacheManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JCacheManagerCustomizer cacheManagerCustomizer() {
|
||||
return cm -> {
|
||||
createCache(cm, com.sisvietnamvn.web.repository.UserRepository.USERS_BY_LOGIN_CACHE);
|
||||
createCache(cm, com.sisvietnamvn.web.repository.UserRepository.USERS_BY_EMAIL_CACHE);
|
||||
createCache(cm, com.sisvietnamvn.web.domain.User.class.getName());
|
||||
createCache(cm, com.sisvietnamvn.web.domain.Authority.class.getName());
|
||||
createCache(cm, com.sisvietnamvn.web.domain.User.class.getName() + ".authorities");
|
||||
// jhipster-needle-ehcache-add-entry
|
||||
};
|
||||
}
|
||||
|
||||
private void createCache(javax.cache.CacheManager cm, String cacheName) {
|
||||
javax.cache.Cache<Object, Object> cache = cm.getCache(cacheName);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
} else {
|
||||
cm.createCache(cacheName, jcacheConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.info.BuildProperties;
|
||||
import org.springframework.boot.info.GitProperties;
|
||||
import org.springframework.cache.interceptor.KeyGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tech.jhipster.config.cache.PrefixedKeyGenerator;
|
||||
|
||||
@Configuration
|
||||
public class CacheKeyGeneratorConfiguration {
|
||||
|
||||
private GitProperties gitProperties;
|
||||
private BuildProperties buildProperties;
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setGitProperties(GitProperties gitProperties) {
|
||||
this.gitProperties = gitProperties;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setBuildProperties(BuildProperties buildProperties) {
|
||||
this.buildProperties = buildProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KeyGenerator keyGenerator() {
|
||||
return new PrefixedKeyGenerator(this.gitProperties, this.buildProperties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
/**
|
||||
* Application constants.
|
||||
*/
|
||||
public final class Constants {
|
||||
|
||||
// Regex for acceptable logins
|
||||
public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$";
|
||||
|
||||
public static final String SYSTEM = "system";
|
||||
public static final String DEFAULT_LANGUAGE = "en";
|
||||
|
||||
private Constants() {}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.h2console.autoconfigure.H2ConsoleProperties;
|
||||
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 tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.config.h2.H2ConfigurationHelper;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories({ "com.sisvietnamvn.web.repository" })
|
||||
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
|
||||
@EnableTransactionManagement
|
||||
@EnableConfigurationProperties(H2ConsoleProperties.class)
|
||||
public class DatabaseConfiguration {
|
||||
|
||||
private static 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)
|
||||
@ConditionalOnProperty(prefix = "spring.h2.console", name = "enabled", havingValue = "true")
|
||||
public Object h2TCPServer() throws SQLException {
|
||||
String port = getValidPortForH2();
|
||||
LOG.debug("H2 database is available on port {}", port);
|
||||
return H2ConfigurationHelper.createServer(port);
|
||||
}
|
||||
|
||||
private String getValidPortForH2() {
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.sisvietnamvn.web.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) {
|
||||
var registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setUseIsoFormat(true);
|
||||
registrar.registerFormatters(registry);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tools.jackson.datatype.hibernate7.Hibernate7Module;
|
||||
import tools.jackson.datatype.hibernate7.Hibernate7Module.Feature;
|
||||
|
||||
@Configuration
|
||||
public class JacksonHibernateConfiguration {
|
||||
|
||||
/*
|
||||
* Support for Hibernate types in Jackson.
|
||||
*/
|
||||
@Bean
|
||||
public Hibernate7Module hibernate7Module() {
|
||||
return new Hibernate7Module().configure(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.sql.DataSource;
|
||||
import liquibase.integration.spring.SpringLiquibase;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties;
|
||||
import org.springframework.boot.liquibase.autoconfigure.LiquibaseDataSource;
|
||||
import org.springframework.boot.liquibase.autoconfigure.LiquibaseProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.config.liquibase.SpringLiquibaseUtil;
|
||||
|
||||
@Configuration
|
||||
public class LiquibaseConfiguration {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LiquibaseConfiguration.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public LiquibaseConfiguration(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SpringLiquibase liquibase(
|
||||
@Qualifier("taskExecutor") Executor executor,
|
||||
LiquibaseProperties liquibaseProperties,
|
||||
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource,
|
||||
ObjectProvider<DataSource> dataSource,
|
||||
ApplicationProperties applicationProperties,
|
||||
DataSourceProperties dataSourceProperties
|
||||
) {
|
||||
SpringLiquibase liquibase;
|
||||
if (Boolean.TRUE.equals(applicationProperties.getLiquibase().getAsyncStart())) {
|
||||
liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase(
|
||||
this.env,
|
||||
executor,
|
||||
liquibaseDataSource.getIfAvailable(),
|
||||
liquibaseProperties,
|
||||
dataSource.getIfUnique(),
|
||||
dataSourceProperties
|
||||
);
|
||||
} else {
|
||||
liquibase = SpringLiquibaseUtil.createSpringLiquibase(
|
||||
liquibaseDataSource.getIfAvailable(),
|
||||
liquibaseProperties,
|
||||
dataSource.getIfUnique(),
|
||||
dataSourceProperties
|
||||
);
|
||||
}
|
||||
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
|
||||
if (!CollectionUtils.isEmpty(liquibaseProperties.getContexts())) {
|
||||
liquibase.setContexts(StringUtils.collectionToCommaDelimitedString(liquibaseProperties.getContexts()));
|
||||
}
|
||||
liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
|
||||
liquibase.setLiquibaseSchema(liquibaseProperties.getLiquibaseSchema());
|
||||
liquibase.setLiquibaseTablespace(liquibaseProperties.getLiquibaseTablespace());
|
||||
liquibase.setDatabaseChangeLogLockTable(liquibaseProperties.getDatabaseChangeLogLockTable());
|
||||
liquibase.setDatabaseChangeLogTable(liquibaseProperties.getDatabaseChangeLogTable());
|
||||
liquibase.setDropFirst(liquibaseProperties.isDropFirst());
|
||||
if (!CollectionUtils.isEmpty(liquibaseProperties.getLabelFilter())) {
|
||||
liquibase.setLabelFilter(StringUtils.collectionToCommaDelimitedString(liquibaseProperties.getLabelFilter()));
|
||||
}
|
||||
liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
|
||||
liquibase.setRollbackFile(liquibaseProperties.getRollbackFile());
|
||||
liquibase.setTestRollbackOnUpdate(liquibaseProperties.isTestRollbackOnUpdate());
|
||||
if (env.matchesProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
|
||||
liquibase.setShouldRun(false);
|
||||
} else {
|
||||
liquibase.setShouldRun(liquibaseProperties.isEnabled());
|
||||
LOG.debug("Configuring Liquibase");
|
||||
}
|
||||
return liquibase;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import com.sisvietnamvn.web.aop.logging.LoggingAspect;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.core.env.Environment;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class LoggingAspectConfiguration {
|
||||
|
||||
@Bean
|
||||
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
|
||||
public LoggingAspect loggingAspect(Environment env) {
|
||||
return new LoggingAspect(env);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import static tech.jhipster.config.logging.LoggingUtils.*;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
/*
|
||||
* Configures the console and Logstash log appenders from the app properties
|
||||
*/
|
||||
@Configuration
|
||||
public class LoggingConfiguration {
|
||||
|
||||
public LoggingConfiguration(
|
||||
@Value("${spring.application.name}") String appName,
|
||||
@Value("${server.port}") String serverPort,
|
||||
JHipsterProperties jHipsterProperties,
|
||||
ObjectMapper mapper
|
||||
) throws JacksonException {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("app_name", appName);
|
||||
map.put("app_port", serverPort);
|
||||
var customFields = mapper.writeValueAsString(map);
|
||||
|
||||
var loggingProperties = jHipsterProperties.getLogging();
|
||||
var logstashProperties = loggingProperties.getLogstash();
|
||||
|
||||
if (loggingProperties.isUseJsonFormat()) {
|
||||
addJsonConsoleAppender(context, customFields);
|
||||
}
|
||||
if (logstashProperties.isEnabled()) {
|
||||
addLogstashTcpSocketAppender(context, customFields, logstashProperties);
|
||||
}
|
||||
if (loggingProperties.isUseJsonFormat() || logstashProperties.isEnabled()) {
|
||||
addContextListener(context, customFields, loggingProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
import com.sisvietnamvn.web.security.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.Profiles;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
||||
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
|
||||
private final Environment env;
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
public SecurityConfiguration(Environment env, JHipsterProperties jHipsterProperties) {
|
||||
this.env = env;
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http.cors(withDefaults())
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(authz ->
|
||||
// prettier-ignore
|
||||
authz
|
||||
.requestMatchers(HttpMethod.POST, "/api/authenticate").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/authenticate").permitAll()
|
||||
.requestMatchers("/api/register").permitAll()
|
||||
.requestMatchers("/api/activate").permitAll()
|
||||
.requestMatchers("/api/account/reset-password/init").permitAll()
|
||||
.requestMatchers("/api/account/reset-password/finish").permitAll()
|
||||
.requestMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
.requestMatchers("/v3/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.requestMatchers("/management/health").permitAll()
|
||||
.requestMatchers("/management/health/**").permitAll()
|
||||
.requestMatchers("/management/info").permitAll()
|
||||
.requestMatchers("/management/prometheus").permitAll()
|
||||
.requestMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
)
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.exceptionHandling(exceptions ->
|
||||
exceptions
|
||||
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
|
||||
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));
|
||||
if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
|
||||
http.authorizeHttpRequests(authz -> authz.requestMatchers("/h2-console/**").permitAll());
|
||||
}
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import static com.sisvietnamvn.web.security.SecurityUtils.JWT_ALGORITHM;
|
||||
|
||||
import com.nimbusds.jose.jwk.source.ImmutableSecret;
|
||||
import com.nimbusds.jose.util.Base64;
|
||||
import com.sisvietnamvn.web.management.SecurityMetersService;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
|
||||
@Configuration
|
||||
public class SecurityJwtConfiguration {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SecurityJwtConfiguration.class);
|
||||
|
||||
@Value("${jhipster.security.authentication.jwt.base64-secret}")
|
||||
private String jwtKey;
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(SecurityMetersService metersService) {
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(getSecretKey()).macAlgorithm(JWT_ALGORITHM).build();
|
||||
return token -> {
|
||||
try {
|
||||
return jwtDecoder.decode(token);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("Invalid signature")) {
|
||||
metersService.trackTokenInvalidSignature();
|
||||
} else if (e.getMessage().contains("Jwt expired at")) {
|
||||
metersService.trackTokenExpired();
|
||||
} else if (
|
||||
e.getMessage().contains("Invalid JWT serialization") ||
|
||||
e.getMessage().contains("Malformed token") ||
|
||||
e.getMessage().contains("Invalid unsecured/JWS/JWE")
|
||||
) {
|
||||
metersService.trackTokenMalformed();
|
||||
} else {
|
||||
LOG.error("Unknown JWT error {}", e.getMessage());
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtEncoder jwtEncoder() {
|
||||
return new NimbusJwtEncoder(new ImmutableSecret<>(getSecretKey()));
|
||||
}
|
||||
|
||||
private SecretKey getSecretKey() {
|
||||
byte[] keyBytes = Base64.from(jwtKey).decode();
|
||||
return new SecretKeySpec(keyBytes, 0, keyBytes.length, JWT_ALGORITHM.getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import jakarta.servlet.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.web.server.*;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.Profiles;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
import tech.jhipster.config.h2.H2ConfigurationHelper;
|
||||
|
||||
/**
|
||||
* Configuration of web application with Servlet 3.0 APIs.
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfigurer implements ServletContextInitializer {
|
||||
|
||||
private static 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) {
|
||||
if (env.getActiveProfiles().length != 0) {
|
||||
LOG.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
|
||||
}
|
||||
|
||||
if (h2ConsoleIsEnabled(env)) {
|
||||
initH2Console(servletContext);
|
||||
}
|
||||
LOG.info("Web application fully configured");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = jHipsterProperties.getCors();
|
||||
if (!CollectionUtils.isEmpty(config.getAllowedOrigins()) || !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) {
|
||||
LOG.debug("Registering CORS filter");
|
||||
source.registerCorsConfiguration("/api/**", config);
|
||||
source.registerCorsConfiguration("/management/**", config);
|
||||
source.registerCorsConfiguration("/v3/api-docs", config);
|
||||
source.registerCorsConfiguration("/swagger-ui/**", config);
|
||||
}
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
|
||||
private boolean h2ConsoleIsEnabled(Environment env) {
|
||||
return (
|
||||
env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) &&
|
||||
"true".equals(env.getProperty("spring.h2.console.enabled"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes H2 console.
|
||||
*/
|
||||
private void initH2Console(ServletContext servletContext) {
|
||||
LOG.info("Initialize H2 console");
|
||||
H2ConfigurationHelper.initH2Console(servletContext);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application configuration.
|
||||
*/
|
||||
package com.sisvietnamvn.web.config;
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.sisvietnamvn.web.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Base abstract class for entities which will hold definitions for created, last modified, created by,
|
||||
* last modified by attributes.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@JsonIgnoreProperties(value = { "createdBy", "createdDate", "lastModifiedBy", "lastModifiedDate" }, allowGetters = true)
|
||||
public abstract class AbstractAuditingEntity<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public abstract T getId();
|
||||
|
||||
@CreatedBy
|
||||
@Column(name = "created_by", nullable = false, length = 50, updatable = false)
|
||||
private String createdBy;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_date", updatable = false)
|
||||
private Instant createdDate = Instant.now();
|
||||
|
||||
@LastModifiedBy
|
||||
@Column(name = "last_modified_by", length = 50)
|
||||
private String lastModifiedBy;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "last_modified_date")
|
||||
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,99 @@
|
||||
package com.sisvietnamvn.web.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
|
||||
/**
|
||||
* A Authority.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "jhi_authority")
|
||||
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
|
||||
@JsonIgnoreProperties(value = { "new", "id" })
|
||||
@SuppressWarnings("common-java:DuplicatedBlocks")
|
||||
public class Authority implements Serializable, Persistable<String> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotNull
|
||||
@Size(max = 50)
|
||||
@Id
|
||||
@Column(name = "name", length = 50, nullable = false)
|
||||
private String name;
|
||||
|
||||
@org.springframework.data.annotation.Transient
|
||||
@Transient
|
||||
private boolean isPersisted;
|
||||
|
||||
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Authority name(String name) {
|
||||
this.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
@PostPersist
|
||||
public void updateEntityState() {
|
||||
this.setIsPersisted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@org.springframework.data.annotation.Transient
|
||||
@Transient
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return !this.isPersisted;
|
||||
}
|
||||
|
||||
public Authority setIsPersisted() {
|
||||
this.isPersisted = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Authority)) {
|
||||
return false;
|
||||
}
|
||||
return getName() != null && getName().equals(((Authority) o).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(getName());
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Authority{" +
|
||||
"name=" + getName() +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.sisvietnamvn.web.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.sisvietnamvn.web.config.Constants;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hibernate.annotations.BatchSize;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
|
||||
/**
|
||||
* A user.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "jhi_user")
|
||||
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
|
||||
public class User extends AbstractAuditingEntity<Long> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
|
||||
@SequenceGenerator(name = "sequenceGenerator")
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
@Pattern(regexp = Constants.LOGIN_REGEX)
|
||||
@Size(min = 1, max = 50)
|
||||
@Column(name = "login", 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(name = "email", length = 254, unique = true)
|
||||
private String email;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "activated", nullable = false)
|
||||
private boolean activated = false;
|
||||
|
||||
@Size(min = 2, max = 10)
|
||||
@Column(name = "lang_key", length = 10)
|
||||
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") }
|
||||
)
|
||||
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
|
||||
@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 isActivated() {
|
||||
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 instanceof User)) {
|
||||
return false;
|
||||
}
|
||||
return id != null && id.equals(((User) o).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@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 @@
|
||||
/**
|
||||
* Domain objects.
|
||||
*/
|
||||
package com.sisvietnamvn.web.domain;
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.sisvietnamvn.web.management;
|
||||
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SecurityMetersService {
|
||||
|
||||
public static final String INVALID_TOKENS_METER_NAME = "security.authentication.invalid-tokens";
|
||||
public static final String INVALID_TOKENS_METER_DESCRIPTION =
|
||||
"Indicates validation error count of the tokens presented by the clients.";
|
||||
public static final String INVALID_TOKENS_METER_BASE_UNIT = "errors";
|
||||
public static final String INVALID_TOKENS_METER_CAUSE_DIMENSION = "cause";
|
||||
|
||||
private final Counter tokenInvalidSignatureCounter;
|
||||
private final Counter tokenExpiredCounter;
|
||||
private final Counter tokenUnsupportedCounter;
|
||||
private final Counter tokenMalformedCounter;
|
||||
|
||||
public SecurityMetersService(MeterRegistry registry) {
|
||||
this.tokenInvalidSignatureCounter = invalidTokensCounterForCauseBuilder("invalid-signature").register(registry);
|
||||
this.tokenExpiredCounter = invalidTokensCounterForCauseBuilder("expired").register(registry);
|
||||
this.tokenUnsupportedCounter = invalidTokensCounterForCauseBuilder("unsupported").register(registry);
|
||||
this.tokenMalformedCounter = invalidTokensCounterForCauseBuilder("malformed").register(registry);
|
||||
}
|
||||
|
||||
private Counter.Builder invalidTokensCounterForCauseBuilder(String cause) {
|
||||
return Counter.builder(INVALID_TOKENS_METER_NAME)
|
||||
.baseUnit(INVALID_TOKENS_METER_BASE_UNIT)
|
||||
.description(INVALID_TOKENS_METER_DESCRIPTION)
|
||||
.tag(INVALID_TOKENS_METER_CAUSE_DIMENSION, cause);
|
||||
}
|
||||
|
||||
public void trackTokenInvalidSignature() {
|
||||
this.tokenInvalidSignatureCounter.increment();
|
||||
}
|
||||
|
||||
public void trackTokenExpired() {
|
||||
this.tokenExpiredCounter.increment();
|
||||
}
|
||||
|
||||
public void trackTokenUnsupported() {
|
||||
this.tokenUnsupportedCounter.increment();
|
||||
}
|
||||
|
||||
public void trackTokenMalformed() {
|
||||
this.tokenMalformedCounter.increment();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application management.
|
||||
*/
|
||||
package com.sisvietnamvn.web.management;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application root.
|
||||
*/
|
||||
package com.sisvietnamvn.web;
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.sisvietnamvn.web.repository;
|
||||
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import org.springframework.data.jpa.repository.*;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the Authority entity.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Repository
|
||||
public interface AuthorityRepository extends JpaRepository<Authority, String> {}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.sisvietnamvn.web.repository;
|
||||
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.domain.*;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the {@link 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> findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant dateTime);
|
||||
Optional<User> findOneByResetKey(String resetKey);
|
||||
Optional<User> findOneByEmailIgnoreCase(String email);
|
||||
Optional<User> findOneByLogin(String login);
|
||||
|
||||
@EntityGraph(attributePaths = "authorities")
|
||||
@Cacheable(cacheNames = USERS_BY_LOGIN_CACHE, unless = "#result == null")
|
||||
Optional<User> findOneWithAuthoritiesByLogin(String login);
|
||||
|
||||
@EntityGraph(attributePaths = "authorities")
|
||||
@Cacheable(cacheNames = USERS_BY_EMAIL_CACHE, unless = "#result == null")
|
||||
Optional<User> findOneWithAuthoritiesByEmailIgnoreCase(String email);
|
||||
|
||||
Page<User> findAllByIdNotNullAndActivatedIsTrue(Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Repository layer.
|
||||
*/
|
||||
package com.sisvietnamvn.web.repository;
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.sisvietnamvn.web.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() {}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.repository.UserRepository;
|
||||
import java.util.*;
|
||||
import org.hibernate.validator.internal.constraintvalidators.bv.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;
|
||||
|
||||
/**
|
||||
* Authenticate a user from the database.
|
||||
*/
|
||||
@Component("userDetailsService")
|
||||
public class DomainUserDetailsService implements UserDetailsService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DomainUserDetailsService.class);
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public DomainUserDetailsService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public UserDetails loadUserByUsername(final String login) {
|
||||
LOG.debug("Authenticating {}", login);
|
||||
|
||||
if (new EmailValidator().isValid(login, null)) {
|
||||
return userRepository
|
||||
.findOneWithAuthoritiesByEmailIgnoreCase(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.isActivated()) {
|
||||
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
|
||||
}
|
||||
return UserWithId.fromUser(user);
|
||||
}
|
||||
|
||||
public static class UserWithId extends org.springframework.security.core.userdetails.User {
|
||||
|
||||
private final Long id;
|
||||
|
||||
public UserWithId(String login, String password, Collection<? extends GrantedAuthority> authorities, Long id) {
|
||||
super(login, password, authorities);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
public static UserWithId fromUser(User user) {
|
||||
return new UserWithId(
|
||||
user.getLogin(),
|
||||
user.getPassword(),
|
||||
user.getAuthorities().stream().map(Authority::getName).map(SimpleGrantedAuthority::new).toList(),
|
||||
user.getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
/**
|
||||
* Utility class for Spring Security.
|
||||
*/
|
||||
public final class SecurityUtils {
|
||||
|
||||
public static final MacAlgorithm JWT_ALGORITHM = MacAlgorithm.HS512;
|
||||
|
||||
public static final String AUTHORITIES_CLAIM = "auth";
|
||||
|
||||
public static final String USER_ID_CLAIM = "userId";
|
||||
|
||||
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(extractPrincipal(securityContext.getAuthentication()));
|
||||
}
|
||||
|
||||
private static String extractPrincipal(Authentication authentication) {
|
||||
if (authentication == null) {
|
||||
return null;
|
||||
} else if (authentication.getPrincipal() instanceof UserDetails springSecurityUser) {
|
||||
return springSecurityUser.getUsername();
|
||||
} else if (authentication.getPrincipal() instanceof Jwt jwt) {
|
||||
return jwt.getSubject();
|
||||
} else if (authentication.getPrincipal() instanceof String s) {
|
||||
return s;
|
||||
}
|
||||
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()).map(SecurityUtils::extractCredentials);
|
||||
}
|
||||
|
||||
private static String extractCredentials(Authentication authentication) {
|
||||
if (authentication.getCredentials() instanceof String token) {
|
||||
return token;
|
||||
} else if (authentication.getCredentials() instanceof Jwt jwt) {
|
||||
return jwt.getTokenValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Id of the current user.
|
||||
*
|
||||
* @return the Id of the current user.
|
||||
*/
|
||||
public static Optional<Long> getCurrentUserId() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
return Optional.ofNullable(securityContext.getAuthentication())
|
||||
.filter(authentication -> authentication.getPrincipal() instanceof ClaimAccessor)
|
||||
.map(authentication -> (ClaimAccessor) authentication.getPrincipal())
|
||||
.map(principal -> principal.getClaim(USER_ID_CLAIM));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is authenticated.
|
||||
*
|
||||
* @return true if the user is authenticated, false otherwise.
|
||||
*/
|
||||
public static boolean isAuthenticated() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication != null && getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has any of the authorities.
|
||||
*
|
||||
* @param authorities the authorities to check.
|
||||
* @return true if the current user has any of the authorities, false otherwise.
|
||||
*/
|
||||
public static boolean hasCurrentUserAnyOfAuthorities(String... authorities) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return (authentication != null && getAuthorities(authentication).anyMatch(authority -> List.of(authorities).contains(authority)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has none of the authorities.
|
||||
*
|
||||
* @param authorities the authorities to check.
|
||||
* @return true if the current user has none of the authorities, false otherwise.
|
||||
*/
|
||||
public static boolean hasCurrentUserNoneOfAuthorities(String... authorities) {
|
||||
return !hasCurrentUserAnyOfAuthorities(authorities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has a specific authority.
|
||||
*
|
||||
* @param authority the authority to check.
|
||||
* @return true if the current user has the authority, false otherwise.
|
||||
*/
|
||||
public static boolean hasCurrentUserThisAuthority(String authority) {
|
||||
return hasCurrentUserAnyOfAuthorities(authority);
|
||||
}
|
||||
|
||||
private static Stream<String> getAuthorities(Authentication authentication) {
|
||||
return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import com.sisvietnamvn.web.config.Constants;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Implementation of {@link 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));
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import java.io.Serial;
|
||||
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 {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UserNotActivatedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UserNotActivatedException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application security utilities.
|
||||
*/
|
||||
package com.sisvietnamvn.web.security;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.sisvietnamvn.web.service;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
public class EmailAlreadyUsedException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EmailAlreadyUsedException() {
|
||||
super("Email is already in use!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.sisvietnamvn.web.service;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
public class InvalidPasswordException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidPasswordException() {
|
||||
super("Incorrect password");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.sisvietnamvn.web.service;
|
||||
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.mail.MailException;
|
||||
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.spring6.SpringTemplateEngine;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
/**
|
||||
* Service for sending emails asynchronously.
|
||||
* <p>
|
||||
* We use the {@link Async} annotation to send emails asynchronously.
|
||||
*/
|
||||
@Service
|
||||
public class MailService {
|
||||
|
||||
private static 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) {
|
||||
sendEmailSync(to, subject, content, isMultipart, isHtml);
|
||||
}
|
||||
|
||||
private void sendEmailSync(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 (MailException | MessagingException e) {
|
||||
LOG.warn("Email could not be sent to user '{}'", to, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
public void sendEmailFromTemplate(User user, String templateName, String titleKey) {
|
||||
sendEmailFromTemplateSync(user, templateName, titleKey);
|
||||
}
|
||||
|
||||
private void sendEmailFromTemplateSync(User user, String templateName, String titleKey) {
|
||||
if (user.getEmail() == null) {
|
||||
LOG.debug("Email doesn't exist for user '{}'", user.getLogin());
|
||||
return;
|
||||
}
|
||||
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);
|
||||
sendEmailSync(user.getEmail(), subject, content, false, true);
|
||||
}
|
||||
|
||||
@Async
|
||||
public void sendActivationEmail(User user) {
|
||||
LOG.debug("Sending activation email to '{}'", user.getEmail());
|
||||
sendEmailFromTemplateSync(user, "mail/activationEmail", "email.activation.title");
|
||||
}
|
||||
|
||||
@Async
|
||||
public void sendCreationEmail(User user) {
|
||||
LOG.debug("Sending creation email to '{}'", user.getEmail());
|
||||
sendEmailFromTemplateSync(user, "mail/creationEmail", "email.activation.title");
|
||||
}
|
||||
|
||||
@Async
|
||||
public void sendPasswordResetMail(User user) {
|
||||
LOG.debug("Sending password reset email to '{}'", user.getEmail());
|
||||
sendEmailFromTemplateSync(user, "mail/passwordResetEmail", "email.reset.title");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package com.sisvietnamvn.web.service;
|
||||
|
||||
import com.sisvietnamvn.web.config.Constants;
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.repository.AuthorityRepository;
|
||||
import com.sisvietnamvn.web.repository.UserRepository;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
import com.sisvietnamvn.web.security.SecurityUtils;
|
||||
import com.sisvietnamvn.web.service.dto.AdminUserDTO;
|
||||
import com.sisvietnamvn.web.service.dto.UserDTO;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
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 tech.jhipster.security.RandomUtil;
|
||||
|
||||
/**
|
||||
* Service class for managing users.
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class UserService {
|
||||
|
||||
private static 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().minus(1, ChronoUnit.DAYS)))
|
||||
.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::isActivated)
|
||||
.map(user -> {
|
||||
user.setResetKey(RandomUtil.generateResetKey());
|
||||
user.setResetDate(Instant.now());
|
||||
this.clearUserCaches(user);
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
public User registerUser(AdminUserDTO userDTO, String password) {
|
||||
userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).ifPresent(existingUser -> {
|
||||
boolean removed = removeNonActivatedUser(existingUser);
|
||||
if (!removed) {
|
||||
throw new UsernameAlreadyUsedException();
|
||||
}
|
||||
});
|
||||
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());
|
||||
if (userDTO.getEmail() != null) {
|
||||
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.isActivated()) {
|
||||
return false;
|
||||
}
|
||||
userRepository.delete(existingUser);
|
||||
userRepository.flush();
|
||||
this.clearUserCaches(existingUser);
|
||||
return true;
|
||||
}
|
||||
|
||||
public User createUser(AdminUserDTO userDTO) {
|
||||
User user = new User();
|
||||
user.setLogin(userDTO.getLogin().toLowerCase());
|
||||
user.setFirstName(userDTO.getFirstName());
|
||||
user.setLastName(userDTO.getLastName());
|
||||
if (userDTO.getEmail() != null) {
|
||||
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 all information for a specific user, and return the modified user.
|
||||
*
|
||||
* @param userDTO user to update.
|
||||
* @return updated user.
|
||||
*/
|
||||
public Optional<AdminUserDTO> updateUser(AdminUserDTO 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());
|
||||
if (userDTO.getEmail() != null) {
|
||||
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);
|
||||
userRepository.save(user);
|
||||
this.clearUserCaches(user);
|
||||
LOG.debug("Changed Information for User: {}", user);
|
||||
return user;
|
||||
})
|
||||
.map(AdminUserDTO::new);
|
||||
}
|
||||
|
||||
public void deleteUser(String login) {
|
||||
userRepository.findOneByLogin(login).ifPresent(user -> {
|
||||
userRepository.delete(user);
|
||||
this.clearUserCaches(user);
|
||||
LOG.debug("Deleted User: {}", 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);
|
||||
if (email != null) {
|
||||
user.setEmail(email.toLowerCase());
|
||||
}
|
||||
user.setLangKey(langKey);
|
||||
user.setImageUrl(imageUrl);
|
||||
userRepository.save(user);
|
||||
this.clearUserCaches(user);
|
||||
LOG.debug("Changed Information for User: {}", user);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
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<AdminUserDTO> getAllManagedUsers(Pageable pageable) {
|
||||
return userRepository.findAll(pageable).map(AdminUserDTO::new);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<UserDTO> getAllPublicUsers(Pageable pageable) {
|
||||
return userRepository.findAllByIdNotNullAndActivatedIsTrue(pageable).map(UserDTO::new);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<User> getUserWithAuthoritiesByLogin(String login) {
|
||||
return userRepository.findOneWithAuthoritiesByLogin(login);
|
||||
}
|
||||
|
||||
@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 every day, at 01:00 (am).
|
||||
*/
|
||||
@Scheduled(cron = "0 0 1 * * ?")
|
||||
public void removeNotActivatedUsers() {
|
||||
userRepository
|
||||
.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS))
|
||||
.forEach(user -> {
|
||||
LOG.debug("Deleting not activated user {}", user.getLogin());
|
||||
userRepository.delete(user);
|
||||
this.clearUserCaches(user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the authorities.
|
||||
* @return a list of all the authorities.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public List<String> getAuthorities() {
|
||||
return authorityRepository.findAll().stream().map(Authority::getName).toList();
|
||||
}
|
||||
|
||||
private void clearUserCaches(User user) {
|
||||
Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)).evictIfPresent(user.getLogin());
|
||||
if (user.getEmail() != null) {
|
||||
Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evictIfPresent(user.getEmail());
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.sisvietnamvn.web.service;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
public class UsernameAlreadyUsedException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UsernameAlreadyUsedException() {
|
||||
super("Login name already used!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.sisvietnamvn.web.service.dto;
|
||||
|
||||
import com.sisvietnamvn.web.config.Constants;
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A DTO representing a user, with his authorities.
|
||||
*/
|
||||
public class AdminUserDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
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 = 10)
|
||||
private String langKey;
|
||||
|
||||
private String createdBy;
|
||||
|
||||
private Instant createdDate;
|
||||
|
||||
private String lastModifiedBy;
|
||||
|
||||
private Instant lastModifiedDate;
|
||||
|
||||
private Set<String> authorities;
|
||||
|
||||
public AdminUserDTO() {
|
||||
// Empty constructor needed for Jackson.
|
||||
}
|
||||
|
||||
public AdminUserDTO(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.isActivated();
|
||||
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;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AdminUserDTO{" +
|
||||
"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,41 @@
|
||||
package com.sisvietnamvn.web.service.dto;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A DTO representing a password change required data - current and new password.
|
||||
*/
|
||||
public class PasswordChangeDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.sisvietnamvn.web.service.dto;
|
||||
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A DTO representing a user, with only the public attributes.
|
||||
*/
|
||||
public class UserDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
|
||||
private String login;
|
||||
|
||||
public UserDTO() {
|
||||
// Empty constructor needed for Jackson.
|
||||
}
|
||||
|
||||
public UserDTO(User user) {
|
||||
this.id = user.getId();
|
||||
// Customize it here if you need, or not, firstName/lastName/etc
|
||||
this.login = user.getLogin();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserDTO userDTO = (UserDTO) o;
|
||||
if (userDTO.getId() == null || getId() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(getId(), userDTO.getId()) && Objects.equals(getLogin(), userDTO.getLogin());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getId(), getLogin());
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserDTO{" +
|
||||
"id='" + id + '\'' +
|
||||
", login='" + login + '\'' +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Data transfer objects for rest mapping.
|
||||
*/
|
||||
package com.sisvietnamvn.web.service.dto;
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.sisvietnamvn.web.service.mapper;
|
||||
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.service.dto.AdminUserDTO;
|
||||
import com.sisvietnamvn.web.service.dto.UserDTO;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Mapper for the entity {@link User} and its DTO called {@link 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).toList();
|
||||
}
|
||||
|
||||
public UserDTO userToUserDTO(User user) {
|
||||
return new UserDTO(user);
|
||||
}
|
||||
|
||||
public List<AdminUserDTO> usersToAdminUserDTOs(List<User> users) {
|
||||
return users.stream().filter(Objects::nonNull).map(this::userToAdminUserDTO).toList();
|
||||
}
|
||||
|
||||
public AdminUserDTO userToAdminUserDTO(User user) {
|
||||
return new AdminUserDTO(user);
|
||||
}
|
||||
|
||||
public List<User> userDTOsToUsers(List<AdminUserDTO> userDTOs) {
|
||||
return userDTOs.stream().filter(Objects::nonNull).map(this::userDTOToUser).toList();
|
||||
}
|
||||
|
||||
public User userDTOToUser(AdminUserDTO 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.setCreatedBy(userDTO.getCreatedBy());
|
||||
user.setCreatedDate(userDTO.getCreatedDate());
|
||||
user.setLastModifiedBy(userDTO.getLastModifiedBy());
|
||||
user.setLastModifiedDate(userDTO.getLastModifiedDate());
|
||||
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;
|
||||
}
|
||||
|
||||
@Named("id")
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "id", source = "id")
|
||||
public UserDTO toDtoId(User user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
UserDTO userDto = new UserDTO();
|
||||
userDto.setId(user.getId());
|
||||
return userDto;
|
||||
}
|
||||
|
||||
@Named("idSet")
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "id", source = "id")
|
||||
public Set<UserDTO> toDtoIdSet(Set<User> users) {
|
||||
if (users == null) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
Set<UserDTO> userSet = new HashSet<>();
|
||||
for (User userEntity : users) {
|
||||
userSet.add(this.toDtoId(userEntity));
|
||||
}
|
||||
|
||||
return userSet;
|
||||
}
|
||||
|
||||
@Named("login")
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "id", source = "id")
|
||||
@Mapping(target = "login", source = "login")
|
||||
public UserDTO toDtoLogin(User user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
UserDTO userDto = new UserDTO();
|
||||
userDto.setId(user.getId());
|
||||
userDto.setLogin(user.getLogin());
|
||||
return userDto;
|
||||
}
|
||||
|
||||
@Named("loginSet")
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "id", source = "id")
|
||||
@Mapping(target = "login", source = "login")
|
||||
public Set<UserDTO> toDtoLoginSet(Set<User> users) {
|
||||
if (users == null) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
Set<UserDTO> userSet = new HashSet<>();
|
||||
for (User userEntity : users) {
|
||||
userSet.add(this.toDtoLogin(userEntity));
|
||||
}
|
||||
|
||||
return userSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Data transfer objects mappers.
|
||||
*/
|
||||
package com.sisvietnamvn.web.service.mapper;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Service layer.
|
||||
*/
|
||||
package com.sisvietnamvn.web.service;
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.sisvietnamvn.web.web.rest;
|
||||
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.repository.UserRepository;
|
||||
import com.sisvietnamvn.web.security.SecurityUtils;
|
||||
import com.sisvietnamvn.web.service.MailService;
|
||||
import com.sisvietnamvn.web.service.UserService;
|
||||
import com.sisvietnamvn.web.service.dto.AdminUserDTO;
|
||||
import com.sisvietnamvn.web.service.dto.PasswordChangeDTO;
|
||||
import com.sisvietnamvn.web.web.rest.errors.*;
|
||||
import com.sisvietnamvn.web.web.rest.vm.KeyAndPasswordVM;
|
||||
import com.sisvietnamvn.web.web.rest.vm.ManagedUserVM;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.*;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* REST controller for managing the current user's account.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class AccountResource {
|
||||
|
||||
private static class AccountResourceException extends RuntimeException {
|
||||
|
||||
private AccountResourceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /register} : register the user.
|
||||
*
|
||||
* @param managedUserVM the managed user View Model.
|
||||
* @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect.
|
||||
* @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used.
|
||||
* @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already used.
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) {
|
||||
if (isPasswordLengthInvalid(managedUserVM.getPassword())) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
User user = userService.registerUser(managedUserVM, managedUserVM.getPassword());
|
||||
mailService.sendActivationEmail(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /activate} : activate the registered user.
|
||||
*
|
||||
* @param key the activation key.
|
||||
* @throws RuntimeException {@code 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 AccountResourceException("No user was found for this activation key");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /account} : get the current user.
|
||||
*
|
||||
* @return the current user.
|
||||
* @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be returned.
|
||||
*/
|
||||
@GetMapping("/account")
|
||||
public AdminUserDTO getAccount() {
|
||||
return userService
|
||||
.getUserWithAuthorities()
|
||||
.map(AdminUserDTO::new)
|
||||
.orElseThrow(() -> new AccountResourceException("User could not be found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /account} : update the current user information.
|
||||
*
|
||||
* @param userDTO the current user information.
|
||||
* @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used.
|
||||
* @throws RuntimeException {@code 500 (Internal Server Error)} if the user login wasn't found.
|
||||
*/
|
||||
@PostMapping("/account")
|
||||
public void saveAccount(@Valid @RequestBody AdminUserDTO userDTO) {
|
||||
String userLogin = SecurityUtils.getCurrentUserLogin().orElseThrow(() ->
|
||||
new AccountResourceException("Current user login not found")
|
||||
);
|
||||
Optional<User> existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail());
|
||||
if (existingUser.isPresent() && (!existingUser.orElseThrow().getLogin().equalsIgnoreCase(userLogin))) {
|
||||
throw new EmailAlreadyUsedException();
|
||||
}
|
||||
Optional<User> user = userRepository.findOneByLogin(userLogin);
|
||||
if (!user.isPresent()) {
|
||||
throw new AccountResourceException("User could not be found");
|
||||
}
|
||||
userService.updateUser(
|
||||
userDTO.getFirstName(),
|
||||
userDTO.getLastName(),
|
||||
userDTO.getEmail(),
|
||||
userDTO.getLangKey(),
|
||||
userDTO.getImageUrl()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /account/change-password} : changes the current user's password.
|
||||
*
|
||||
* @param passwordChangeDto current and new password.
|
||||
* @throws InvalidPasswordException {@code 400 (Bad Request)} if the new password is incorrect.
|
||||
*/
|
||||
@PostMapping(path = "/account/change-password")
|
||||
public void changePassword(@RequestBody PasswordChangeDTO passwordChangeDto) {
|
||||
if (isPasswordLengthInvalid(passwordChangeDto.getNewPassword())) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
userService.changePassword(passwordChangeDto.getCurrentPassword(), passwordChangeDto.getNewPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /account/reset-password/init} : Send an email to reset the password of the user.
|
||||
*
|
||||
* @param mail the mail of the user.
|
||||
*/
|
||||
@PostMapping(path = "/account/reset-password/init")
|
||||
public void requestPasswordReset(@RequestBody String mail) {
|
||||
Optional<User> user = userService.requestPasswordReset(mail);
|
||||
if (user.isPresent()) {
|
||||
mailService.sendPasswordResetMail(user.orElseThrow());
|
||||
} else {
|
||||
// Pretend the request has been successful to prevent checking which emails really exist
|
||||
// but log that an invalid attempt has been made
|
||||
LOG.warn("Password reset requested for non existing mail");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /account/reset-password/finish} : Finish to reset the password of the user.
|
||||
*
|
||||
* @param keyAndPassword the generated key and the new password.
|
||||
* @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect.
|
||||
* @throws RuntimeException {@code 500 (Internal Server Error)} if the password could not be reset.
|
||||
*/
|
||||
@PostMapping(path = "/account/reset-password/finish")
|
||||
public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) {
|
||||
if (isPasswordLengthInvalid(keyAndPassword.getNewPassword())) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
Optional<User> user = userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey());
|
||||
|
||||
if (!user.isPresent()) {
|
||||
throw new AccountResourceException("No user was found for this reset key");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPasswordLengthInvalid(String password) {
|
||||
return (
|
||||
StringUtils.isEmpty(password) ||
|
||||
password.length() < ManagedUserVM.PASSWORD_MIN_LENGTH ||
|
||||
password.length() > ManagedUserVM.PASSWORD_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.sisvietnamvn.web.web.rest;
|
||||
|
||||
import static com.sisvietnamvn.web.security.SecurityUtils.AUTHORITIES_CLAIM;
|
||||
import static com.sisvietnamvn.web.security.SecurityUtils.JWT_ALGORITHM;
|
||||
import static com.sisvietnamvn.web.security.SecurityUtils.USER_ID_CLAIM;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.sisvietnamvn.web.security.DomainUserDetailsService.UserWithId;
|
||||
import com.sisvietnamvn.web.web.rest.vm.LoginVM;
|
||||
import jakarta.validation.Valid;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.jwt.JwsHeader;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Controller to authenticate users.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class AuthenticateController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthenticateController.class);
|
||||
|
||||
private final JwtEncoder jwtEncoder;
|
||||
|
||||
@Value("${jhipster.security.authentication.jwt.token-validity-in-seconds:0}")
|
||||
private long tokenValidityInSeconds;
|
||||
|
||||
@Value("${jhipster.security.authentication.jwt.token-validity-in-seconds-for-remember-me:0}")
|
||||
private long tokenValidityInSecondsForRememberMe;
|
||||
|
||||
private final AuthenticationManagerBuilder authenticationManagerBuilder;
|
||||
|
||||
public AuthenticateController(JwtEncoder jwtEncoder, AuthenticationManagerBuilder authenticationManagerBuilder) {
|
||||
this.jwtEncoder = jwtEncoder;
|
||||
this.authenticationManagerBuilder = authenticationManagerBuilder;
|
||||
}
|
||||
|
||||
@PostMapping("/authenticate")
|
||||
public ResponseEntity<JWTToken> authorize(@Valid @RequestBody LoginVM loginVM) {
|
||||
var authenticationToken = new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());
|
||||
|
||||
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
String jwt = this.createToken(authentication, loginVM.isRememberMe());
|
||||
var httpHeaders = new HttpHeaders();
|
||||
httpHeaders.setBearerAuth(jwt);
|
||||
return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /authenticate} : check if the user is authenticated.
|
||||
*
|
||||
* @return the {@link ResponseEntity} with status {@code 204 (No Content)},
|
||||
* or with status {@code 401 (Unauthorized)} if not authenticated.
|
||||
*/
|
||||
@GetMapping("/authenticate")
|
||||
public ResponseEntity<Void> isAuthenticated(Principal principal) {
|
||||
LOG.debug("REST request to check if the current user is authenticated");
|
||||
return ResponseEntity.status(principal == null ? HttpStatus.UNAUTHORIZED : HttpStatus.NO_CONTENT).build();
|
||||
}
|
||||
|
||||
public String createToken(Authentication authentication, boolean rememberMe) {
|
||||
String authorities = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(" "));
|
||||
|
||||
var now = Instant.now();
|
||||
Instant validity;
|
||||
if (rememberMe) {
|
||||
validity = now.plus(this.tokenValidityInSecondsForRememberMe, ChronoUnit.SECONDS);
|
||||
} else {
|
||||
validity = now.plus(this.tokenValidityInSeconds, ChronoUnit.SECONDS);
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
JwtClaimsSet.Builder builder = JwtClaimsSet.builder()
|
||||
.issuedAt(now)
|
||||
.expiresAt(validity)
|
||||
.subject(authentication.getName())
|
||||
.claim(AUTHORITIES_CLAIM, authorities);
|
||||
if (authentication.getPrincipal() instanceof UserWithId user) {
|
||||
builder.claim(USER_ID_CLAIM, user.getId());
|
||||
}
|
||||
|
||||
JwsHeader jwsHeader = JwsHeader.with(JWT_ALGORITHM).build();
|
||||
return this.jwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, builder.build())).getTokenValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,103 @@
|
||||
package com.sisvietnamvn.web.web.rest;
|
||||
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import com.sisvietnamvn.web.repository.AuthorityRepository;
|
||||
import com.sisvietnamvn.web.web.rest.errors.BadRequestAlertException;
|
||||
import jakarta.validation.Valid;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.jhipster.web.util.HeaderUtil;
|
||||
import tech.jhipster.web.util.ResponseUtil;
|
||||
|
||||
/**
|
||||
* REST controller for managing {@link com.sisvietnamvn.web.domain.Authority}.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/authorities")
|
||||
@Transactional
|
||||
public class AuthorityResource {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthorityResource.class);
|
||||
|
||||
private static final String ENTITY_NAME = "adminAuthority";
|
||||
|
||||
@Value("${jhipster.clientApp.name:sisvietnamvn}")
|
||||
private String applicationName;
|
||||
|
||||
private final AuthorityRepository authorityRepository;
|
||||
|
||||
public AuthorityResource(AuthorityRepository authorityRepository) {
|
||||
this.authorityRepository = authorityRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /authorities} : Create a new authority.
|
||||
*
|
||||
* @param authority the authority to create.
|
||||
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new authority, or with status {@code 400 (Bad Request)} if the authority has already an ID.
|
||||
* @throws URISyntaxException if the Location URI syntax is incorrect.
|
||||
*/
|
||||
@PostMapping("")
|
||||
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
|
||||
public ResponseEntity<Authority> createAuthority(@Valid @RequestBody Authority authority) throws URISyntaxException {
|
||||
LOG.debug("REST request to save Authority : {}", authority);
|
||||
if (authorityRepository.existsById(authority.getName())) {
|
||||
throw new BadRequestAlertException("authority already exists", ENTITY_NAME, "idexists");
|
||||
}
|
||||
authority = authorityRepository.save(authority);
|
||||
return ResponseEntity.created(new URI("/api/authorities/" + authority.getName()))
|
||||
.headers(HeaderUtil.createEntityCreationAlert(applicationName, false, ENTITY_NAME, authority.getName()))
|
||||
.body(authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /authorities} : get all the Authorities.
|
||||
*
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of Authorities in body.
|
||||
*/
|
||||
@GetMapping("")
|
||||
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
|
||||
public List<Authority> getAllAuthorities() {
|
||||
LOG.debug("REST request to get all Authorities");
|
||||
return authorityRepository.findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /authorities/:id} : get the "id" authority.
|
||||
*
|
||||
* @param id the id of the authority to retrieve.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the authority, or with status {@code 404 (Not Found)}.
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
|
||||
public ResponseEntity<Authority> getAuthority(@PathVariable("id") String id) {
|
||||
LOG.debug("REST request to get Authority : {}", id);
|
||||
Optional<Authority> authority = authorityRepository.findById(id);
|
||||
return ResponseUtil.wrapOrNotFound(authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code DELETE /authorities/:id} : delete the "id" authority.
|
||||
*
|
||||
* @param id the id of the authority to delete.
|
||||
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
|
||||
public ResponseEntity<Void> deleteAuthority(@PathVariable("id") String id) {
|
||||
LOG.debug("REST request to delete Authority : {}", id);
|
||||
authorityRepository.deleteById(id);
|
||||
return ResponseEntity.noContent()
|
||||
.headers(HeaderUtil.createEntityDeletionAlert(applicationName, false, ENTITY_NAME, id))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.sisvietnamvn.web.web.rest;
|
||||
|
||||
import com.sisvietnamvn.web.service.UserService;
|
||||
import com.sisvietnamvn.web.service.dto.UserDTO;
|
||||
import java.util.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import tech.jhipster.web.util.PaginationUtil;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class PublicUserResource {
|
||||
|
||||
private static final List<String> ALLOWED_ORDERED_PROPERTIES = List.of(
|
||||
"id",
|
||||
"login",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"activated",
|
||||
"langKey"
|
||||
);
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PublicUserResource.class);
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public PublicUserResource(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /users} : get all users with only public information - calling this method is allowed for anyone.
|
||||
*
|
||||
* @param pageable the pagination information.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users.
|
||||
*/
|
||||
@GetMapping("/users")
|
||||
public ResponseEntity<List<UserDTO>> getAllPublicUsers(@org.springdoc.core.annotations.ParameterObject Pageable pageable) {
|
||||
LOG.debug("REST request to get all public User names");
|
||||
if (!onlyContainsAllowedProperties(pageable)) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
final Page<UserDTO> page = userService.getAllPublicUsers(pageable);
|
||||
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
|
||||
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
|
||||
}
|
||||
|
||||
private boolean onlyContainsAllowedProperties(Pageable pageable) {
|
||||
return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.sisvietnamvn.web.web.rest;
|
||||
|
||||
import com.sisvietnamvn.web.config.Constants;
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.repository.UserRepository;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
import com.sisvietnamvn.web.service.MailService;
|
||||
import com.sisvietnamvn.web.service.UserService;
|
||||
import com.sisvietnamvn.web.service.dto.AdminUserDTO;
|
||||
import com.sisvietnamvn.web.web.rest.errors.BadRequestAlertException;
|
||||
import com.sisvietnamvn.web.web.rest.errors.EmailAlreadyUsedException;
|
||||
import com.sisvietnamvn.web.web.rest.errors.LoginAlreadyUsedException;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
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 org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import tech.jhipster.web.util.HeaderUtil;
|
||||
import tech.jhipster.web.util.PaginationUtil;
|
||||
import tech.jhipster.web.util.ResponseUtil;
|
||||
|
||||
/**
|
||||
* REST controller for managing users.
|
||||
* <p>
|
||||
* This class accesses the {@link com.sisvietnamvn.web.domain.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/admin")
|
||||
public class UserResource {
|
||||
|
||||
private static final List<String> ALLOWED_ORDERED_PROPERTIES = List.of(
|
||||
"id",
|
||||
"login",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"activated",
|
||||
"langKey",
|
||||
"createdBy",
|
||||
"createdDate",
|
||||
"lastModifiedBy",
|
||||
"lastModifiedDate"
|
||||
);
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UserResource.class);
|
||||
|
||||
@Value("${jhipster.clientApp.name:sisvietnamvn}")
|
||||
private String applicationName;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /admin/users} : Creates a new user.
|
||||
* <p>
|
||||
* Creates a new user if the login and email are not already used, and sends a
|
||||
* mail with an activation link.
|
||||
* The user needs to be activated on creation.
|
||||
*
|
||||
* @param userDTO the user to create.
|
||||
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use.
|
||||
* @throws URISyntaxException if the Location URI syntax is incorrect.
|
||||
* @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use.
|
||||
*/
|
||||
@PostMapping("/users")
|
||||
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||
public ResponseEntity<User> createUser(@Valid @RequestBody AdminUserDTO 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/admin/users/" + newUser.getLogin()))
|
||||
.headers(
|
||||
HeaderUtil.createAlert(applicationName, "A user is created with identifier " + newUser.getLogin(), newUser.getLogin())
|
||||
)
|
||||
.body(newUser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code PUT /admin/users} : Updates an existing User.
|
||||
*
|
||||
* @param userDTO the user to update.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated user.
|
||||
* @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already in use.
|
||||
* @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already in use.
|
||||
*/
|
||||
@PutMapping({ "/users", "/users/{login}" })
|
||||
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||
public ResponseEntity<AdminUserDTO> updateUser(
|
||||
@PathVariable(name = "login", required = false) @Pattern(regexp = Constants.LOGIN_REGEX) String login,
|
||||
@Valid @RequestBody AdminUserDTO userDTO
|
||||
) {
|
||||
LOG.debug("REST request to update User : {}", userDTO);
|
||||
Optional<User> existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail());
|
||||
if (existingUser.isPresent() && (!existingUser.orElseThrow().getId().equals(userDTO.getId()))) {
|
||||
throw new EmailAlreadyUsedException();
|
||||
}
|
||||
existingUser = userRepository.findOneByLogin(userDTO.getLogin().toLowerCase());
|
||||
if (existingUser.isPresent() && (!existingUser.orElseThrow().getId().equals(userDTO.getId()))) {
|
||||
throw new LoginAlreadyUsedException();
|
||||
}
|
||||
Optional<AdminUserDTO> updatedUser = userService.updateUser(userDTO);
|
||||
|
||||
return ResponseUtil.wrapOrNotFound(
|
||||
updatedUser,
|
||||
HeaderUtil.createAlert(applicationName, "A user is updated with identifier " + userDTO.getLogin(), userDTO.getLogin())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /admin/users} : get all users with all the details - calling this are only allowed for the administrators.
|
||||
*
|
||||
* @param pageable the pagination information.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users.
|
||||
*/
|
||||
@GetMapping("/users")
|
||||
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||
public ResponseEntity<List<AdminUserDTO>> getAllUsers(@org.springdoc.core.annotations.ParameterObject Pageable pageable) {
|
||||
LOG.debug("REST request to get all User for an admin");
|
||||
if (!onlyContainsAllowedProperties(pageable)) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
final Page<AdminUserDTO> page = userService.getAllManagedUsers(pageable);
|
||||
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
|
||||
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
|
||||
}
|
||||
|
||||
private boolean onlyContainsAllowedProperties(Pageable pageable) {
|
||||
return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /admin/users/:login} : get the "login" user.
|
||||
*
|
||||
* @param login the login of the user to find.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status {@code 404 (Not Found)}.
|
||||
*/
|
||||
@GetMapping("/users/{login}")
|
||||
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||
public ResponseEntity<AdminUserDTO> getUser(@PathVariable("login") @Pattern(regexp = Constants.LOGIN_REGEX) String login) {
|
||||
LOG.debug("REST request to get User : {}", login);
|
||||
return ResponseUtil.wrapOrNotFound(userService.getUserWithAuthoritiesByLogin(login).map(AdminUserDTO::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code DELETE /admin/users/:login} : delete the "login" User.
|
||||
*
|
||||
* @param login the login of the user to delete.
|
||||
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
|
||||
*/
|
||||
@DeleteMapping("/users/{login}")
|
||||
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||
public ResponseEntity<Void> deleteUser(@PathVariable("login") @Pattern(regexp = Constants.LOGIN_REGEX) String login) {
|
||||
LOG.debug("REST request to delete User: {}", login);
|
||||
userService.deleteUser(login);
|
||||
return ResponseEntity.noContent()
|
||||
.headers(HeaderUtil.createAlert(applicationName, "A user is deleted with identifier " + login, login))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.sisvietnamvn.web.web.rest.errors;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.net.URI;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import tech.jhipster.web.rest.errors.ProblemDetailWithCause;
|
||||
import tech.jhipster.web.rest.errors.ProblemDetailWithCause.ProblemDetailWithCauseBuilder;
|
||||
|
||||
@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep
|
||||
public class BadRequestAlertException extends ErrorResponseException {
|
||||
|
||||
@Serial
|
||||
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(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
ProblemDetailWithCauseBuilder.instance()
|
||||
.withStatus(HttpStatus.BAD_REQUEST.value())
|
||||
.withType(type)
|
||||
.withTitle(defaultMessage)
|
||||
.withProperty("message", "error." + errorKey)
|
||||
.withProperty("params", entityName)
|
||||
.build(),
|
||||
null
|
||||
);
|
||||
this.entityName = entityName;
|
||||
this.errorKey = errorKey;
|
||||
}
|
||||
|
||||
public String getEntityName() {
|
||||
return entityName;
|
||||
}
|
||||
|
||||
public String getErrorKey() {
|
||||
return errorKey;
|
||||
}
|
||||
|
||||
public ProblemDetailWithCause getProblemDetailWithCause() {
|
||||
return (ProblemDetailWithCause) this.getBody();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.sisvietnamvn.web.web.rest.errors;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep
|
||||
public class EmailAlreadyUsedException extends BadRequestAlertException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EmailAlreadyUsedException() {
|
||||
super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.sisvietnamvn.web.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 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");
|
||||
|
||||
private ErrorConstants() {}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user