Initial version of sisvietnamvn generated by generator-jhipster@9.1.0

This commit is contained in:
NAS\NASPC
2026-06-03 13:27:09 +07:00
parent e3c8157c44
commit 69e8b767ff
176 changed files with 22171 additions and 106 deletions
+25
View File
@@ -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"
}
}
+24
View File
@@ -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
View File
@@ -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
View File
@@ -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/
+8
View File
@@ -0,0 +1,8 @@
node_modules
package-lock.json
.git
# Generated by jhipster:java-simple-application:gradle
build
gradle
.gradle
+13
View File
@@ -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
+33
View File
@@ -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": []
}
}
+25
View File
@@ -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)
+165
View File
@@ -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
View File
@@ -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
}
+17
View File
@@ -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"]
}
}
+25
View File
@@ -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>
+39
View File
@@ -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
+20
View File
@@ -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
+53
View File
@@ -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"
}
}
+48
View File
@@ -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'))
}
+52
View File
@@ -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'))
}
+81
View File
@@ -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')) }
+12
View File
@@ -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/**"]
}
Binary file not shown.
+1 -3
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
dependencies {
implementation "io.micrometer:micrometer-tracing-bridge-brave"
implementation "io.zipkin.reporter2:zipkin-reporter-brave"
}
+15 -18
View File
@@ -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.
+12 -13
View File
@@ -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
+7382
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -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"
}
}
+20 -1
View File
@@ -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"
+55
View File
@@ -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
+26
View File
@@ -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
+15
View File
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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() {}
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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;
@@ -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());
}
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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