Initial version of smartbooking generated by generator-jhipster@9.0.0-beta.0
This commit is contained in:
25
.devcontainer/Dockerfile
Normal file
25
.devcontainer/Dockerfile
Normal 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
|
||||
57
.devcontainer/devcontainer.json
Normal file
57
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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": "Smartbooking",
|
||||
"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": "17-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.11.1"
|
||||
}
|
||||
},
|
||||
|
||||
"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": [
|
||||
"christian-kohler.npm-intellisense",
|
||||
"firsttris.vscode-jest-runner",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"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": [9060, 3001, 9000, 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
.editorconfig
Normal file
24
.editorconfig
Normal file
@@ -0,0 +1,24 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
# 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
.gitattributes
vendored
Normal file
149
.gitattributes
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
# This file is inspired by https://github.com/alexkaratarakis/gitattributes
|
||||
#
|
||||
# Auto detect text files and perform LF normalization
|
||||
# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
|
||||
* text=auto
|
||||
|
||||
# The above will handle all files NOT found below
|
||||
# These files are text and should be normalized (Convert crlf => lf)
|
||||
|
||||
*.bat text eol=crlf
|
||||
*.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
|
||||
153
.gitignore
vendored
Normal file
153
.gitignore
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
######################
|
||||
# Node
|
||||
######################
|
||||
/node/
|
||||
node_tmp/
|
||||
node_modules/
|
||||
npm-debug.log.*
|
||||
/.awcache/*
|
||||
/.cache-loader/*
|
||||
/dist/
|
||||
|
||||
######################
|
||||
# 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/
|
||||
6
.husky/pre-commit
Executable file
6
.husky/pre-commit
Executable file
@@ -0,0 +1,6 @@
|
||||
if [ -z "$(which lint-staged)" ]; then
|
||||
echo 'Running package manager install'
|
||||
./npmw install
|
||||
fi
|
||||
|
||||
lint-staged
|
||||
3
.lintstagedrc.cjs
Normal file
3
.lintstagedrc.cjs
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'{,.blueprint/**/,src/**/}*.{md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,vue,css,scss}': ['prettier --write'],
|
||||
};
|
||||
11
.prettierignore
Normal file
11
.prettierignore
Normal file
@@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
.git
|
||||
|
||||
# Generated by jhipster:java-simple-application:gradle
|
||||
build
|
||||
gradle
|
||||
.gradle
|
||||
|
||||
# Generated by jhipster:client
|
||||
build/resources/main/static/
|
||||
13
.prettierrc
Normal file
13
.prettierrc
Normal 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
|
||||
37
.yo-rc.json
Normal file
37
.yo-rc.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"generator-jhipster": {
|
||||
"applicationType": "monolith",
|
||||
"authenticationType": "session",
|
||||
"baseName": "smartbooking",
|
||||
"buildTool": "gradle",
|
||||
"cacheProvider": "ehcache",
|
||||
"clientFramework": "vue",
|
||||
"clientTestFrameworks": ["cypress"],
|
||||
"clientTheme": "none",
|
||||
"creationTimestamp": 1765381052139,
|
||||
"cypressAudit": true,
|
||||
"cypressCoverage": null,
|
||||
"databaseType": "sql",
|
||||
"devDatabaseType": "postgresql",
|
||||
"enableGradleDevelocity": false,
|
||||
"enableHibernateCache": true,
|
||||
"enableTranslation": true,
|
||||
"entities": [],
|
||||
"feignClient": null,
|
||||
"gradleDevelocityHost": null,
|
||||
"jhipsterVersion": "9.0.0-beta.0",
|
||||
"languages": ["it"],
|
||||
"microfrontend": null,
|
||||
"microfrontends": [],
|
||||
"nativeLanguage": "it",
|
||||
"packageName": "it.sw.pa.comune.artegna",
|
||||
"prodDatabaseType": "postgresql",
|
||||
"reactive": false,
|
||||
"rememberMeKey": "c3201ccf39b5d82ef696ad12b95009f33d980fc0468a86e957ad91487d1fe80d9440f72a1d09721585bc96b11048fda07240",
|
||||
"serverPort": null,
|
||||
"serviceDiscoveryType": null,
|
||||
"syncUserWithIdp": null,
|
||||
"testFrameworks": ["cypress"],
|
||||
"withAdminUi": true
|
||||
}
|
||||
}
|
||||
254
README.md
Normal file
254
README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# smartbooking
|
||||
|
||||
This application was generated using JHipster 9.0.0-beta.0, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v9.0.0-beta.0](https://www.jhipster.tech/documentation-archive/v9.0.0-beta.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
|
||||
|
||||
- `npmw` - wrapper to use locally installed npm.
|
||||
JHipster installs Node and npm locally using the build tool by default. This wrapper makes sure npm is installed locally and uses it avoiding some differences different versions can cause. By using `./npmw` instead of the traditional `npm` you can configure a Node-less environment to develop or test your application.
|
||||
- `/src/main/docker` - Docker configurations for the application and services that the application depends on
|
||||
|
||||
## Development
|
||||
|
||||
The build system will install automatically the recommended version of Node and npm.
|
||||
|
||||
We provide a wrapper to launch npm.
|
||||
You will only need to run this command when dependencies change in [package.json](package.json).
|
||||
|
||||
```
|
||||
./npmw install
|
||||
```
|
||||
|
||||
We use npm scripts and [Vite][] as our build system.
|
||||
|
||||
Run the following commands in two separate terminals to create a blissful development experience where your browser
|
||||
auto-refreshes when files change on your hard drive.
|
||||
|
||||
```
|
||||
./npmw backend:start
|
||||
./npmw start
|
||||
```
|
||||
|
||||
Npm is also used to manage CSS and JavaScript dependencies used in this application. You can upgrade dependencies by
|
||||
specifying a newer version in [package.json](package.json). You can also run `./npmw update` and `./npmw install` to manage dependencies.
|
||||
Add the `help` flag on any command to see how you can use it. For example, `./npmw help update`.
|
||||
|
||||
The `./npmw run` command will list all the scripts available to run for this project.
|
||||
|
||||
### PWA Support
|
||||
|
||||
JHipster ships with PWA (Progressive Web App) support, and it's turned off by default. One of the main components of a PWA is a service worker.
|
||||
|
||||
The service worker initialization code is commented out by default. To enable it, uncomment the following code in `src/main/webapp/index.html`:
|
||||
|
||||
```html
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('./service-worker.js').then(function () {
|
||||
console.log('Service Worker Registered');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Note: [Workbox](https://developer.chrome.com/docs/workbox) powers JHipster's service worker. It dynamically generates the `service-worker.js` file.
|
||||
|
||||
### Managing dependencies
|
||||
|
||||
For example, to add [Leaflet][] library as a runtime dependency of your application, you would run the following command:
|
||||
|
||||
```
|
||||
./npmw install --save --save-exact leaflet
|
||||
```
|
||||
|
||||
To benefit from TypeScript type definitions from [DefinitelyTyped][] repository in development, you would run the following command:
|
||||
|
||||
```
|
||||
./npmw install --save-dev --save-exact @types/leaflet
|
||||
```
|
||||
|
||||
Then you would import the JS and CSS files specified in library's installation instructions so that [Vite][] knows about them:
|
||||
Note: There are still a few other things remaining to do for Leaflet that we won't detail here.
|
||||
|
||||
For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][].
|
||||
|
||||
## Building for production
|
||||
|
||||
### Packaging as jar
|
||||
|
||||
To build the final jar and optimize the smartbooking application for production, run:
|
||||
|
||||
```
|
||||
./gradlew -Pprod clean bootJar
|
||||
```
|
||||
|
||||
This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files.
|
||||
To ensure everything worked, run:
|
||||
|
||||
```
|
||||
java -jar build/libs/*.jar
|
||||
```
|
||||
|
||||
Then navigate to [http://localhost:8080](http://localhost:8080) in your browser.
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
./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:
|
||||
|
||||
```
|
||||
docker compose -f src/main/docker/jhipster-control-center.yml up
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Spring Boot tests
|
||||
|
||||
To launch your application's tests, run:
|
||||
|
||||
```
|
||||
./gradlew test integrationTest jacocoTestReport
|
||||
```
|
||||
|
||||
### Client tests
|
||||
|
||||
Unit tests are run by [Vitest][]. They're located near components and can be run with:
|
||||
|
||||
```
|
||||
./npmw test
|
||||
```
|
||||
|
||||
#### E2E tests
|
||||
|
||||
UI end-to-end tests are powered by [Cypress][]. They're located in [src/test/javascript/cypress/](src/test/javascript/cypress/)
|
||||
and can be run by starting Spring Boot in one terminal (`./npmw run app:start`) and running the tests (`./npmw run e2e`) in a second one.
|
||||
|
||||
Before running Cypress tests, it's possible to specify user credentials by overriding the `CYPRESS_E2E_USERNAME` and `CYPRESS_E2E_PASSWORD` environment variables.
|
||||
|
||||
```
|
||||
export CYPRESS_E2E_USERNAME="<your-username>"
|
||||
export CYPRESS_E2E_PASSWORD="<your-password>"
|
||||
```
|
||||
|
||||
See Cypress documentation for setting OS [environment variables](https://docs.cypress.io/app/references/environment-variables#Setting) to learn more.
|
||||
|
||||
#### Lighthouse audits
|
||||
|
||||
You can execute automated [Lighthouse audits](https://developer.chrome.com/docs/lighthouse/overview) with [cypress-audit](https://github.com/mfrachet/cypress-audit) by running `./npmw run e2e:cypress:audits`.
|
||||
You should only run the audits when your application is packaged with the production profile.
|
||||
The lighthouse report is created in `build/cypress/lhreport.html`.
|
||||
|
||||
## 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:
|
||||
|
||||
```
|
||||
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:
|
||||
|
||||
```
|
||||
./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:
|
||||
|
||||
```
|
||||
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:
|
||||
|
||||
```
|
||||
docker compose -f src/main/docker/services.yml up -d
|
||||
```
|
||||
|
||||
To stop and remove the containers, run:
|
||||
|
||||
```
|
||||
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:
|
||||
|
||||
```sh
|
||||
npm run java:docker
|
||||
```
|
||||
|
||||
Or build an arm64 Docker image when using an arm64 processor os like MacOS with M1 processor family running:
|
||||
|
||||
```sh
|
||||
npm run java:docker:arm64
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
docker compose -f src/main/docker/app.yml up -d
|
||||
```
|
||||
|
||||
For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the Docker Compose sub-generator (`jhipster docker-compose`), which is able to generate Docker configurations for one or several JHipster applications.
|
||||
|
||||
## Continuous Integration (optional)
|
||||
|
||||
To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information.
|
||||
|
||||
[JHipster Homepage and latest documentation]: https://www.jhipster.tech/
|
||||
[JHipster 9.0.0-beta.0 archive]: https://www.jhipster.tech/documentation-archive/v9.0.0-beta.0
|
||||
[Using JHipster in development]: https://www.jhipster.tech/documentation-archive/v9.0.0-beta.0/development/
|
||||
[Using Docker and Docker-Compose]: https://www.jhipster.tech/documentation-archive/v9.0.0-beta.0/docker-compose
|
||||
[Using JHipster in production]: https://www.jhipster.tech/documentation-archive/v9.0.0-beta.0/production/
|
||||
[Running tests page]: https://www.jhipster.tech/documentation-archive/v9.0.0-beta.0/running-tests/
|
||||
[Code quality page]: https://www.jhipster.tech/documentation-archive/v9.0.0-beta.0/code-quality/
|
||||
[Setting up Continuous Integration]: https://www.jhipster.tech/documentation-archive/v9.0.0-beta.0/setting-up-ci/
|
||||
[Node.js]: https://nodejs.org/
|
||||
[NPM]: https://www.npmjs.com/
|
||||
[Leaflet]: https://leafletjs.com/
|
||||
[DefinitelyTyped]: https://definitelytyped.org/
|
||||
[Cypress]: https://www.cypress.io/
|
||||
102
build.gradle
Normal file
102
build.gradle
Normal file
@@ -0,0 +1,102 @@
|
||||
plugins {
|
||||
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"
|
||||
id "jhipster.node-gradle-conventions"
|
||||
// jhipster-needle-gradle-plugins - JHipster will add additional gradle plugins here
|
||||
}
|
||||
|
||||
group = "it.sw.pa.comune.artegna"
|
||||
version = "0.0.1-SNAPSHOT"
|
||||
|
||||
description = ""
|
||||
|
||||
java {
|
||||
sourceCompatibility=17
|
||||
targetCompatibility=17
|
||||
}
|
||||
assert System.properties["java.specification.version"] == "17" || "21" || "25"
|
||||
|
||||
repositories {
|
||||
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 libs.jhipster.framework
|
||||
implementation libs.mapstruct
|
||||
implementation libs.springdoc.openapi.starter.webmvc.api
|
||||
implementation "org.springframework.boot:spring-boot-loader-tools"
|
||||
implementation "org.springframework.boot:spring-boot-starter"
|
||||
implementation "org.springframework.boot:spring-boot-starter-actuator"
|
||||
implementation "org.springframework.boot:spring-boot-starter-aop"
|
||||
implementation "org.springframework.boot:spring-boot-starter-cache"
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
implementation "org.springframework.boot:spring-boot-starter-mail"
|
||||
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-hibernate6"
|
||||
implementation "com.fasterxml.jackson.datatype:jackson-datatype-hppc"
|
||||
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
|
||||
implementation "com.fasterxml.jackson.module:jackson-module-jaxb-annotations"
|
||||
implementation "com.zaxxer:HikariCP"
|
||||
implementation "io.micrometer:micrometer-registry-prometheus-simpleclient"
|
||||
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 "org.postgresql:postgresql"
|
||||
testImplementation(libs.archunit.junit5.api) {
|
||||
exclude group: 'org.slf4j', module: 'slf4j-api'
|
||||
}
|
||||
testImplementation "org.springframework.boot:spring-boot-starter-test"
|
||||
testImplementation "org.springframework.boot:spring-boot-test"
|
||||
testImplementation "org.springframework.security:spring-security-test"
|
||||
testImplementation "org.testcontainers:jdbc"
|
||||
testImplementation "org.testcontainers:junit-jupiter"
|
||||
testImplementation "org.testcontainers:postgresql"
|
||||
testImplementation "org.testcontainers:testcontainers"
|
||||
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-jpamodelgen"
|
||||
developmentOnly "org.springframework.boot:spring-boot-docker-compose"
|
||||
// jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
|
||||
}
|
||||
18
buildSrc/build.gradle
Normal file
18
buildSrc/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
implementation libs.node.gradle
|
||||
// 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
|
||||
}
|
||||
15
buildSrc/gradle/libs.versions.toml
Normal file
15
buildSrc/gradle/libs.versions.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[versions]
|
||||
# jhipster-needle-gradle-dependency-catalog-version - JHipster will add additional versions for convention plugins heref
|
||||
# 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.1" }
|
||||
modernizer-plugin = { module = "com.github.andygoossens:gradle-modernizer-plugin", version = "1.12.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.2.0.6526" }
|
||||
spotless-plugin = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "8.1.0" }
|
||||
node-gradle = { module = "com.github.node-gradle:gradle-node-plugin", version = "7.1.0" }
|
||||
# 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")
|
||||
}
|
||||
}
|
||||
33
buildSrc/src/main/groovy/jhipster.docker-conventions.gradle
Normal file
33
buildSrc/src/main/groovy/jhipster.docker-conventions.gradle
Normal file
@@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
id "com.google.cloud.tools.jib"
|
||||
}
|
||||
|
||||
jib {
|
||||
configurationName = "productionRuntimeClasspath"
|
||||
from {
|
||||
image = "eclipse-temurin:17-jre-focal"
|
||||
platforms {
|
||||
platform {
|
||||
architecture = "${findProperty('jibArchitecture') ?: 'amd64'}"
|
||||
os = "linux"
|
||||
}
|
||||
}
|
||||
}
|
||||
to {
|
||||
image = "smartbooking:latest"
|
||||
}
|
||||
container {
|
||||
entrypoint = ["bash", "-c", "/entrypoint.sh"]
|
||||
ports = ["8080"]
|
||||
environment = [
|
||||
SPRING_OUTPUT_ANSI_ENABLED: "ALWAYS",
|
||||
JHIPSTER_SLEEP: "0"
|
||||
]
|
||||
creationTime = "USE_CURRENT_TIMESTAMP"
|
||||
user = 1000
|
||||
}
|
||||
extraDirectories {
|
||||
paths = file("src/main/docker/jib")
|
||||
permissions = ["/entrypoint.sh": "755"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
plugins {
|
||||
id "com.github.node-gradle.node"
|
||||
}
|
||||
|
||||
if (project.hasProperty("nodeInstall")) {
|
||||
node {
|
||||
version = "24.11.1"
|
||||
npmVersion = "11.6.4"
|
||||
download = true
|
||||
}
|
||||
|
||||
// Copy local node and npm to a fixed location for npmw
|
||||
def deleteOldNpm = tasks.register("deleteOldNpm", Delete) {
|
||||
delete('build/node/lib/node_modules/npm')
|
||||
}
|
||||
def fixedNode = tasks.register("fixedNode", Copy) {
|
||||
from(nodeSetup)
|
||||
into('build/node')
|
||||
finalizedBy(deleteOldNpm)
|
||||
}
|
||||
tasks.named("nodeSetup").configure { finalizedBy(fixedNode) }
|
||||
|
||||
def fixedNpm = tasks.register("fixedNpm", Copy) {
|
||||
from(npmSetup)
|
||||
into('build/node')
|
||||
}
|
||||
tasks.named("npmSetup").configure { finalizedBy(fixedNpm) }
|
||||
}
|
||||
|
||||
task webapp_test(type: NpmTask) {
|
||||
inputs.property('appVersion', project.version)
|
||||
inputs.files("build.gradle")
|
||||
.withPropertyName('build.gradle')
|
||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||
inputs.files('.postcssrc.js', 'package-lock.json', 'package.json', 'tsconfig.app.json', 'tsconfig.json', 'vite.config.mts')
|
||||
.withPropertyName('vue-build')
|
||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||
inputs.dir("src/main/webapp/")
|
||||
.withPropertyName("src/main/webapp/")
|
||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||
|
||||
outputs.dir("build/test-results/jest/")
|
||||
.withPropertyName("jest-result-dir")
|
||||
outputs.file("build/test-results/TESTS-results-jest.xml")
|
||||
.withPropertyName("jest-result")
|
||||
outputs.file("build/test-results/clover.xml")
|
||||
.withPropertyName("clover-result")
|
||||
|
||||
dependsOn(npmInstall, compileTestJava)
|
||||
args = ["run", "webapp:test"]
|
||||
}
|
||||
|
||||
test.dependsOn(webapp_test)
|
||||
|
||||
25
checkstyle.xml
Normal file
25
checkstyle.xml
Normal 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>
|
||||
11
cypress-audits.config.ts
Normal file
11
cypress-audits.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
import defaultConfig from './cypress.config';
|
||||
|
||||
export default defineConfig({
|
||||
...defaultConfig,
|
||||
e2e: {
|
||||
...defaultConfig.e2e,
|
||||
specPattern: 'src/test/javascript/cypress/e2e/**/*.audits.ts',
|
||||
},
|
||||
});
|
||||
27
cypress.config.ts
Normal file
27
cypress.config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
video: false,
|
||||
fixturesFolder: 'src/test/javascript/cypress/fixtures',
|
||||
screenshotsFolder: 'build/cypress/screenshots',
|
||||
downloadsFolder: 'build/cypress/downloads',
|
||||
videosFolder: 'build/cypress/videos',
|
||||
chromeWebSecurity: true,
|
||||
viewportWidth: 1200,
|
||||
viewportHeight: 720,
|
||||
retries: 2,
|
||||
env: {
|
||||
authenticationUrl: '/api/authentication',
|
||||
},
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
async setupNodeEvents(on, config) {
|
||||
return (await import('./src/test/javascript/cypress/plugins/index')).default(on, config);
|
||||
},
|
||||
baseUrl: 'http://localhost:8080/',
|
||||
specPattern: 'src/test/javascript/cypress/e2e/**/*.cy.ts',
|
||||
supportFile: 'src/test/javascript/cypress/support/index.ts',
|
||||
experimentalRunAllSpecs: true,
|
||||
},
|
||||
});
|
||||
84
eslint.config.ts
Normal file
84
eslint.config.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import js from '@eslint/js';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import cypress from 'eslint-plugin-cypress';
|
||||
import prettier from 'eslint-plugin-prettier/recommended';
|
||||
import vue from 'eslint-plugin-vue';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
// jhipster-needle-eslint-add-import - JHipster will add additional import here
|
||||
|
||||
export default defineConfig(
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ ignores: ['src/main/docker/'] },
|
||||
{ ignores: ['build/resources/main/static/', 'build/'] },
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended.map(config =>
|
||||
config.name === 'typescript-eslint/base' ? config : { ...config, files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'] },
|
||||
),
|
||||
{
|
||||
files: ['**/*.{js,cjs,mjs}'],
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
},
|
||||
},
|
||||
...vue.configs['flat/recommended'],
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
parserOptions: { parser: '@typescript-eslint/parser' },
|
||||
globals: { ...globals.browser },
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/main/webapp/**/*.vue', 'src/main/webapp/**/*.ts'],
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser },
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'vue/no-v-text-v-html-on-component': ['error', { allow: ['router-link', 'b-alert', 'b-badge', 'b-button', 'b-link'] }],
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
'vue/attributes-order': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/main/webapp/**/*.spec.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/test/javascript/cypress/**/*.ts'],
|
||||
extends: [...tseslint.configs.recommendedTypeChecked, cypress.configs.recommended],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./src/test/javascript/cypress/tsconfig.json'],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||
'@typescript-eslint/unbound-method': 'off',
|
||||
},
|
||||
},
|
||||
// jhipster-needle-eslint-add-config - JHipster will add additional config here
|
||||
prettier,
|
||||
);
|
||||
41
gradle.properties
Normal file
41
gradle.properties
Normal file
@@ -0,0 +1,41 @@
|
||||
rootProject.name=smartbooking
|
||||
profile=dev
|
||||
|
||||
liquibaseVersion=4.31.1
|
||||
liquibaseTaskPrefix=liquibase
|
||||
liquibasePluginVersion=3.0.2
|
||||
## Install and use a local version of node and npm.
|
||||
nodeInstall
|
||||
# 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
gradle/libs.versions.toml
Normal file
20
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[versions]
|
||||
mapstruct = "1.6.3"
|
||||
archunit-junit5 = "1.4.1"
|
||||
checkstyle = "12.2.0"
|
||||
jacoco = "0.8.14"
|
||||
# jhipster-needle-gradle-dependency-catalog-version - JHipster will add additional versions for convention plugins heref
|
||||
|
||||
[libraries]
|
||||
jhipster-framework = { module = "tech.jhipster:jhipster-framework", version = "9.0.0-beta.0" }
|
||||
springdoc-openapi-starter-webmvc-api = { module = "org.springdoc:springdoc-openapi-starter-webmvc-api", version = "2.8.14" }
|
||||
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 = "2.5.4" }
|
||||
spring-boot = { id = "org.springframework.boot", version = "3.5.8" }
|
||||
# jhipster-needle-gradle-dependency-catalog-plugins - JHipster will add additional plugins versions
|
||||
51
gradle/liquibase.gradle
Normal file
51
gradle/liquibase.gradle
Normal file
@@ -0,0 +1,51 @@
|
||||
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"
|
||||
liquibaseRuntime "org.postgresql:postgresql"
|
||||
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.postgresql.Driver"
|
||||
url "jdbc:postgresql://localhost:5432/smartbooking"
|
||||
username "smartbooking"
|
||||
changelogFile "src/main/resources/config/liquibase/master.xml"
|
||||
logLevel "debug"
|
||||
classpath "src/main/resources/"
|
||||
}
|
||||
diffLog {
|
||||
driver "org.postgresql.Driver"
|
||||
url "jdbc:postgresql://localhost:5432/smartbooking"
|
||||
username "smartbooking"
|
||||
changelogFile project.ext.diffChangelogFile
|
||||
referenceUrl "hibernate:spring:it.sw.pa.comune.artegna.domain?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy"
|
||||
logLevel "debug"
|
||||
classpath "$buildDir/classes/java/main"
|
||||
}
|
||||
}
|
||||
|
||||
runList = project.ext.runList
|
||||
}
|
||||
|
||||
liquibaseDiff.dependsOn(compileJava)
|
||||
liquibaseDiffChangelog.dependsOn(compileJava)
|
||||
|
||||
ext {
|
||||
if (project.hasProperty("no-liquibase")) {
|
||||
springProfiles += ",no-liquibase"
|
||||
}
|
||||
}
|
||||
69
gradle/profile_dev.gradle
Normal file
69
gradle/profile_dev.gradle
Normal file
@@ -0,0 +1,69 @@
|
||||
dependencies {
|
||||
developmentOnly "org.springframework.boot:spring-boot-devtools"
|
||||
// jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
|
||||
}
|
||||
|
||||
springBoot {
|
||||
buildInfo {
|
||||
excludes = ['time']
|
||||
}
|
||||
}
|
||||
|
||||
bootRun {
|
||||
args = ["--spring.profiles.active=${springProfiles}"]
|
||||
}
|
||||
|
||||
task webapp(type: NpmTask) {
|
||||
inputs.property('appVersion', project.version)
|
||||
inputs.files("build.gradle")
|
||||
.withPropertyName('build.gradle')
|
||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||
inputs.files('.postcssrc.js', 'package-lock.json', 'package.json', 'tsconfig.app.json', 'tsconfig.json', 'vite.config.mts')
|
||||
.withPropertyName('vue-build')
|
||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||
inputs.dir("src/main/webapp/")
|
||||
.withPropertyName("src/main/webapp/")
|
||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||
|
||||
outputs.dir("build/resources/main/static/")
|
||||
.withPropertyName("webapp-build-dir")
|
||||
|
||||
dependsOn(npmInstall)
|
||||
|
||||
args = ["run", "webapp:build"]
|
||||
environment = [APP_VERSION: project.version]
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task integrationTest(type: 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
|
||||
}
|
||||
integrationTest.dependsOn(test)
|
||||
|
||||
processResources.dependsOn(webapp)
|
||||
bootJar.dependsOn(processResources)
|
||||
57
gradle/profile_prod.gradle
Normal file
57
gradle/profile_prod.gradle
Normal file
@@ -0,0 +1,57 @@
|
||||
dependencies {
|
||||
// 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}"]
|
||||
}
|
||||
|
||||
task webapp(type: NpmTask) {
|
||||
dependsOn(npmInstall)
|
||||
args = ["run", "webapp:prod"]
|
||||
environment = [APP_VERSION: project.version]
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task integrationTest(type: 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
|
||||
}
|
||||
integrationTest.dependsOn(test)
|
||||
|
||||
processResources.dependsOn(webapp)
|
||||
bootJar.dependsOn(processResources)
|
||||
81
gradle/spring-boot.gradle
Normal file
81
gradle/spring-boot.gradle
Normal 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 = "it.sw.pa.comune.artegna.SmartbookingApp"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
check.dependsOn(integrationTest)
|
||||
|
||||
task testReport(type: TestReport) {
|
||||
destinationDirectory = file("$buildDir/reports/tests")
|
||||
testResults.from(test)
|
||||
}
|
||||
|
||||
task integrationTestReport(type: TestReport) {
|
||||
destinationDirectory = file("$buildDir/reports/tests")
|
||||
testResults.from(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 }
|
||||
}
|
||||
|
||||
task cleanResources(type: Delete) {
|
||||
delete "build/resources"
|
||||
}
|
||||
|
||||
compileJava.dependsOn(processResources)
|
||||
processResources.dependsOn(bootBuildInfo)
|
||||
14
gradle/war.gradle
Normal file
14
gradle/war.gradle
Normal file
@@ -0,0 +1,14 @@
|
||||
apply plugin: "war"
|
||||
|
||||
bootWar {
|
||||
webAppDirectory = file("build/resources/main/static/")
|
||||
mainClass = "it.sw.pa.comune.artegna.SmartbookingApp"
|
||||
includes = ["WEB-INF/**", "META-INF/**"]
|
||||
}
|
||||
|
||||
war {
|
||||
webAppDirectory = file("build/resources/main/static/")
|
||||
enabled = true
|
||||
archiveExtension = "war.original"
|
||||
includes = ["WEB-INF/**", "META-INF/**"]
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
4
gradle/zipkin.gradle
Normal file
4
gradle/zipkin.gradle
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
implementation "io.micrometer:micrometer-tracing-bridge-brave"
|
||||
implementation "io.zipkin.reporter2:zipkin-reporter-brave"
|
||||
}
|
||||
245
gradlew
vendored
Executable file
245
gradlew
vendored
Executable file
@@ -0,0 +1,245 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# 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/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$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=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# 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" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
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 $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" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
92
gradlew.bat
vendored
Normal file
92
gradlew.bat
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
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%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
177
jhipster-jdl.jdl
Normal file
177
jhipster-jdl.jdl
Normal file
@@ -0,0 +1,177 @@
|
||||
application {
|
||||
config {
|
||||
applicationType monolith
|
||||
authenticationType session
|
||||
baseName smartbooking
|
||||
buildTool gradle
|
||||
cacheProvider ehcache
|
||||
clientFramework vue
|
||||
clientTheme yeti
|
||||
clientThemeVariant primary
|
||||
creationTimestamp 1765013775744
|
||||
databaseType sql
|
||||
devDatabaseType postgresql
|
||||
enableGradleEnterprise false
|
||||
enableHibernateCache true
|
||||
enableTranslation true
|
||||
jhipsterVersion "8.11.0"
|
||||
languages [it]
|
||||
microfrontends []
|
||||
nativeLanguage it
|
||||
packageName it.softwarepa.comune.artegna
|
||||
prodDatabaseType postgresql
|
||||
reactive false
|
||||
rememberMeKey "810f1205976fd73602beb36796a53c78e3c4974c4fe22f343acbab764cdc74e50f3aeb807a64e1aa922f6f557ada00a8c89f"
|
||||
testFrameworks [cucumber, cypress]
|
||||
withAdminUi true
|
||||
}
|
||||
|
||||
entities AuditLog, Conferma, Disponibilita, Notifica, Prenotazione, Struttura, UtenteApp, Messaggio
|
||||
}
|
||||
|
||||
|
||||
entity AuditLog {
|
||||
entitaTipo TipoEntita
|
||||
entitaId Long
|
||||
azione AzioneAudit
|
||||
dettagli String
|
||||
ipAddress String
|
||||
createdAt Instant
|
||||
}
|
||||
|
||||
entity Disponibilita {
|
||||
giornoSettimana GiornoSettimana
|
||||
dataSpecifica LocalDate
|
||||
oraInizio Instant
|
||||
oraFine Instant
|
||||
orarioInizio String
|
||||
orarioFine String
|
||||
tipo TipoDisponibilita
|
||||
note String
|
||||
}
|
||||
|
||||
entity Notifica {
|
||||
tipoCanale TipoCanaleNotifica
|
||||
tipoEvento TipoEventoNotifica
|
||||
messaggio String
|
||||
inviata Boolean
|
||||
inviataAt Instant
|
||||
errore String
|
||||
createdAt Instant
|
||||
}
|
||||
|
||||
entity Prenotazione {
|
||||
oraInizio Instant
|
||||
oraFine Instant
|
||||
stato StatoPrenotazione
|
||||
motivoEvento String
|
||||
numeroPartecipanti Integer
|
||||
noteUtente String
|
||||
}
|
||||
|
||||
entity Conferma {
|
||||
motivoConferma String
|
||||
tipoConferma TipoConferma
|
||||
}
|
||||
|
||||
entity Struttura {
|
||||
nome String
|
||||
descrizione String
|
||||
indirizzo String
|
||||
capienzaMax Integer
|
||||
attiva Boolean
|
||||
fotoUrl String
|
||||
createdAt Instant
|
||||
updatedAt Instant
|
||||
}
|
||||
|
||||
entity UtenteApp {
|
||||
username String required unique
|
||||
email String required
|
||||
telefono String
|
||||
ruolo Ruolo required
|
||||
attivo Boolean required
|
||||
createdAt Instant
|
||||
updatedAt Instant
|
||||
}
|
||||
|
||||
entity Messaggio {
|
||||
spedito Instant
|
||||
testo String
|
||||
letto Instant
|
||||
}
|
||||
|
||||
enum TipoEntita {
|
||||
PRENOTAZIONE,
|
||||
CONFERMA
|
||||
}
|
||||
|
||||
enum TipoConferma {
|
||||
CONFERMATA,
|
||||
RIFIUTATA
|
||||
}
|
||||
enum AzioneAudit {
|
||||
CREATE,
|
||||
UPDATE,
|
||||
DELETE,
|
||||
CONFIRM,
|
||||
REJECT
|
||||
}
|
||||
enum GiornoSettimana {
|
||||
LUNEDI,
|
||||
MARTEDI,
|
||||
MERCOLEDI,
|
||||
GIOVEDI,
|
||||
VENERDI,
|
||||
SABATO,
|
||||
DOMENICA
|
||||
}
|
||||
enum TipoDisponibilita {
|
||||
DISPONIBILE,
|
||||
CHIUSURA
|
||||
}
|
||||
enum TipoCanaleNotifica {
|
||||
EMAIL,
|
||||
WHATSAPP,
|
||||
SISTEMA
|
||||
}
|
||||
enum TipoEventoNotifica {
|
||||
RICHIESTA_CREATA,
|
||||
PRENOTAZIONE_CONFERMATA,
|
||||
PRENOTAZIONE_RIFIUTATA,
|
||||
REMINDER,
|
||||
ANNULLAMENTO
|
||||
}
|
||||
enum StatoPrenotazione {
|
||||
RICHIESTA,
|
||||
CONFERMATA,
|
||||
RIFIUTATA,
|
||||
ANNULLATA
|
||||
}
|
||||
enum Ruolo {
|
||||
USER,
|
||||
INCARICATO,
|
||||
ADMIN
|
||||
}
|
||||
|
||||
relationship OneToOne {
|
||||
Prenotazione to Conferma
|
||||
}
|
||||
|
||||
|
||||
relationship ManyToOne {
|
||||
AuditLog{utente(username)} to UtenteApp
|
||||
Disponibilita{struttura(nome)} to Struttura{disponibilita}
|
||||
Notifica{conferma} to Conferma
|
||||
Prenotazione{utente(username)} to UtenteApp
|
||||
Prenotazione{struttura(nome)} to Struttura
|
||||
Conferma{confermataDa(username)} to UtenteApp
|
||||
Messaggio{utente(username)} to UtenteApp
|
||||
}
|
||||
|
||||
dto AuditLog, Disponibilita, Notifica, Prenotazione, Conferma, Struttura, UtenteApp, Messaggio with mapstruct
|
||||
paginate AuditLog, Notifica, Prenotazione, Struttura, Conferma with pagination
|
||||
paginate Disponibilita with infinite-scroll
|
||||
service AuditLog, Conferma, Disponibilita, Notifica, Prenotazione, Struttura, UtenteApp with serviceImpl
|
||||
search AuditLog, Conferma, Disponibilita, Notifica, Prenotazione, Struttura, UtenteApp with no
|
||||
filter AuditLog, Notifica, Prenotazione, Struttura, Conferma
|
||||
34
npmw
Executable file
34
npmw
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
basedir=$(dirname "$0")
|
||||
|
||||
if [ -f "$basedir/gradlew" ]; then
|
||||
bindir="$basedir/build/node/bin"
|
||||
repodir="$basedir/build/node/lib/node_modules"
|
||||
installCommand="$basedir/gradlew npmSetup --no-daemon"
|
||||
else
|
||||
echo "Using npm installed globally"
|
||||
exec npm "$@"
|
||||
fi
|
||||
|
||||
NPM_EXE="$repodir/npm/bin/npm-cli.js"
|
||||
NODE_EXE="$bindir/node"
|
||||
|
||||
if [ ! -x "$NPM_EXE" ] || [ ! -x "$NODE_EXE" ]; then
|
||||
$installCommand || true
|
||||
fi
|
||||
|
||||
if [ -x "$NODE_EXE" ]; then
|
||||
echo "Using node installed locally $($NODE_EXE --version)"
|
||||
PATH="$bindir:$PATH"
|
||||
else
|
||||
NODE_EXE='node'
|
||||
fi
|
||||
|
||||
if [ ! -x "$NPM_EXE" ]; then
|
||||
echo "Local npm not found, using npm installed globally"
|
||||
npm "$@"
|
||||
else
|
||||
echo "Using npm installed locally $($NODE_EXE $NPM_EXE --version)"
|
||||
$NODE_EXE $NPM_EXE "$@"
|
||||
fi
|
||||
24
npmw.cmd
Normal file
24
npmw.cmd
Normal file
@@ -0,0 +1,24 @@
|
||||
@echo off
|
||||
|
||||
setlocal
|
||||
|
||||
set NPMW_DIR=%~dp0
|
||||
|
||||
set NODE_EXE=^"%NPMW_DIR%build\node\bin\node.exe^"
|
||||
set NODE_PATH=%NPMW_DIR%build\node\bin\
|
||||
set NPM_EXE=^"%NPMW_DIR%build\node\lib\node_modules\npm\bin\npm-cli.js^"
|
||||
set INSTALL_NPM_COMMAND=^"%NPMW_DIR%gradlew.bat^" npmSetup --no-daemon
|
||||
|
||||
if not exist %NPM_EXE% (
|
||||
call %INSTALL_NPM_COMMAND%
|
||||
)
|
||||
|
||||
if exist %NODE_EXE% (
|
||||
Rem execute local npm with local node, whilst adding local node location to the PATH for this CMD session
|
||||
endlocal & echo "%PATH%"|find /i "%NODE_PATH%;">nul || set "PATH=%NODE_PATH%;%PATH%" & call %NODE_EXE% %NPM_EXE% %*
|
||||
) else if exist %NPM_EXE% (
|
||||
Rem execute local npm, whilst adding local npm location to the PATH for this CMD session
|
||||
endlocal & echo "%PATH%"|find /i "%NODE_PATH%;">nul || set "PATH=%NODE_PATH%;%PATH%" & call %NPM_EXE% %*
|
||||
) else (
|
||||
call npm %*
|
||||
)
|
||||
18090
package-lock.json
generated
Normal file
18090
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
162
package.json
Normal file
162
package.json
Normal file
@@ -0,0 +1,162 @@
|
||||
{
|
||||
"name": "smartbooking",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Description for Smartbooking",
|
||||
"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 -x webapp -x webapp_test",
|
||||
"backend:info": "./gradlew -v",
|
||||
"backend:nohttp:test": "./gradlew checkstyleNohttp -x webapp -x webapp_test",
|
||||
"backend:start": "./gradlew -x webapp -x webapp_test",
|
||||
"backend:unit:test": "./gradlew test integrationTest -x webapp -x webapp_test -Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.it.sw.pa.comune.artegna=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF",
|
||||
"build": "npm run webapp:prod --",
|
||||
"build-watch": "concurrently 'npm run webapp:build:dev -- --watch' npm:backend:start",
|
||||
"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:dev": "concurrently -k -s first -n application,e2e -c red,blue npm:app:start npm:e2e:headless",
|
||||
"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",
|
||||
"ci:e2e:run": "concurrently -k -s first -n application,e2e -c red,blue npm:ci:e2e:server:start npm:e2e:headless",
|
||||
"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,$npm_package_config_default_environment -Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.it.sw.pa.comune.artegna=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:e2e:teardown:docker": "docker compose -f src/main/docker/services.yml down -v && docker ps -a",
|
||||
"ci:frontend:build": "npm run webapp:build:$npm_package_config_default_environment",
|
||||
"ci:frontend:test": "npm run ci:frontend:build && npm test",
|
||||
"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\"",
|
||||
"clean-www": "rimraf build/resources/main/static/",
|
||||
"cleanup": "rimraf build/",
|
||||
"cypress": "cypress open --e2e",
|
||||
"cypress:audits": "cypress open --e2e --config-file cypress-audits.config.js",
|
||||
"docker:db:down": "docker compose -f src/main/docker/postgresql.yml down -v",
|
||||
"docker:db:up": "docker compose -f src/main/docker/postgresql.yml up --wait",
|
||||
"e2e": "npm run e2e:cypress:headed --",
|
||||
"e2e:cypress": "cypress run --e2e --browser chrome",
|
||||
"e2e:cypress:audits": "cypress run --e2e --browser chrome --config-file cypress-audits.config.js",
|
||||
"e2e:cypress:audits:headless": "npm run e2e:cypress -- --config-file cypress-audits.config.js",
|
||||
"e2e:cypress:headed": "npm run e2e:cypress -- --headed",
|
||||
"e2e:cypress:record": "npm run e2e:cypress -- --record",
|
||||
"e2e:dev": "concurrently -k -s first -n application,e2e -c red,blue npm:app:start npm:e2e",
|
||||
"e2e:devserver": "concurrently -k -s first -n backend,frontend,e2e -c red,yellow,blue npm:backend:start npm:start \"wait-on -t 180000 http-get://127.0.0.1:9000 && npm run e2e:headless -- -c baseUrl=http://localhost:9000\"",
|
||||
"pree2e:headless": "npm run ci:server:await",
|
||||
"e2e:headless": "npm run e2e:cypress --",
|
||||
"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",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"prepare": "husky",
|
||||
"prettier:check": "prettier --check \"{,.blueprint/**/,src/**/}*.{md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,vue,css,scss}\"",
|
||||
"prettier:format": "prettier --write \"{,.blueprint/**/,src/**/}*.{md,json,yml,js,cjs,mjs,ts,cts,mts,java,html,vue,css,scss}\"",
|
||||
"serve": "npm run start --",
|
||||
"services:up": "docker compose -f src/main/docker/services.yml up --wait",
|
||||
"start": "npm run webapp:dev --",
|
||||
"start-tls": "npm run webapp:dev -- --env.tls",
|
||||
"pretest": "npm run lint",
|
||||
"test": "npm run vitest-run --",
|
||||
"test:watch": "npm run vitest",
|
||||
"vite-build": "vite build",
|
||||
"vite-serve": "vite",
|
||||
"vitest": "vitest",
|
||||
"vitest-run": "vitest --run --coverage",
|
||||
"watch": "concurrently npm:start npm:backend:start",
|
||||
"webapp:build": "npm run clean-www && npm run webapp:build:dev --",
|
||||
"webapp:build:dev": "npm run vite-build",
|
||||
"webapp:build:prod": "npm run vite-build",
|
||||
"webapp:dev": "npm run vite-serve",
|
||||
"webapp:prod": "npm run clean-www && npm run webapp:build:prod --",
|
||||
"webapp:serve": "npm run vite-serve",
|
||||
"webapp:test": "npm run test --"
|
||||
},
|
||||
"config": {
|
||||
"backend_port": 8080,
|
||||
"default_environment": "prod",
|
||||
"packaging": "jar"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
],
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "7.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "7.1.0",
|
||||
"@fortawesome/vue-fontawesome": "3.1.2",
|
||||
"@vuelidate/core": "2.0.3",
|
||||
"@vuelidate/validators": "2.0.4",
|
||||
"@vueuse/core": "14.1.0",
|
||||
"axios": "1.13.2",
|
||||
"bootstrap": "5.3.7",
|
||||
"bootstrap-vue-next": "0.40.9",
|
||||
"dayjs": "1.11.19",
|
||||
"deepmerge": "4.3.1",
|
||||
"pinia": "3.0.4",
|
||||
"vue": "3.5.25",
|
||||
"vue-i18n": "11.2.2",
|
||||
"vue-router": "4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.39.1",
|
||||
"@pinia/testing": "1.0.3",
|
||||
"@tsconfig/node18": "18.2.6",
|
||||
"@types/node": "20.19.25",
|
||||
"@types/sinon": "21.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.2",
|
||||
"@vitest/coverage-v8": "4.0.15",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"@vue/tsconfig": "0.8.1",
|
||||
"autoprefixer": "10.4.22",
|
||||
"axios-mock-adapter": "2.1.0",
|
||||
"concurrently": "9.2.1",
|
||||
"cypress": "15.7.1",
|
||||
"cypress-audit": "1.1.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-cypress": "5.2.0",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"eslint-plugin-vue": "10.6.2",
|
||||
"flush-promises": "1.0.2",
|
||||
"generator-jhipster": "9.0.0-beta.0",
|
||||
"happy-dom": "20.0.11",
|
||||
"husky": "9.1.7",
|
||||
"jiti": "2.6.1",
|
||||
"lighthouse": "13.0.1",
|
||||
"lint-staged": "16.1.5",
|
||||
"numeral": "2.0.6",
|
||||
"postcss-import": "16.1.1",
|
||||
"postcss-url": "10.1.3",
|
||||
"prettier": "3.7.4",
|
||||
"prettier-plugin-java": "2.7.7",
|
||||
"prettier-plugin-packagejson": "2.5.20",
|
||||
"rimraf": "6.1.2",
|
||||
"sass": "1.70.0",
|
||||
"sinon": "21.0.0",
|
||||
"swagger-ui-dist": "5.30.3",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.48.1",
|
||||
"vite": "7.2.6",
|
||||
"vite-plugin-static-copy": "3.1.4",
|
||||
"vitest": "4.0.15",
|
||||
"vitest-sonar-reporter": "3.0.0",
|
||||
"wait-on": "9.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.11.1"
|
||||
},
|
||||
"cacheDirectories": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
20
settings.gradle
Normal file
20
settings.gradle
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
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 = "smartbooking"
|
||||
48
sonar-project.properties
Normal file
48
sonar-project.properties
Normal file
@@ -0,0 +1,48 @@
|
||||
sonar.projectKey = smartbooking
|
||||
sonar.projectName = smartbooking 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/**/*.*, src/main/webapp/app/**/*.spec.ts
|
||||
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.exclusions = src/main/webapp/content/**/*.*, src/main/webapp/i18n/*.ts, build/resources/main/static/**/*.*
|
||||
sonar.javascript.lcov.reportPaths = build/test-results/lcov.info
|
||||
sonar.testExecutionReportPaths = build/test-results/jest/TESTS-results-sonar.xml
|
||||
|
||||
sonar.issue.ignore.multicriteria = S7027-domain, S3437, S7027-dto, UndocumentedApi, S125, S4684, S5145, S6437, S1192
|
||||
# Rule https://rules.sonarsource.com/java/RSPEC-7027 is ignored for entities
|
||||
sonar.issue.ignore.multicriteria.S7027-domain.resourceKey = src/main/java/it/sw/pa/comune/artegna/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/it/sw/pa/comune/artegna/**/*
|
||||
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/it/sw/pa/comune/artegna/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/it/sw/pa/comune/artegna/**/*
|
||||
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-4684
|
||||
sonar.issue.ignore.multicriteria.S4684.resourceKey = src/main/java/it/sw/pa/comune/artegna/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/it/sw/pa/comune/artegna/**/*
|
||||
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/*
|
||||
sonar.issue.ignore.multicriteria.S6437.ruleKey = java:S6437
|
||||
# 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/it/sw/pa/comune/artegna/config/CacheConfiguration.java
|
||||
sonar.issue.ignore.multicriteria.S1192.ruleKey = java:S1192
|
||||
29
src/main/docker/app.yml
Normal file
29
src/main/docker/app.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: smartbooking
|
||||
services:
|
||||
app:
|
||||
image: smartbooking
|
||||
environment:
|
||||
- _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||
- SPRING_PROFILES_ACTIVE=prod,api-docs
|
||||
- MANAGEMENT_PROMETHEUS_METRICS_EXPORT_ENABLED=true
|
||||
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/smartbooking
|
||||
- SPRING_LIQUIBASE_URL=jdbc:postgresql://postgresql:5432/smartbooking
|
||||
ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- curl
|
||||
- -f
|
||||
- http://localhost:8080/management/health
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 40
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
postgresql:
|
||||
extends:
|
||||
file: ./postgresql.yml
|
||||
service: postgresql
|
||||
3778
src/main/docker/grafana/provisioning/dashboards/JVM.json
Normal file
3778
src/main/docker/grafana/provisioning/dashboards/JVM.json
Normal file
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
|
||||
48
src/main/docker/jhipster-control-center.yml
Normal file
48
src/main/docker/jhipster-control-center.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
## 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: smartbooking
|
||||
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_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_SMARTBOOKING_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
|
||||
40
src/main/docker/jib/entrypoint.sh
Normal file
40
src/main/docker/jib/entrypoint.sh
Normal file
@@ -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/* "it.sw.pa.comune.artegna.SmartbookingApp" "$@"
|
||||
31
src/main/docker/monitoring.yml
Normal file
31
src/main/docker/monitoring.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: smartbooking
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:v3.8.0
|
||||
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:12.3.0
|
||||
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
|
||||
19
src/main/docker/postgresql.yml
Normal file
19
src/main/docker/postgresql.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: smartbooking
|
||||
services:
|
||||
postgresql:
|
||||
image: postgres:18.1
|
||||
# volumes:
|
||||
# - ~/volumes/jhipster/smartbooking/postgresql/:/var/lib/postgresql/data/
|
||||
environment:
|
||||
- POSTGRES_USER=smartbooking
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER}']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:5432:5432
|
||||
31
src/main/docker/prometheus/prometheus.yml
Normal file
31
src/main/docker/prometheus/prometheus.yml
Normal file
@@ -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
|
||||
7
src/main/docker/services.yml
Normal file
7
src/main/docker/services.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: smartbooking
|
||||
services:
|
||||
postgresql:
|
||||
extends:
|
||||
file: ./postgresql.yml
|
||||
service: postgresql
|
||||
15
src/main/docker/sonar.yml
Normal file
15
src/main/docker/sonar.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: smartbooking
|
||||
services:
|
||||
sonar:
|
||||
container_name: sonarqube
|
||||
image: sonarqube:25.11.0.114957-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
|
||||
19
src/main/java/it/sw/pa/comune/artegna/ApplicationWebXml.java
Normal file
19
src/main/java/it/sw/pa/comune/artegna/ApplicationWebXml.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package it.sw.pa.comune.artegna;
|
||||
|
||||
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(SmartbookingApp.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package it.sw.pa.comune.artegna;
|
||||
|
||||
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.0.0-beta.0")
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ ElementType.TYPE })
|
||||
public @interface GeneratedByJHipster {}
|
||||
110
src/main/java/it/sw/pa/comune/artegna/SmartbookingApp.java
Normal file
110
src/main/java/it/sw/pa/comune/artegna/SmartbookingApp.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package it.sw.pa.comune.artegna;
|
||||
|
||||
import it.sw.pa.comune.artegna.config.ApplicationProperties;
|
||||
import it.sw.pa.comune.artegna.config.CRLFLogConverter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
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.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.core.env.Environment;
|
||||
import tech.jhipster.config.DefaultProfileUtil;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class })
|
||||
public class SmartbookingApp {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SmartbookingApp.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public SmartbookingApp(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes smartbooking.
|
||||
* <p>
|
||||
* Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile
|
||||
* <p>
|
||||
* You can find more information on how profiles work with JHipster on <a href="https://www.jhipster.tech/profiles/">https://www.jhipster.tech/profiles/</a>.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initApplication() {
|
||||
Collection<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
|
||||
if (
|
||||
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) &&
|
||||
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)
|
||||
) {
|
||||
LOG.error(
|
||||
"You have misconfigured your application! It should not run " + "with both the 'dev' and 'prod' profiles at the same time."
|
||||
);
|
||||
}
|
||||
if (
|
||||
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) &&
|
||||
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)
|
||||
) {
|
||||
LOG.error(
|
||||
"You have misconfigured your application! It should not " + "run with both the 'dev' and 'cloud' profiles at the same time."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method, used to run the application.
|
||||
*
|
||||
* @param args the command line arguments.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var app = new SpringApplication(SmartbookingApp.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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package it.sw.pa.comune.artegna.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(it.sw.pa.comune.artegna.repository..*)" +
|
||||
" || within(it.sw.pa.comune.artegna.service..*)" +
|
||||
" || within(it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.aop.logging;
|
||||
@@ -0,0 +1,38 @@
|
||||
package it.sw.pa.comune.artegna.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties specific to Smartbooking.
|
||||
* <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 it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.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.get(0).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,82 @@
|
||||
package it.sw.pa.comune.artegna.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.ehcache.config.builders.*;
|
||||
import org.ehcache.jsr107.Eh107Configuration;
|
||||
import org.hibernate.cache.jcache.ConfigSettings;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
|
||||
import org.springframework.boot.info.BuildProperties;
|
||||
import org.springframework.boot.info.GitProperties;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.interceptor.KeyGenerator;
|
||||
import org.springframework.context.annotation.*;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
import tech.jhipster.config.cache.PrefixedKeyGenerator;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfiguration {
|
||||
|
||||
private GitProperties gitProperties;
|
||||
private BuildProperties buildProperties;
|
||||
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, it.sw.pa.comune.artegna.repository.UserRepository.USERS_BY_LOGIN_CACHE);
|
||||
createCache(cm, it.sw.pa.comune.artegna.repository.UserRepository.USERS_BY_EMAIL_CACHE);
|
||||
createCache(cm, it.sw.pa.comune.artegna.domain.User.class.getName());
|
||||
createCache(cm, it.sw.pa.comune.artegna.domain.Authority.class.getName());
|
||||
createCache(cm, it.sw.pa.comune.artegna.domain.User.class.getName() + ".authorities");
|
||||
createCache(cm, it.sw.pa.comune.artegna.domain.PersistentToken.class.getName());
|
||||
createCache(cm, it.sw.pa.comune.artegna.domain.User.class.getName() + ".persistentTokens");
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
15
src/main/java/it/sw/pa/comune/artegna/config/Constants.java
Normal file
15
src/main/java/it/sw/pa/comune/artegna/config/Constants.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package it.sw.pa.comune.artegna.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 = "it";
|
||||
|
||||
private Constants() {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package it.sw.pa.comune.artegna.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories({ "it.sw.pa.comune.artegna.repository" })
|
||||
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
|
||||
@EnableTransactionManagement
|
||||
public class DatabaseConfiguration {}
|
||||
@@ -0,0 +1,20 @@
|
||||
package it.sw.pa.comune.artegna.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,34 @@
|
||||
package it.sw.pa.comune.artegna.config;
|
||||
|
||||
import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module;
|
||||
import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module.Feature;
|
||||
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();
|
||||
}
|
||||
|
||||
/*
|
||||
* Support for Hibernate types in Jackson.
|
||||
*/
|
||||
@Bean
|
||||
public Hibernate6Module hibernate6Module() {
|
||||
return new Hibernate6Module().configure(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package it.sw.pa.comune.artegna.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.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSource;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.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 it.sw.pa.comune.artegna.config;
|
||||
|
||||
import it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.config;
|
||||
|
||||
import static tech.jhipster.config.logging.LoggingUtils.*;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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;
|
||||
|
||||
/*
|
||||
* 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 JsonProcessingException {
|
||||
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,163 @@
|
||||
package it.sw.pa.comune.artegna.config;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
import it.sw.pa.comune.artegna.security.*;
|
||||
import it.sw.pa.comune.artegna.web.filter.SpaWebFilter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.function.Supplier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||
import org.springframework.security.web.authentication.RememberMeServices;
|
||||
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.security.web.csrf.*;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
private final RememberMeServices rememberMeServices;
|
||||
|
||||
public SecurityConfiguration(RememberMeServices rememberMeServices, JHipsterProperties jHipsterProperties) {
|
||||
this.rememberMeServices = rememberMeServices;
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors(withDefaults())
|
||||
.csrf(csrf ->
|
||||
csrf
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
|
||||
)
|
||||
.addFilterAfter(new SpaWebFilter(), BasicAuthenticationFilter.class)
|
||||
.headers(headers ->
|
||||
headers
|
||||
.contentSecurityPolicy(csp -> csp.policyDirectives(jHipsterProperties.getSecurity().getContentSecurityPolicy()))
|
||||
.frameOptions(FrameOptionsConfig::sameOrigin)
|
||||
.referrerPolicy(referrer -> referrer.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
|
||||
.permissionsPolicyHeader(permissions ->
|
||||
permissions.policy(
|
||||
"camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()"
|
||||
)
|
||||
)
|
||||
)
|
||||
.authorizeHttpRequests(authz ->
|
||||
// prettier-ignore
|
||||
authz
|
||||
.requestMatchers("/index.html", "/*.js", "/*.txt", "/*.json", "/*.map", "/*.css").permitAll()
|
||||
.requestMatchers("/*.ico", "/*.png", "/*.svg", "/*.webapp").permitAll()
|
||||
.requestMatchers("/assets/**").permitAll()
|
||||
.requestMatchers("/swagger-ui/**").permitAll()
|
||||
.requestMatchers("/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)
|
||||
)
|
||||
.rememberMe(rememberMe ->
|
||||
rememberMe
|
||||
.rememberMeServices(rememberMeServices)
|
||||
.rememberMeParameter("remember-me")
|
||||
.key(jHipsterProperties.getSecurity().getRememberMe().getKey())
|
||||
)
|
||||
.exceptionHandling(exceptionHanding -> {
|
||||
AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
RequestMatcher apiRequestMatcher = request -> pathMatcher.match("/api/**", request.getRequestURI());
|
||||
exceptionHanding.defaultAuthenticationEntryPointFor(
|
||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
||||
new OrRequestMatcher(apiRequestMatcher)
|
||||
);
|
||||
})
|
||||
.formLogin(formLogin ->
|
||||
formLogin
|
||||
.loginPage("/")
|
||||
.loginProcessingUrl("/api/authentication")
|
||||
.successHandler((request, response, authentication) -> response.setStatus(HttpStatus.OK.value()))
|
||||
.failureHandler((request, response, exception) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()))
|
||||
.permitAll()
|
||||
)
|
||||
.logout(logout ->
|
||||
logout.logoutUrl("/api/logout").logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()).permitAll()
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom CSRF handler to provide BREACH protection for Single-Page Applications (SPA).
|
||||
*
|
||||
* @see <a href="https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa">Spring Security Documentation - Integrating with CSRF Protection</a>
|
||||
* @see <a href="https://github.com/jhipster/generator-jhipster/pull/25907">JHipster - use customized SpaCsrfTokenRequestHandler to handle CSRF token</a>
|
||||
* @see <a href="https://stackoverflow.com/q/74447118/65681">CSRF protection not working with Spring Security 6</a>
|
||||
*/
|
||||
static final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
|
||||
|
||||
private final CsrfTokenRequestHandler plain = new CsrfTokenRequestAttributeHandler();
|
||||
private final CsrfTokenRequestHandler xor = new XorCsrfTokenRequestAttributeHandler();
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
|
||||
/*
|
||||
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
||||
* the CsrfToken when it is rendered in the response body.
|
||||
*/
|
||||
this.xor.handle(request, response, csrfToken);
|
||||
|
||||
// Render the token value to a cookie by causing the deferred token to be loaded.
|
||||
csrfToken.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
|
||||
/*
|
||||
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
||||
* to resolve the CsrfToken. This applies when a single-page application includes
|
||||
* the header value automatically, which was obtained via a cookie containing the
|
||||
* raw CsrfToken.
|
||||
*/
|
||||
if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
|
||||
return this.plain.resolveCsrfTokenValue(request, csrfToken);
|
||||
}
|
||||
/*
|
||||
* In all other cases (e.g. if the request contains a request parameter), use
|
||||
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
|
||||
* when a server-side rendered form includes the _csrf request parameter as a
|
||||
* hidden input.
|
||||
*/
|
||||
return this.xor.resolveCsrfTokenValue(request, csrfToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package it.sw.pa.comune.artegna.config;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
@Configuration
|
||||
@Profile({ JHipsterConstants.SPRING_PROFILE_PRODUCTION })
|
||||
public class StaticResourcesWebConfiguration implements WebMvcConfigurer {
|
||||
|
||||
protected static final String[] RESOURCE_LOCATIONS = { "classpath:/static/", "classpath:/static/content/", "classpath:/static/i18n/" };
|
||||
protected static final String[] RESOURCE_PATHS = { "/*.js", "/*.css", "/*.svg", "/*.png", "*.ico", "/content/**", "/i18n/*" };
|
||||
|
||||
private final JHipsterProperties jhipsterProperties;
|
||||
|
||||
public StaticResourcesWebConfiguration(JHipsterProperties jHipsterProperties) {
|
||||
this.jhipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
ResourceHandlerRegistration resourceHandlerRegistration = appendResourceHandler(registry);
|
||||
initializeResourceHandler(resourceHandlerRegistration);
|
||||
}
|
||||
|
||||
protected ResourceHandlerRegistration appendResourceHandler(ResourceHandlerRegistry registry) {
|
||||
return registry.addResourceHandler(RESOURCE_PATHS);
|
||||
}
|
||||
|
||||
protected void initializeResourceHandler(ResourceHandlerRegistration resourceHandlerRegistration) {
|
||||
resourceHandlerRegistration.addResourceLocations(RESOURCE_LOCATIONS).setCacheControl(getCacheControl());
|
||||
}
|
||||
|
||||
protected CacheControl getCacheControl() {
|
||||
return CacheControl.maxAge(getJHipsterHttpCacheProperty(), TimeUnit.DAYS).cachePublic();
|
||||
}
|
||||
|
||||
private int getJHipsterHttpCacheProperty() {
|
||||
return jhipsterProperties.getHttp().getCache().getTimeToLiveInDays();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package it.sw.pa.comune.artegna.config;
|
||||
|
||||
import static java.net.URLDecoder.decode;
|
||||
|
||||
import jakarta.servlet.*;
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.web.server.*;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
/**
|
||||
* Configuration of web application with Servlet 3.0 APIs.
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer<WebServerFactory> {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
LOG.info("Web application fully configured");
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the Servlet engine: Mime types, the document root, the cache.
|
||||
*/
|
||||
@Override
|
||||
public void customize(WebServerFactory server) {
|
||||
// When running in an IDE or with ./gradlew bootRun, set location of the static web assets.
|
||||
setLocationForStaticAssets(server);
|
||||
}
|
||||
|
||||
private void setLocationForStaticAssets(WebServerFactory server) {
|
||||
if (server instanceof ConfigurableServletWebServerFactory servletWebServer) {
|
||||
File root;
|
||||
String prefixPath = resolvePathPrefix();
|
||||
root = Path.of(prefixPath + "build/resources/main/static/").toFile();
|
||||
if (root.exists() && root.isDirectory()) {
|
||||
servletWebServer.setDocumentRoot(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve path prefix to static resources.
|
||||
*/
|
||||
private String resolvePathPrefix() {
|
||||
String fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8);
|
||||
String rootPath = Path.of(".").toUri().normalize().getPath();
|
||||
String extractedPath = fullExecutablePath.replace(rootPath, "");
|
||||
int extractionEndIndex = extractedPath.indexOf("build/");
|
||||
if (extractionEndIndex <= 0) {
|
||||
return "";
|
||||
}
|
||||
return extractedPath.substring(0, extractionEndIndex);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application configuration.
|
||||
*/
|
||||
package it.sw.pa.comune.artegna.config;
|
||||
@@ -0,0 +1,77 @@
|
||||
package it.sw.pa.comune.artegna.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;
|
||||
}
|
||||
}
|
||||
99
src/main/java/it/sw/pa/comune/artegna/domain/Authority.java
Normal file
99
src/main/java/it/sw/pa/comune/artegna/domain/Authority.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package it.sw.pa.comune.artegna.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,131 @@
|
||||
package it.sw.pa.comune.artegna.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
|
||||
/**
|
||||
* Persistent tokens are used by Spring Security to automatically log in users.
|
||||
*
|
||||
* @see it.sw.pa.comune.artegna.security.PersistentTokenRememberMeServices
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "jhi_persistent_token")
|
||||
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
|
||||
public class PersistentToken implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final int MAX_USER_AGENT_LEN = 255;
|
||||
|
||||
@Id
|
||||
private String series;
|
||||
|
||||
@JsonIgnore
|
||||
@NotNull
|
||||
@Column(name = "token_value", nullable = false)
|
||||
private String tokenValue;
|
||||
|
||||
@Column(name = "token_date")
|
||||
private LocalDate tokenDate;
|
||||
|
||||
//an IPV6 address max length is 39 characters
|
||||
@Size(min = 0, max = 39)
|
||||
@Column(name = "ip_address", length = 39)
|
||||
private String ipAddress;
|
||||
|
||||
@Column(name = "user_agent")
|
||||
private String userAgent;
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToOne
|
||||
private User user;
|
||||
|
||||
public String getSeries() {
|
||||
return series;
|
||||
}
|
||||
|
||||
public void setSeries(String series) {
|
||||
this.series = series;
|
||||
}
|
||||
|
||||
public String getTokenValue() {
|
||||
return tokenValue;
|
||||
}
|
||||
|
||||
public void setTokenValue(String tokenValue) {
|
||||
this.tokenValue = tokenValue;
|
||||
}
|
||||
|
||||
public LocalDate getTokenDate() {
|
||||
return tokenDate;
|
||||
}
|
||||
|
||||
public void setTokenDate(LocalDate tokenDate) {
|
||||
this.tokenDate = tokenDate;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
if (userAgent.length() >= MAX_USER_AGENT_LEN) {
|
||||
this.userAgent = userAgent.substring(0, MAX_USER_AGENT_LEN - 1);
|
||||
} else {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof PersistentToken)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(series, ((PersistentToken) o).series);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(series);
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PersistentToken{" +
|
||||
"series='" + series + '\'' +
|
||||
", tokenValue='" + tokenValue + '\'' +
|
||||
", tokenDate=" + tokenDate +
|
||||
", ipAddress='" + ipAddress + '\'' +
|
||||
", userAgent='" + userAgent + '\'' +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
247
src/main/java/it/sw/pa/comune/artegna/domain/User.java
Normal file
247
src/main/java/it/sw/pa/comune/artegna/domain/User.java
Normal file
@@ -0,0 +1,247 @@
|
||||
package it.sw.pa.comune.artegna.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import it.sw.pa.comune.artegna.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")
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
@Pattern(regexp = Constants.LOGIN_REGEX)
|
||||
@Size(min = 1, max = 50)
|
||||
@Column(length = 50, unique = true, nullable = false)
|
||||
private String login;
|
||||
|
||||
@JsonIgnore
|
||||
@NotNull
|
||||
@Size(min = 60, max = 60)
|
||||
@Column(name = "password_hash", length = 60, nullable = false)
|
||||
private String password;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "first_name", length = 50)
|
||||
private String firstName;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "last_name", length = 50)
|
||||
private String lastName;
|
||||
|
||||
@Email
|
||||
@Size(min = 5, max = 254)
|
||||
@Column(length = 254, unique = true)
|
||||
private String email;
|
||||
|
||||
@NotNull
|
||||
@Column(nullable = false)
|
||||
private boolean activated = false;
|
||||
|
||||
@Size(min = 2, max = 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<>();
|
||||
|
||||
@JsonIgnore
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "user")
|
||||
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Set<PersistentToken> persistentTokens = 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;
|
||||
}
|
||||
|
||||
public Set<PersistentToken> getPersistentTokens() {
|
||||
return persistentTokens;
|
||||
}
|
||||
|
||||
public void setPersistentTokens(Set<PersistentToken> persistentTokens) {
|
||||
this.persistentTokens = persistentTokens;
|
||||
}
|
||||
|
||||
@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 it.sw.pa.comune.artegna.domain;
|
||||
4
src/main/java/it/sw/pa/comune/artegna/package-info.java
Normal file
4
src/main/java/it/sw/pa/comune/artegna/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application root.
|
||||
*/
|
||||
package it.sw.pa.comune.artegna;
|
||||
@@ -0,0 +1,12 @@
|
||||
package it.sw.pa.comune.artegna.repository;
|
||||
|
||||
import it.sw.pa.comune.artegna.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,16 @@
|
||||
package it.sw.pa.comune.artegna.repository;
|
||||
|
||||
import it.sw.pa.comune.artegna.domain.PersistentToken;
|
||||
import it.sw.pa.comune.artegna.domain.User;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the {@link PersistentToken} entity.
|
||||
*/
|
||||
public interface PersistentTokenRepository extends JpaRepository<PersistentToken, String> {
|
||||
List<PersistentToken> findByUser(User user);
|
||||
|
||||
List<PersistentToken> findByTokenDateBefore(LocalDate localDate);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package it.sw.pa.comune.artegna.repository;
|
||||
|
||||
import it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.repository;
|
||||
@@ -0,0 +1,15 @@
|
||||
package it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.security;
|
||||
|
||||
import it.sw.pa.comune.artegna.domain.Authority;
|
||||
import it.sw.pa.comune.artegna.domain.User;
|
||||
import it.sw.pa.comune.artegna.repository.UserRepository;
|
||||
import java.util.*;
|
||||
import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 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,222 @@
|
||||
package it.sw.pa.comune.artegna.security;
|
||||
|
||||
import it.sw.pa.comune.artegna.domain.PersistentToken;
|
||||
import it.sw.pa.comune.artegna.repository.PersistentTokenRepository;
|
||||
import it.sw.pa.comune.artegna.repository.UserRepository;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.rememberme.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
import tech.jhipster.security.PersistentTokenCache;
|
||||
import tech.jhipster.security.RandomUtil;
|
||||
|
||||
/**
|
||||
* Custom implementation of Spring Security's RememberMeServices.
|
||||
* <p>
|
||||
* Persistent tokens are used by Spring Security to automatically log in users.
|
||||
* <p>
|
||||
* This is a specific implementation of Spring Security's remember-me authentication, but it is much
|
||||
* more powerful than the standard implementations:
|
||||
* <ul>
|
||||
* <li>It allows a user to see the list of his currently opened sessions, and invalidate them</li>
|
||||
* <li>It stores more information, such as the IP address and the user agent, for audit purposes</li>
|
||||
* <li>When a user logs out, only his current session is invalidated, and not all of his sessions</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Please note that it allows the use of the same token for 5 seconds, and this value stored in a specific
|
||||
* cache during that period. This is to allow concurrent requests from the same user: otherwise, two
|
||||
* requests being sent at the same time could invalidate each other's token.
|
||||
* <p>
|
||||
* This is inspired by:
|
||||
* <ul>
|
||||
* <li><a href="https://github.com/blog/1661-modeling-your-app-s-user-session">GitHub's "Modeling your App's User Session"</a></li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The main algorithm comes from Spring Security's {@code PersistentTokenBasedRememberMeServices}, but this class
|
||||
* couldn't be cleanly extended.
|
||||
*/
|
||||
@Service
|
||||
public class PersistentTokenRememberMeServices extends AbstractRememberMeServices {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersistentTokenRememberMeServices.class);
|
||||
|
||||
// Token is valid for one month
|
||||
private static final int TOKEN_VALIDITY_DAYS = 31;
|
||||
|
||||
private static final int TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * TOKEN_VALIDITY_DAYS;
|
||||
|
||||
private static final long UPGRADED_TOKEN_VALIDITY_MILLIS = 5000l;
|
||||
|
||||
private final PersistentTokenCache<UpgradedRememberMeToken> upgradedTokenCache;
|
||||
|
||||
private final PersistentTokenRepository persistentTokenRepository;
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public PersistentTokenRememberMeServices(
|
||||
JHipsterProperties jHipsterProperties,
|
||||
org.springframework.security.core.userdetails.UserDetailsService userDetailsService,
|
||||
PersistentTokenRepository persistentTokenRepository,
|
||||
UserRepository userRepository
|
||||
) {
|
||||
super(jHipsterProperties.getSecurity().getRememberMe().getKey(), userDetailsService);
|
||||
this.persistentTokenRepository = persistentTokenRepository;
|
||||
this.userRepository = userRepository;
|
||||
upgradedTokenCache = new PersistentTokenCache<>(UPGRADED_TOKEN_VALIDITY_MILLIS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
|
||||
synchronized (this) {
|
||||
// prevent 2 authentication requests from the same user in parallel
|
||||
String login = null;
|
||||
UpgradedRememberMeToken upgradedToken = upgradedTokenCache.get(cookieTokens[0]);
|
||||
if (upgradedToken != null) {
|
||||
login = upgradedToken.getUserLoginIfValid(cookieTokens);
|
||||
LOG.debug("Detected previously upgraded login token for user '{}'", login);
|
||||
}
|
||||
|
||||
if (login == null) {
|
||||
PersistentToken token = getPersistentToken(cookieTokens);
|
||||
login = token.getUser().getLogin();
|
||||
|
||||
// Token also matches, so login is valid. Update the token value, keeping the *same* series number.
|
||||
LOG.debug("Refreshing persistent login token for user '{}', series '{}'", login, token.getSeries());
|
||||
token.setTokenDate(LocalDate.now());
|
||||
token.setTokenValue(RandomUtil.generateRandomAlphanumericString());
|
||||
token.setIpAddress(request.getRemoteAddr());
|
||||
token.setUserAgent(request.getHeader("User-Agent"));
|
||||
try {
|
||||
persistentTokenRepository.saveAndFlush(token);
|
||||
} catch (DataAccessException e) {
|
||||
LOG.error("Failed to update token: ", e);
|
||||
throw new RememberMeAuthenticationException("Autologin failed due to data access problem", e);
|
||||
}
|
||||
addCookie(token, request, response);
|
||||
upgradedTokenCache.put(cookieTokens[0], new UpgradedRememberMeToken(cookieTokens, login));
|
||||
}
|
||||
return getUserDetailsService().loadUserByUsername(login);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
|
||||
String login = successfulAuthentication.getName();
|
||||
|
||||
LOG.debug("Creating new persistent login for user {}", login);
|
||||
PersistentToken token = userRepository
|
||||
.findOneByLogin(login)
|
||||
.map(u -> {
|
||||
PersistentToken t = new PersistentToken();
|
||||
t.setSeries(RandomUtil.generateRandomAlphanumericString());
|
||||
t.setUser(u);
|
||||
t.setTokenValue(RandomUtil.generateRandomAlphanumericString());
|
||||
t.setTokenDate(LocalDate.now());
|
||||
t.setIpAddress(request.getRemoteAddr());
|
||||
t.setUserAgent(request.getHeader("User-Agent"));
|
||||
return t;
|
||||
})
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User " + login + " was not found in the database"));
|
||||
try {
|
||||
persistentTokenRepository.saveAndFlush(token);
|
||||
addCookie(token, request, response);
|
||||
} catch (DataAccessException e) {
|
||||
LOG.error("Failed to save persistent token ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When logout occurs, only invalidate the current token, and not all user sessions.
|
||||
* <p>
|
||||
* The standard Spring Security implementations are too basic: they invalidate all tokens for the
|
||||
* current user, so when he logs out from one browser, all his other sessions are destroyed.
|
||||
*
|
||||
* @param request the request.
|
||||
* @param response the response.
|
||||
* @param authentication the authentication.
|
||||
*/
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
String rememberMeCookie = extractRememberMeCookie(request);
|
||||
if (rememberMeCookie != null && rememberMeCookie.length() != 0) {
|
||||
try {
|
||||
String[] cookieTokens = decodeCookie(rememberMeCookie);
|
||||
PersistentToken token = getPersistentToken(cookieTokens);
|
||||
persistentTokenRepository.deleteById(token.getSeries());
|
||||
} catch (InvalidCookieException ice) {
|
||||
LOG.info("Invalid cookie, no persistent token could be deleted", ice);
|
||||
} catch (RememberMeAuthenticationException rmae) {
|
||||
LOG.debug("No persistent token found, so no token could be deleted", rmae);
|
||||
}
|
||||
}
|
||||
super.logout(request, response, authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the token and return it.
|
||||
*/
|
||||
private PersistentToken getPersistentToken(String[] cookieTokens) {
|
||||
if (cookieTokens.length != 2) {
|
||||
throw new InvalidCookieException(
|
||||
"Cookie token did not contain " + 2 + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'"
|
||||
);
|
||||
}
|
||||
String presentedSeries = cookieTokens[0];
|
||||
String presentedToken = cookieTokens[1];
|
||||
Optional<PersistentToken> optionalToken = persistentTokenRepository.findById(presentedSeries);
|
||||
if (!optionalToken.isPresent()) {
|
||||
// No series match, so we can't authenticate using this cookie
|
||||
throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
|
||||
}
|
||||
PersistentToken token = optionalToken.orElseThrow();
|
||||
// We have a match for this user/series combination
|
||||
LOG.info("presentedToken={} / tokenValue={}", presentedToken, token.getTokenValue());
|
||||
if (!presentedToken.equals(token.getTokenValue())) {
|
||||
// Token doesn't match series value. Delete this session and throw an exception.
|
||||
persistentTokenRepository.deleteById(token.getSeries());
|
||||
throw new CookieTheftException("Invalid remember-me token (Series/token) mismatch. Implies previous " + "cookie theft attack.");
|
||||
}
|
||||
if (token.getTokenDate().plusDays(TOKEN_VALIDITY_DAYS).isBefore(LocalDate.now())) {
|
||||
persistentTokenRepository.deleteById(token.getSeries());
|
||||
throw new RememberMeAuthenticationException("Remember-me login has expired");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private void addCookie(PersistentToken token, HttpServletRequest request, HttpServletResponse response) {
|
||||
setCookie(new String[] { token.getSeries(), token.getTokenValue() }, TOKEN_VALIDITY_SECONDS, request, response);
|
||||
}
|
||||
|
||||
private static class UpgradedRememberMeToken implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String[] upgradedToken;
|
||||
|
||||
private final String userLogin;
|
||||
|
||||
UpgradedRememberMeToken(String[] upgradedToken, String userLogin) {
|
||||
this.upgradedToken = upgradedToken;
|
||||
this.userLogin = userLogin;
|
||||
}
|
||||
|
||||
String getUserLoginIfValid(String[] currentToken) {
|
||||
if (currentToken[0].equals(this.upgradedToken[0]) && currentToken[1].equals(this.upgradedToken[1])) {
|
||||
return this.userLogin;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package it.sw.pa.comune.artegna.security;
|
||||
|
||||
import java.util.Arrays;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Utility class for Spring Security.
|
||||
*/
|
||||
public final class SecurityUtils {
|
||||
|
||||
private SecurityUtils() {}
|
||||
|
||||
/**
|
||||
* Get the login of the current user.
|
||||
*
|
||||
* @return the login of the current user.
|
||||
*/
|
||||
public static Optional<String> getCurrentUserLogin() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
return Optional.ofNullable(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 String s) {
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 -> Arrays.asList(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 it.sw.pa.comune.artegna.security;
|
||||
|
||||
import it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.security;
|
||||
@@ -0,0 +1,13 @@
|
||||
package it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.service;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
public class InvalidPasswordException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidPasswordException() {
|
||||
super("Incorrect password");
|
||||
}
|
||||
}
|
||||
120
src/main/java/it/sw/pa/comune/artegna/service/MailService.java
Normal file
120
src/main/java/it/sw/pa/comune/artegna/service/MailService.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package it.sw.pa.comune.artegna.service;
|
||||
|
||||
import it.sw.pa.comune.artegna.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");
|
||||
}
|
||||
}
|
||||
349
src/main/java/it/sw/pa/comune/artegna/service/UserService.java
Normal file
349
src/main/java/it/sw/pa/comune/artegna/service/UserService.java
Normal file
@@ -0,0 +1,349 @@
|
||||
package it.sw.pa.comune.artegna.service;
|
||||
|
||||
import it.sw.pa.comune.artegna.config.Constants;
|
||||
import it.sw.pa.comune.artegna.domain.Authority;
|
||||
import it.sw.pa.comune.artegna.domain.User;
|
||||
import it.sw.pa.comune.artegna.repository.AuthorityRepository;
|
||||
import it.sw.pa.comune.artegna.repository.PersistentTokenRepository;
|
||||
import it.sw.pa.comune.artegna.repository.UserRepository;
|
||||
import it.sw.pa.comune.artegna.security.AuthoritiesConstants;
|
||||
import it.sw.pa.comune.artegna.security.SecurityUtils;
|
||||
import it.sw.pa.comune.artegna.service.dto.AdminUserDTO;
|
||||
import it.sw.pa.comune.artegna.service.dto.UserDTO;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
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 PersistentTokenRepository persistentTokenRepository;
|
||||
|
||||
private final AuthorityRepository authorityRepository;
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
public UserService(
|
||||
UserRepository userRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
PersistentTokenRepository persistentTokenRepository,
|
||||
AuthorityRepository authorityRepository,
|
||||
CacheManager cacheManager
|
||||
) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.persistentTokenRepository = persistentTokenRepository;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent Token are used for providing automatic authentication, they should be automatically deleted after
|
||||
* 30 days.
|
||||
* <p>
|
||||
* This is scheduled to get fired every day, at midnight.
|
||||
*/
|
||||
@Scheduled(cron = "0 0 0 * * ?")
|
||||
public void removeOldPersistentTokens() {
|
||||
LocalDate now = LocalDate.now();
|
||||
persistentTokenRepository
|
||||
.findByTokenDateBefore(now.minusMonths(1))
|
||||
.forEach(token -> {
|
||||
LOG.debug("Deleting token {}", token.getSeries());
|
||||
User user = token.getUser();
|
||||
user.getPersistentTokens().remove(token);
|
||||
persistentTokenRepository.delete(token);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.service.dto;
|
||||
|
||||
import it.sw.pa.comune.artegna.config.Constants;
|
||||
import it.sw.pa.comune.artegna.domain.Authority;
|
||||
import it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.service.dto;
|
||||
|
||||
import it.sw.pa.comune.artegna.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 it.sw.pa.comune.artegna.service.dto;
|
||||
@@ -0,0 +1,150 @@
|
||||
package it.sw.pa.comune.artegna.service.mapper;
|
||||
|
||||
import it.sw.pa.comune.artegna.domain.Authority;
|
||||
import it.sw.pa.comune.artegna.domain.User;
|
||||
import it.sw.pa.comune.artegna.service.dto.AdminUserDTO;
|
||||
import it.sw.pa.comune.artegna.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 Collections.emptySet();
|
||||
}
|
||||
|
||||
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 Collections.emptySet();
|
||||
}
|
||||
|
||||
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 it.sw.pa.comune.artegna.service.mapper;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Service layer.
|
||||
*/
|
||||
package it.sw.pa.comune.artegna.service;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user