Compare commits
17 Commits
bugfix/swa
...
master
Author | SHA1 | Date | |
---|---|---|---|
ce7e3741bd | |||
2a61686918 | |||
cd01d0ab8f | |||
9d251f88e9 | |||
c1d3d583e7 | |||
ad61f2af59 | |||
abddebd7c3 | |||
|
02495de36f | ||
|
19c1e1ba5c | ||
|
a31159eb5b | ||
|
15e94a1800 | ||
78cc729a97 | |||
d6edfa4dc7 | |||
9c8d7616e3 | |||
6a673c66d4 | |||
27de4ce634 | |||
a7ffee9348 |
50
.aliases
50
.aliases
@ -90,10 +90,51 @@ alias pg-sql-restore='gunzip --stdout | docker exec -i hsadmin-ng-postgres psql
|
||||
alias fp='grep -r '@Accepts' src | sed -e 's/^.*@/@/g' | sort -u | wc -l'
|
||||
|
||||
alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources'
|
||||
alias gw-test='. .aliases; ./gradlew test'
|
||||
alias gw-check='. .aliases; gw test check -x pitest'
|
||||
alias gw-check='. .aliases; . .tc-environment; gw test check -x pitest'
|
||||
|
||||
alias cas-curl='bin/cas-curl'
|
||||
# HOWTO: run all 'normal' tests (no scenario+import-tests): `gw-test`
|
||||
# You can also mention specific targets: `gw-test importOfficeData`.
|
||||
# This will always use the environment from `.tc-environment`.
|
||||
#
|
||||
# HOWTO: re-run tests even if no changed can be detected: `gw-test --rerun`
|
||||
# You can also mention specific targets: `gw-test scenarioTest --rerun`.
|
||||
# This will always use the environment from `.tc-environment`.
|
||||
#
|
||||
# HOWTO: run all tests (unit, integration+acceptance, import and scenario): `gw-test --all`
|
||||
# You can also re-run all these tests, which will take ~20min: `gw-test --all --rerun`
|
||||
# This will always use the environment from `.tc-environment`.
|
||||
#
|
||||
function _gwTest1() {
|
||||
echo
|
||||
printf -- '=%0.s' {1..80}; echo
|
||||
echo "RUNNING gw $@"
|
||||
printf -- '-%0.s' {1..80}; echo
|
||||
./gradlew "$@"
|
||||
printf -- '-%0.s' {1..80}; echo
|
||||
echo "DONE gw $@"
|
||||
}
|
||||
function _gwTest() {
|
||||
. .aliases;
|
||||
. .tc-environment;
|
||||
rm /tmp/gwTest.tmp
|
||||
if [ "$1" == "--all" ]; then
|
||||
shift # to remove the --all from $@
|
||||
# delierately in separate gradlew-calls to avoid Testcontains-PostgreSQL problem spillover
|
||||
time (_gwTest1 unitTest "$@" &&
|
||||
_gwTest1 officeIntegrationTest bookingIntegrationTest hostingIntegrationTest "$@" &&
|
||||
_gwTest1 scenarioTest "$@" &&
|
||||
_gwTest1 importOfficeData importHostingAssets "$@");
|
||||
elif [ $# -eq 0 ] || [[ $1 == -* ]]; then
|
||||
time _gwTest1 test "$@";
|
||||
else
|
||||
time _gwTest1 "$@";
|
||||
fi
|
||||
printf -- '=%0.s' {1..80}; echo
|
||||
}
|
||||
alias gw-test=_gwTest
|
||||
|
||||
alias howto=bin/howto
|
||||
alias cas-curl=bin/cas-curl
|
||||
|
||||
# etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries
|
||||
alias gw-importOfficeData-in-docker-compose='
|
||||
@ -106,5 +147,6 @@ if [ ! -f .environment ]; then
|
||||
fi
|
||||
source .environment
|
||||
|
||||
alias scenario-reports-upload='./gradlew scenarioTests convertMarkdownToHtml && ssh hsh03-hsngdev@h50.hostsharing.net "rm -f doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office/*.html" && scp build/doc/scenarios/*.html hsh03-hsngdev@h50.hostsharing.net:doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office'
|
||||
alias scenario-reports-upload='./gradlew scenarioTest convertMarkdownToHtml && ssh hsh03-hsngdev@h50.hostsharing.net "rm -f doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office/*.html" && scp build/doc/scenarios/*.html hsh03-hsngdev@h50.hostsharing.net:doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office'
|
||||
alias scenario-reports-open='open https://hsngdev.hs-example.de/scenarios/office'
|
||||
|
||||
|
@ -4,5 +4,4 @@ export HSADMINNG_POSTGRES_ADMIN_PASSWORD=
|
||||
export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted
|
||||
export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net
|
||||
export HSADMINNG_MIGRATION_DATA_PATH=migration
|
||||
export LIQUIBASE_CONTEXT=
|
||||
export LANG=en_US.UTF-8
|
||||
|
@ -4,5 +4,4 @@ unset HSADMINNG_POSTGRES_ADMIN_PASSWORD
|
||||
unset HSADMINNG_POSTGRES_RESTRICTED_USERNAME
|
||||
unset HSADMINNG_SUPERUSER
|
||||
unset HSADMINNG_MIGRATION_DATA_PATH
|
||||
unset LIQUIBASE_CONTEXT
|
||||
|
||||
|
21
Jenkinsfile
vendored
21
Jenkinsfile
vendored
@ -35,9 +35,24 @@ pipeline {
|
||||
|
||||
stage ('Tests') {
|
||||
parallel {
|
||||
stage('Unit-/Integration/Acceptance-Tests') {
|
||||
stage('Unit-Tests') {
|
||||
steps {
|
||||
sh './gradlew check --no-daemon -x pitest -x dependencyCheckAnalyze -x importOfficeData -x importHostingAssets'
|
||||
sh './gradlew unitTest --no-daemon'
|
||||
}
|
||||
}
|
||||
stage('General-Tests') {
|
||||
steps {
|
||||
sh './gradlew generalTest --no-daemon'
|
||||
}
|
||||
}
|
||||
stage('Office-Tests') {
|
||||
steps {
|
||||
sh './gradlew officeIntegrationTest --no-daemon'
|
||||
}
|
||||
}
|
||||
stage('Booking+Hosting-Tests') {
|
||||
steps {
|
||||
sh './gradlew bookingIntegrationTest hostingIntegrationTest --no-daemon'
|
||||
}
|
||||
}
|
||||
stage('Import-Tests') {
|
||||
@ -47,7 +62,7 @@ pipeline {
|
||||
}
|
||||
stage ('Scenario-Tests') {
|
||||
steps {
|
||||
sh './gradlew scenarioTests --no-daemon'
|
||||
sh './gradlew scenarioTest --no-daemon'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
177
README.md
177
README.md
@ -5,41 +5,48 @@ For architecture consider the files in the `doc` and `adr` folder.
|
||||
|
||||
<!-- generated TOC begin: -->
|
||||
- [Setting up the Development Environment](#setting-up-the-development-environment)
|
||||
- [PostgreSQL Server](#postgresql-server)
|
||||
- [Markdown](#markdown)
|
||||
- [Render Markdown embedded PlantUML](#render-markdown-embedded-plantuml)
|
||||
- [Render Markdown Embedded Mermaid Diagrams](#render-markdown-embedded-mermaid-diagrams)
|
||||
- [IDE Specific Settings](#ide-specific-settings)
|
||||
- [IntelliJ IDEA](#intellij-idea)
|
||||
- [Other Tools](#other-tools)
|
||||
- [PostgreSQL Server](#postgresql-server)
|
||||
- [Markdown](#markdown)
|
||||
- [Render Markdown embedded PlantUML](#render-markdown-embedded-plantuml)
|
||||
- [Render Markdown Embedded Mermaid Diagrams](#render-markdown-embedded-mermaid-diagrams)
|
||||
- [IDE Specific Settings](#ide-specific-settings)
|
||||
- [IntelliJ IDEA](#intellij-idea)
|
||||
- [Other Tools](#other-tools)
|
||||
- [Running the SQL files](#running-the-sql-files)
|
||||
- [For RBAC](#for-rbac)
|
||||
- [For Historization](#for-historization)
|
||||
- [For RBAC](#for-rbac)
|
||||
- [For Historization](#for-historization)
|
||||
- [Coding Guidelines](#coding-guidelines)
|
||||
- [Directory and Package Structure](#directory-and-package-structure)
|
||||
- [General Directory Structure](#general-directory-structure)
|
||||
- [Source Code Package Structure](#source-code-package-structure)
|
||||
- [Run Tests from Command Line](#run-tests-from-command-line)
|
||||
- [Spotless Code Formatting](#spotless-code-formatting)
|
||||
- [JaCoCo Test Code Coverage Check](#jacoco-test-code-coverage-check)
|
||||
- [PiTest Mutation Testing](#pitest-mutation-testing)
|
||||
- [Remark](#remark)
|
||||
- [OWASP Security Vulnerability Check](#owasp-security-vulnerability-check)
|
||||
- [Dependency-License-Compatibility](#dependency-license-compatibility)
|
||||
- [Dependency Version Upgrade](#dependency-version-upgrade)
|
||||
- [Directory and Package Structure](#directory-and-package-structure)
|
||||
- [General Directory Structure](#general-directory-structure)
|
||||
- [Source Code Package Structure](#source-code-package-structure)
|
||||
- [Run Tests from Command Line](#run-tests-from-command-line)
|
||||
- [Spotless Code Formatting](#spotless-code-formatting)
|
||||
- [JaCoCo Test Code Coverage Check](#jacoco-test-code-coverage-check)
|
||||
- [PiTest Mutation Testing](#pitest-mutation-testing)
|
||||
- [Remark](#remark)
|
||||
- [OWASP Security Vulnerability Check](#owasp-security-vulnerability-check)
|
||||
- [Dependency-License-Compatibility](#dependency-license-compatibility)
|
||||
- [Dependency Version Upgrade](#dependency-version-upgrade)
|
||||
- [Biggest Flaws in our Architecture](#biggest-flaws-in-our-architecture)
|
||||
- [The RBAC System is too Complicated](#the-rbac-system-is-too-complicated)
|
||||
- [The Mapper is Error-Prone](#the-mapper-is-error-prone)
|
||||
- [Too Many Business-Rules Implemented in Controllers](#too-many-business-rules-implemented-in-controllers)
|
||||
- [How To ...](#how-to-...)
|
||||
- [How to Configure .pgpass for the Default PostgreSQL Database?](#how-to-configure-.pgpass-for-the-default-postgresql-database?)
|
||||
- [How to Run the Tests Against a Local User-Space Podman Daemon?](#how-to-run-the-tests-against-a-local-user-space-podman-daemon?)
|
||||
- [Install and Run Podman](#install-and-run-podman)
|
||||
- [Use the Command Line to Run the Tests Against the Podman Daemon ](#use-the-command-line-to-run-the-tests-against-the-podman-daemon-)
|
||||
- [Use IntelliJ IDEA Run the Tests Against the Podman Daemon](#use-intellij-idea-run-the-tests-against-the-podman-daemon)
|
||||
- [~/.testcontainers.properties](#~/.testcontainers.properties)
|
||||
- [How to Run the Tests Against a Remote Podman or Docker Daemon?](#how-to-run-the-tests-against-a-remote-podman-or-docker-daemon?)
|
||||
- [How to Run the Application on a Different Port?](#how-to-run-the-application-on-a-different-port?)
|
||||
- [How to Use a Persistent Database for Integration Tests?](#how-to-use-a-persistent-database-for-integration-tests?)
|
||||
- [How to Amend Liquibase SQL Changesets?](#how-to-amend-liquibase-sql-changesets?)
|
||||
- [How to Re-Generate Spring-Controller-Interfaces from OpenAPI specs?](#how-to-re-generate-spring-controller-interfaces-from-openapi-specs?)
|
||||
- [How to Generate Database Table Diagrams?](#how-to-generate-database-table-diagrams?)
|
||||
- [How to Run the Application With Other Profiles, e.g. production](#)
|
||||
- [How to Do a Clean Run of the Application](#how-to-do-a-clean-run-of-the-application)
|
||||
- [How to Configure .pgpass for the Default PostgreSQL Database?](#how-to-configure-.pgpass-for-the-default-postgresql-database?)
|
||||
- [How to Run the Tests Against a Local User-Space Podman Daemon?](#how-to-run-the-tests-against-a-local-user-space-podman-daemon?)
|
||||
- [Install and Run Podman](#install-and-run-podman)
|
||||
- [Use the Command Line to Run the Tests Against the Podman Daemon ](#use-the-command-line-to-run-the-tests-against-the-podman-daemon-)
|
||||
- [Use IntelliJ IDEA Run the Tests Against the Podman Daemon](#use-intellij-idea-run-the-tests-against-the-podman-daemon)
|
||||
- [~/.testcontainers.properties](#~/.testcontainers.properties)
|
||||
- [How to Run the Tests Against a Remote Podman or Docker Daemon?](#how-to-run-the-tests-against-a-remote-podman-or-docker-daemon?)
|
||||
- [How to Run the Application on a Different Port?](#how-to-run-the-application-on-a-different-port?)
|
||||
- [How to Use a Persistent Database for Integration Tests?](#how-to-use-a-persistent-database-for-integration-tests?)
|
||||
- [How to Amend Liquibase SQL Changesets?](#how-to-amend-liquibase-sql-changesets?)
|
||||
- [How to Re-Generate Spring-Controller-Interfaces from OpenAPI specs?](#how-to-re-generate-spring-controller-interfaces-from-openapi-specs?)
|
||||
- [How to Generate Database Table Diagrams?](#how-to-generate-database-table-diagrams?)
|
||||
- [How to Add (Real) Admin Users](#how-to-add-(real)-admin-users)
|
||||
- [Further Documentation](#further-documentation)
|
||||
<!-- generated TOC end. -->
|
||||
|
||||
@ -51,10 +58,11 @@ Everything is tested on _Ubuntu Linux 22.04_ and _MacOS Monterey (12.4)_.
|
||||
To be able to build and run the Java Spring Boot application, you need the following tools:
|
||||
|
||||
- Docker 20.x (on MacOS you also need *Docker Desktop* or similar) or Podman
|
||||
- optionally: PostgreSQL Server 15.5-bookworm
|
||||
- optionally: PostgreSQL Server 15.5-bookworm, if you want to use the database directly, not just via Docker
|
||||
(see instructions below to install and run in Docker)
|
||||
- The matching Java JDK at will be automatically installed by Gradle toolchain support to `~/.gradle/jdks/`.
|
||||
- You also might need an IDE (e.g. *IntelliJ IDEA* or *Eclipse* or *VS Code* with *[STS](https://spring.io/tools)* and a GUI Frontend for *PostgreSQL* like *Postbird*.
|
||||
- Python 3 is expected in /usr/bin/python3 if you want to run the `howto` tool (see `bin/howto`)
|
||||
|
||||
If you have at least Docker and the Java JDK installed in appropriate versions and in your `PATH`, then you can start like this:
|
||||
|
||||
@ -64,7 +72,12 @@ If you have at least Docker and the Java JDK installed in appropriate versions a
|
||||
gw # initially downloads the configured Gradle version into the project
|
||||
|
||||
gw test # compiles and runs unit- and integration-tests - takes >10min even on a fast machine
|
||||
gw scenarioTests # compiles and scenario-tests - takes ~1min on a decent machine
|
||||
# `gw test` does NOT run import- and scenario-tests.
|
||||
# Use `gw-test` instead to make sure .tc-environment is sourced.
|
||||
gw scenarioTest # compiles and scenario-tests - takes ~1min on a decent machine
|
||||
# Use `gw-test scenarioTest` instead to make sure .tc-environment is sourced.
|
||||
|
||||
howto test # shows more test information about how to run tests
|
||||
|
||||
# if the container has not been built yet, run this:
|
||||
pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432
|
||||
@ -72,12 +85,22 @@ If you have at least Docker and the Java JDK installed in appropriate versions a
|
||||
# if the container has been built already and you want to keep the data, run this:
|
||||
pg-sql-start
|
||||
|
||||
Next, compile and run the application without CAS-authentication on `localhost:8080`:
|
||||
Next, compile and run the application on `localhost:8080` and the management server on `localhost:8081`:
|
||||
|
||||
# this disables CAS-authentication, for using the REST-API with CAS-authentication, see `bin/cas-curl`.
|
||||
export HSADMINNG_CAS_SERVER=
|
||||
gw bootRun
|
||||
|
||||
For using the REST-API with CAS-authentication, see `bin/cas-curl`.
|
||||
# this runs the application with test-data and all modules:
|
||||
gw bootRun --args='--spring.profiles.active=dev,complete,test-data'
|
||||
|
||||
The meaning of these profiles is:
|
||||
|
||||
- **dev**: the PostgreSQL users are created via Liquibase
|
||||
- **complete**: all modules are started
|
||||
- **test-data**: some test data inserted
|
||||
|
||||
Running just `gw bootRun` would just run the *office* module, not insert any test-data and
|
||||
require the PostgreSQL users created in the database (see env-vars in `.aliases`).
|
||||
|
||||
Now we can access the REST API, e.g. using curl:
|
||||
|
||||
@ -109,7 +132,7 @@ Also try for example 'admin@xxx.example.com' or 'unknown@example.org'.
|
||||
|
||||
If you want a formatted JSON output, you can pipe the result to `jq` or similar.
|
||||
|
||||
And to see the full, currently implemented, API, open http://localhost:8080/swagger-ui/index.html.
|
||||
And to see the full, currently implemented, API, open http://localhost:8081/actuator/swagger-ui/index.html (uses management-port and thus bypasses authentication).
|
||||
|
||||
If you still need to install some of these tools, find some hints in the next chapters.
|
||||
|
||||
@ -189,7 +212,7 @@ To generate the TOC (Table of Contents), a little bash script from a
|
||||
Given this is in PATH as `md-toc`, use:
|
||||
|
||||
```shell
|
||||
md-toc <README.md 2 4 | cut -c5-'
|
||||
md-toc <README.md 2 4 | cut -c5-
|
||||
```
|
||||
|
||||
To render the Markdown files, especially to watch embedded PlantUML diagrams, you can use one of the following methods:
|
||||
@ -427,36 +450,42 @@ Some of these rules are checked with *ArchUnit* unit tests.
|
||||
|
||||
### Run Tests from Command Line
|
||||
|
||||
Run all tests which have not yet been passed with the current source code:
|
||||
Run all unit-, integration- and acceptance-tests which have not yet been passed with the current source code:
|
||||
|
||||
```shell
|
||||
gw test
|
||||
gw test # uses the current environment, especially HSADMINNG_POSTGRES_JDBC_URL
|
||||
```
|
||||
|
||||
If the referenced database is not empty, the tests might fail.
|
||||
|
||||
To explicitly use the Testcontainers-environment, run:
|
||||
|
||||
```shell
|
||||
gw-test # uses the environment from .tc-environment
|
||||
```
|
||||
|
||||
Force running all tests:
|
||||
|
||||
```shell
|
||||
gw cleanTest test
|
||||
gw-test --rerun
|
||||
```
|
||||
|
||||
To find more options about running tests, try `howto test`.
|
||||
|
||||
|
||||
### Spotless Code Formatting
|
||||
|
||||
Code formatting for Java is checked via *spotless*.
|
||||
The formatting style can be checked with this command:
|
||||
|
||||
```shell
|
||||
gw spotlessCheck
|
||||
```
|
||||
|
||||
This task is also included in `gw build` and `gw check`.
|
||||
|
||||
To apply formatting rules, use:
|
||||
|
||||
```shell
|
||||
gw spotlessApply
|
||||
gw-spotless
|
||||
```
|
||||
|
||||
The gradle task spotlessCheck is also included in `gw build` and `gw check`,
|
||||
thus if the formatting is not compliant to the rules, the build is going to fail.
|
||||
|
||||
|
||||
### JaCoCo Test Code Coverage Check
|
||||
|
||||
This project uses the JaCoCo test code coverage report with limit checks.
|
||||
@ -494,13 +523,12 @@ Classes to be scanned, tests to be executed and thresholds are configured in [bu
|
||||
A report is generated under [build/reports/pitest/index.html](./build/reports/pitest/index.html).
|
||||
A link to the report is also printed after the `pitest` run.
|
||||
|
||||
This task is also executed as part of `gw check`.
|
||||
<!-- TODO.test: This task is also executed as part of `gw check`. -->
|
||||
|
||||
#### Remark
|
||||
|
||||
In this project, there is little business logic in *Java* code;
|
||||
most business code is in *plsql*
|
||||
and *Java* ist mostly used for mapping REST calls to database queries.
|
||||
In this project, there is a large amount of code is in *plsql*, especially for RBAC.
|
||||
*Java* ist mostly used for mapping and validating REST calls to database queries.
|
||||
This mapping ist mostly done through *Spring* annotations and other implicit code.
|
||||
|
||||
Therefore, there are only few unit tests and thus mutation testing has limited value.
|
||||
@ -534,7 +562,7 @@ In case of suppression, a note must be added to explain why it does not apply to
|
||||
|
||||
See also: https://jeremylong.github.io/DependencyCheck/dependency-check-gradle/index.html.
|
||||
|
||||
### Dependency-License-Compatibility
|
||||
### How to Check Dependency-License-Compatibility
|
||||
|
||||
The `gw check` phase depends on a dependency-license-compatibility check.
|
||||
If any dependency violates the configured [list of allowed licenses](etc/allowed-licenses.json), the build will fail.
|
||||
@ -564,7 +592,7 @@ The generated license can be found here: [index.html](build/reports/dependency-l
|
||||
|
||||
More information can be found on the [project's website](https://github.com/jk1/Gradle-License-Report).
|
||||
|
||||
### Dependency Version Upgrade
|
||||
### How to Upgrade Versions of Dependencies
|
||||
|
||||
Dependency versions can be automatically upgraded to the latest available version:
|
||||
|
||||
@ -592,8 +620,9 @@ This way we would get rid of all explicit grants within the same DB-row
|
||||
and would not need the `rbac.role` table anymore.
|
||||
We would also reduce the depth of the expensive recursive CTE-query.
|
||||
|
||||
This has to be explored further.
|
||||
For now, we just keep it in mind and
|
||||
This has to be explored further. For now, we just keep it in mind and avoid roles+grants
|
||||
which would not fit into a simplified system with a fixed role-type-system.
|
||||
|
||||
|
||||
### The Mapper is Error-Prone
|
||||
|
||||
@ -617,6 +646,36 @@ Besides the following *How Tos* you can also find several *How Tos* in the sourc
|
||||
grep -r HOWTO src
|
||||
```
|
||||
|
||||
also try this (assumed you've sourced .aliases):
|
||||
```sh
|
||||
howto
|
||||
```
|
||||
|
||||
### How to Run the Application With Other Profiles, e.g. production:
|
||||
|
||||
Add `--args='--spring.profiles.active=...` with the wanted profile selector:
|
||||
|
||||
```sh
|
||||
gw bootRun --args='--spring.profiles.active=external-db,only -office,without-test-data'
|
||||
```
|
||||
|
||||
These profiles mean:
|
||||
|
||||
- **external-db**: an external PostgreSQL database is used with the PostgreSQL users already created as specified in the environment
|
||||
- **only-office**: only the Office module is started, but neither the Booking nor the Hosting modules
|
||||
- **without-test-data**: no test-data is inserted
|
||||
|
||||
|
||||
### How to Do a Clean Run of the Application
|
||||
|
||||
If you frequently need to run with a fresh database and a clean build, you can use this:
|
||||
|
||||
```sh
|
||||
export HSADMINNG_CAS_SERVER=
|
||||
gw clean && pg-sql-reset && sleep 5 && gw bootRun' 2>&1 | tee log
|
||||
```
|
||||
|
||||
|
||||
### How to Configure .pgpass for the Default PostgreSQL Database?
|
||||
|
||||
To access the default database schema as used during development, add this line to your `.pgpass` file in your users home directory:
|
||||
|
86
bin/howto
Executable file
86
bin/howto
Executable file
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
from urllib.parse import urljoin, quote
|
||||
|
||||
def path_to_file_uri(path):
|
||||
"""
|
||||
Converts a file path to a file URI.
|
||||
Handles absolute and relative paths.
|
||||
"""
|
||||
abs_path = os.path.abspath(path)
|
||||
return urljoin("file://", quote(abs_path))
|
||||
|
||||
def is_binary_file(filepath, chunk_size=1024):
|
||||
"""
|
||||
Prüft, ob eine Datei binär ist, indem sie den Inhalt der Datei auf nicht-druckbare Zeichen untersucht.
|
||||
"""
|
||||
try:
|
||||
with open(filepath, "rb") as file:
|
||||
chunk = file.read(chunk_size)
|
||||
if b"\0" in chunk: # Nullbyte ist ein typisches Zeichen für Binärdateien
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Prüfen, ob Datei binär ist: {filepath}: {e}")
|
||||
return True
|
||||
|
||||
def search_keywords_in_files(keywords):
|
||||
if not keywords:
|
||||
print("Bitte geben Sie mindestens ein Stichwort an.")
|
||||
sys.exit(1)
|
||||
|
||||
# Allowed comment symbols
|
||||
comment_symbols = {"//", "#", ";"}
|
||||
|
||||
for root, dirs, files in os.walk("."):
|
||||
# Ausschließen bestimmter Verzeichnisse
|
||||
dirs[:] = [d for d in dirs if d not in {".git", "build"}]
|
||||
|
||||
for file in files:
|
||||
filepath = os.path.join(root, file)
|
||||
|
||||
# Überspringen von Binärdateien
|
||||
if is_binary_file(filepath):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for line_number, line in enumerate(lines, start=1):
|
||||
stripped_line = line.lstrip() # Entfernt führende Leerzeichen
|
||||
for symbol in comment_symbols:
|
||||
if stripped_line.startswith(symbol):
|
||||
# Entfernt das Kommentarzeichen und nachfolgende Leerzeichen
|
||||
howtoMatch = stripped_line[len(symbol):].lstrip()
|
||||
if howtoMatch.startswith(("HOWTO ", "HOWTO: ", "How to ")):
|
||||
if all(keyword in howtoMatch.lower() for keyword in keywords):
|
||||
|
||||
# Titelzeile ohne Kommentarzeichen
|
||||
print(howtoMatch.rstrip())
|
||||
|
||||
# Ausgabe nachfolgender Zeilen mit dem gleichen Kommentar-Präfix
|
||||
for subsequent_line in lines[line_number:]:
|
||||
subsequent_line = subsequent_line.lstrip()
|
||||
if subsequent_line.startswith(symbol):
|
||||
# Entfernt Kommentarzeichen aus Folgezeilen
|
||||
print("\t" + subsequent_line[len(symbol):].strip())
|
||||
else:
|
||||
break
|
||||
|
||||
# Link mit Zeilennummer
|
||||
print(f"--> {path_to_file_uri(filepath)}:{line_number}")
|
||||
|
||||
# Abstand zwischen Matches
|
||||
print()
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Lesen der Datei {filepath}: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Verwendung: bin/howto <Stichwort1> <Stichwort2> ...")
|
||||
sys.exit(1)
|
||||
|
||||
search_keywords_in_files([arg.lower() for arg in sys.argv[1:]])
|
139
build.gradle
139
build.gradle
@ -1,17 +1,20 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.3.4'
|
||||
id 'io.spring.dependency-management' version '1.1.6'
|
||||
id 'io.openapiprocessor.openapi-processor' version '2023.2'
|
||||
id 'com.github.jk1.dependency-license-report' version '2.9'
|
||||
id "org.owasp.dependencycheck" version "10.0.4"
|
||||
id "com.diffplug.spotless" version "6.25.0"
|
||||
id 'jacoco'
|
||||
id 'info.solidsoft.pitest' version '1.15.0'
|
||||
id 'se.patrikerdes.use-latest-versions' version '0.2.18'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'org.springframework.boot' version '3.4.1'
|
||||
id 'io.spring.dependency-management' version '1.1.7' // manages implicit dependencies
|
||||
id 'io.openapiprocessor.openapi-processor' version '2023.2' // generates Controller-interface and resources from API-spec
|
||||
id 'com.github.jk1.dependency-license-report' version '2.9' // checks dependency-license compatibility
|
||||
id "org.owasp.dependencycheck" version "12.0.1" // checks dependencies for known vulnerabilities
|
||||
id "com.diffplug.spotless" version "7.0.2" // formats + checks formatting for source-code
|
||||
id 'jacoco' // determines code-coverage of tests
|
||||
id 'info.solidsoft.pitest' version '1.15.0' // performs mutation testing
|
||||
id 'se.patrikerdes.use-latest-versions' version '0.2.18' // updates module and plugin versions
|
||||
id 'com.github.ben-manes.versions' version '0.52.0' // determines which dependencies have updates
|
||||
}
|
||||
|
||||
// HOWTO: find out which dependency versions are managed by Spring Boot:
|
||||
// https://docs.spring.io/spring-boot/appendix/dependency-versions/coordinates.html
|
||||
|
||||
group = 'net.hostsharing'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
|
||||
@ -20,6 +23,9 @@ wrapper {
|
||||
gradleVersion = '8.5'
|
||||
}
|
||||
|
||||
// TODO.impl: self-attaching is deprecated, see:
|
||||
// https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
@ -60,25 +66,25 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.2'
|
||||
implementation 'org.springdoc:springdoc-openapi:2.6.0'
|
||||
implementation 'org.postgresql:postgresql:42.7.4'
|
||||
implementation 'org.liquibase:liquibase-core:4.29.2'
|
||||
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.8.3'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.0'
|
||||
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.10.0'
|
||||
implementation 'org.springdoc:springdoc-openapi:2.8.3'
|
||||
implementation 'org.postgresql:postgresql'
|
||||
implementation 'org.liquibase:liquibase-core'
|
||||
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.9.0'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
||||
implementation 'org.apache.commons:commons-text:1.12.0'
|
||||
implementation 'net.java.dev.jna:jna:5.15.0'
|
||||
implementation 'org.modelmapper:modelmapper:3.2.1'
|
||||
implementation 'org.apache.commons:commons-text:1.13.0'
|
||||
implementation 'net.java.dev.jna:jna:5.16.0'
|
||||
implementation 'org.modelmapper:modelmapper:3.2.2'
|
||||
implementation 'org.iban4j:iban4j:3.2.10-RELEASE'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
|
||||
implementation 'org.webjars:swagger-ui:5.17.14'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3'
|
||||
implementation 'org.reflections:reflections:0.10.2'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
testCompileOnly 'org.projectlombok:lombok'
|
||||
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
// TODO.impl: version conflict with SpringDoc, check later and re-enable if fixed
|
||||
// developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||
@ -90,7 +96,7 @@ dependencies {
|
||||
testImplementation 'org.testcontainers:postgresql'
|
||||
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
||||
testImplementation 'io.rest-assured:spring-mock-mvc'
|
||||
testImplementation 'org.hamcrest:hamcrest-core:3.0'
|
||||
testImplementation 'org.hamcrest:hamcrest-core'
|
||||
testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||
testImplementation 'org.wiremock:wiremock-standalone:3.10.0'
|
||||
@ -174,7 +180,9 @@ openapiProcessor {
|
||||
}
|
||||
}
|
||||
sourceSets.main.java.srcDir 'build/generated/sources/openapi'
|
||||
|
||||
abstract class ProcessSpring extends DefaultTask {}
|
||||
|
||||
tasks.register('processSpring', ProcessSpring)
|
||||
['processSpringRoot',
|
||||
'processSpringRbac',
|
||||
@ -205,7 +213,7 @@ openApiGenerate.dependsOn processSpring
|
||||
spotless {
|
||||
java {
|
||||
removeUnusedImports()
|
||||
indentWithSpaces(4)
|
||||
leadingTabsToSpaces(4)
|
||||
endWithNewline()
|
||||
toggleOffOn()
|
||||
|
||||
@ -219,7 +227,7 @@ project.tasks.check.dependsOn(spotlessCheck)
|
||||
// HACK: no idea why spotless uses the output of these tasks, but we get warnings without those
|
||||
project.tasks.spotlessJava.dependsOn(
|
||||
tasks.generateLicenseReport,
|
||||
tasks.pitest,
|
||||
// tasks.pitest, TODO.test: PiTest currently does not work, needs to be fixed
|
||||
tasks.jacocoTestReport,
|
||||
tasks.processResources,
|
||||
tasks.processTestResources)
|
||||
@ -248,19 +256,21 @@ licenseReport {
|
||||
}
|
||||
project.tasks.check.dependsOn(checkLicense)
|
||||
|
||||
// JaCoCo Test Code Coverage
|
||||
jacoco {
|
||||
toolVersion = "0.8.10"
|
||||
}
|
||||
// HOWTO: run all tests except import- and scenario-tests: gw test
|
||||
test {
|
||||
finalizedBy jacocoTestReport // generate report after tests
|
||||
excludes = [
|
||||
'net.hostsharing.hsadminng.**.generated.**',
|
||||
]
|
||||
useJUnitPlatform {
|
||||
excludeTags 'importOfficeData', 'importHostingData', 'scenarioTest'
|
||||
excludeTags 'importOfficeData', 'importHostingAssets', 'scenarioTest'
|
||||
}
|
||||
}
|
||||
|
||||
// JaCoCo Test Code Coverage for unit-tests
|
||||
jacoco {
|
||||
toolVersion = "0.8.10"
|
||||
}
|
||||
jacocoTestReport {
|
||||
dependsOn test
|
||||
afterEvaluate {
|
||||
@ -325,6 +335,67 @@ jacocoTestCoverageVerification {
|
||||
}
|
||||
}
|
||||
|
||||
// HOWTO: run all unit-tests which don't need a database: gw-test unitTest
|
||||
tasks.register('unitTest', Test) {
|
||||
useJUnitPlatform {
|
||||
excludeTags 'importOfficeData', 'importHostingAssets', 'scenarioTest', 'generalIntegrationTest',
|
||||
'officeIntegrationTest', 'bookingIntegrationTest', 'hostingIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs all unit-tests which do not need a database'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// HOWTO: run all integration tests which are not specific to a module, like base, rbac, config etc.
|
||||
tasks.register('generalIntegrationTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'generalIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs integration tests which are not specific to a module, like base, rbac, config etc.'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// HOWTO: run all integration tests of the office module: gw-test officeIntegrationTest
|
||||
tasks.register('officeIntegrationTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'officeIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs integration tests of the office module'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// HOWTO: run all integration tests of the booking module: gw-test bookingIntegrationTest
|
||||
tasks.register('bookingIntegrationTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'bookingIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs integration tests of the booking module'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// HOWTO: run all integration tests of the hosting module: gw-test hostingIntegrationTest
|
||||
tasks.register('hostingIntegrationTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'hostingIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs integration tests of the hosting module'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
tasks.register('importOfficeData', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'importOfficeData'
|
||||
@ -347,7 +418,7 @@ tasks.register('importHostingAssets', Test) {
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
tasks.register('scenarioTests', Test) {
|
||||
tasks.register('scenarioTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'scenarioTest'
|
||||
}
|
||||
@ -368,7 +439,7 @@ pitest {
|
||||
]
|
||||
|
||||
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
|
||||
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*']
|
||||
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*', '**ImportOfficeData', '**ImportHostingAssets']
|
||||
|
||||
pitestVersion = '1.17.0'
|
||||
junit5PluginVersion = '1.1.0'
|
||||
@ -383,7 +454,7 @@ pitest {
|
||||
outputFormats = ['XML', 'HTML']
|
||||
timestampedReports = false
|
||||
}
|
||||
project.tasks.check.dependsOn(project.tasks.pitest)
|
||||
// project.tasks.check.dependsOn(project.tasks.pitest) TODO.test: PiTest currently does not work, needs to be fixed
|
||||
project.tasks.pitest.doFirst { // Why not doLast? See README.md!
|
||||
println "PiTest Mutation Report: file:///${project.rootDir}/build/reports/pitest/index.html"
|
||||
}
|
||||
@ -447,7 +518,7 @@ tasks.register('convertMarkdownToHtml') {
|
||||
}
|
||||
}
|
||||
}
|
||||
convertMarkdownToHtml.dependsOn scenarioTests
|
||||
convertMarkdownToHtml.dependsOn scenarioTest
|
||||
|
||||
// shortcut for compiling all files
|
||||
tasks.register('compile') {
|
||||
|
@ -50,6 +50,7 @@ classDiagram
|
||||
UNKNOWN: nur für Import
|
||||
NATURAL_PERSON: natürliche Person
|
||||
LEGAL_PERSON: z.B. GmbH, e.K., eG, e.V.
|
||||
ORGANIZATIONAL_UNIT: z.B. "Admin-Team", "Buchhaltung"
|
||||
INCORORATED_FIRM: z.B. OHG, Partnerschaftsgesellschaft
|
||||
UNINCORPORATED_FIRM: z.B. GbR, ARGE, Erbengemeinschaft
|
||||
PUBLIC_INSTITUTION: KdöR, AöR [ohne Registergericht/Registernummer]
|
||||
|
@ -5,9 +5,23 @@
|
||||
{ "moduleLicense": "Apache-2.0" },
|
||||
{ "moduleLicense": "Apache License 2.0" },
|
||||
{ "moduleLicense": "Apache License v2.0" },
|
||||
{ "moduleLicense": "Apache License Version 2.0" },
|
||||
{ "moduleLicense": "Apache License, Version 2.0" },
|
||||
{ "moduleLicense": "The Apache License, Version 2.0" },
|
||||
{ "moduleLicense": "The Apache Software License, Version 2.0" },
|
||||
|
||||
{
|
||||
"moduleLicense": null,
|
||||
"#moduleLicense": "Apache License 2.0, see https://github.com/springdoc/springdoc-openapi/blob/main/LICENSE",
|
||||
"moduleVersion": "2.4.0",
|
||||
"moduleName": "org.springdoc:springdoc-openapi"
|
||||
},
|
||||
{
|
||||
"moduleLicense": null,
|
||||
"moduleVersion": "1.0.0",
|
||||
"moduleName": "org.jspecify:jspecify"
|
||||
},
|
||||
|
||||
{ "moduleLicense": "BSD License" },
|
||||
{ "moduleLicense": "BSD-2-Clause" },
|
||||
{ "moduleLicense": "BSD-3-Clause" },
|
||||
@ -46,14 +60,8 @@
|
||||
{
|
||||
"moduleLicense": "Public Domain, per Creative Commons CC0",
|
||||
"moduleVersion": "2.0.3"
|
||||
},
|
||||
|
||||
{
|
||||
"moduleLicense": null,
|
||||
"#moduleLicense": "Apache License 2.0, see https://github.com/springdoc/springdoc-openapi/blob/main/LICENSE",
|
||||
"moduleVersion": "2.4.0",
|
||||
"moduleName": "org.springdoc:springdoc-openapi"
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
|
@ -9,8 +9,12 @@
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
Malicious HTTP redirect in JAXB on a REST-endpoint is not that dangerous.
|
||||
file name: logback-core-1.5.12.jar
|
||||
A successful attack requires the user to have write access to a configuration file or environment vars.
|
||||
]]></notes>
|
||||
<cve>CVE-2024-9329</cve>
|
||||
<packageUrl regex="true">^pkg:maven/ch\.qos\.logback/logback-core@.*$</packageUrl>
|
||||
<cpe>cpe:/a:qos:logback</cpe>
|
||||
<cve>CVE-2024-12798</cve>
|
||||
</suppress>
|
||||
|
||||
</suppressions>
|
||||
|
@ -0,0 +1,105 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// HOWTO: exclude sensitive values, like passwords and other secrets, from being show by actuator endpoints:
|
||||
// either use: add your custom keys to management.endpoint.additionalKeysToSanitize,
|
||||
// or, if you need more heuristics, amend this code down here.
|
||||
@Component
|
||||
public class ActuatorSanitizer implements SanitizingFunction {
|
||||
|
||||
private static final String[] REGEX_PARTS = {"*", "$", "^", "+"};
|
||||
|
||||
private static final Set<String> DEFAULT_KEYS_TO_SANITIZE = Set.of(
|
||||
"password", "secret", "token", ".*credentials.*", "vcap_services", "^vcap\\.services.*$", "sun.java.command", "^spring[._]application[._]json$"
|
||||
);
|
||||
|
||||
private static final Set<String> URI_USERINFO_KEYS = Set.of(
|
||||
"uri", "uris", "url", "urls", "address", "addresses"
|
||||
);
|
||||
|
||||
private static final Pattern URI_USERINFO_PATTERN = Pattern.compile("^\\[?[A-Za-z][A-Za-z0-9\\+\\.\\-]+://.+:(.*)@.+$");
|
||||
|
||||
private final List<Pattern> keysToSanitize = new ArrayList<>();
|
||||
|
||||
public ActuatorSanitizer(@Value("${management.endpoint.additionalKeysToSanitize:}") final List<String> additionalKeysToSanitize) {
|
||||
addKeysToSanitize(DEFAULT_KEYS_TO_SANITIZE);
|
||||
addKeysToSanitize(URI_USERINFO_KEYS);
|
||||
addKeysToSanitize(additionalKeysToSanitize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SanitizableData apply(final SanitizableData data) {
|
||||
if (data.getValue() == null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
for (final Pattern pattern : keysToSanitize) {
|
||||
if (pattern.matcher(data.getKey()).matches()) {
|
||||
if (keyIsUriWithUserInfo(pattern)) {
|
||||
return data.withValue(sanitizeUris(data.getValue().toString()));
|
||||
}
|
||||
|
||||
return data.withValue(SanitizableData.SANITIZED_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void addKeysToSanitize(final Collection<String> keysToSanitize) {
|
||||
for (final String key : keysToSanitize) {
|
||||
this.keysToSanitize.add(getPattern(key));
|
||||
}
|
||||
}
|
||||
|
||||
private Pattern getPattern(final String value) {
|
||||
if (isRegex(value)) {
|
||||
return Pattern.compile(value, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
return Pattern.compile(".*" + value + "$", Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private boolean isRegex(final String value) {
|
||||
for (final String part : REGEX_PARTS) {
|
||||
if (value.contains(part)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean keyIsUriWithUserInfo(final Pattern pattern) {
|
||||
for (String uriKey : URI_USERINFO_KEYS) {
|
||||
if (pattern.matcher(uriKey).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Object sanitizeUris(final String value) {
|
||||
return Arrays.stream(value.split(",")).map(this::sanitizeUri).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private String sanitizeUri(final String value) {
|
||||
final var matcher = URI_USERINFO_PATTERN.matcher(value);
|
||||
final var password = matcher.matches() ? matcher.group(1) : null;
|
||||
if (password != null) {
|
||||
return StringUtils.replace(value, ":" + password + "@", ":" + SanitizableData.SANITIZED_VALUE + "@");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@ -97,6 +97,7 @@ public class RestResponseEntityExceptionHandler
|
||||
return errorResponse(request, HttpStatus.valueOf(statusCode.value()),
|
||||
Optional.ofNullable(response.getBody()).map(Object::toString).orElse(firstMessageLine(exc)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked,rawtypes")
|
||||
protected ResponseEntity handleHttpMessageNotReadable(
|
||||
@ -131,7 +132,7 @@ public class RestResponseEntityExceptionHandler
|
||||
final HttpStatusCode status,
|
||||
final WebRequest request) {
|
||||
final var errorList = exc
|
||||
.getAllValidationResults()
|
||||
.getParameterValidationResults()
|
||||
.stream()
|
||||
.map(ParameterValidationResult::getResolvableErrors)
|
||||
.flatMap(Collection::stream)
|
||||
|
@ -5,6 +5,7 @@ import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
|
||||
@ -24,7 +25,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayAs("BookingDebitor")
|
||||
public class HsBookingDebitorEntity implements Stringifyable {
|
||||
public class HsBookingDebitorEntity implements Stringifyable, WithRoleId {
|
||||
|
||||
public static final String DEBITOR_NUMBER_TAG = "D-";
|
||||
|
||||
|
@ -2,12 +2,14 @@ package net.hostsharing.hsadminng.hs.booking.debitor;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsBookingDebitorRepository extends Repository<HsBookingDebitorEntity, UUID> {
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingDebitorRepository extends Repository<HsBookingDebitorEntity, UUID> {
|
||||
|
||||
@Timed("app.booking.debitor.repo.findByUuid")
|
||||
Optional<HsBookingDebitorEntity> findByUuid(UUID id);
|
||||
|
@ -1,10 +1,12 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface BookingItemCreatedEventRepository extends Repository<BookingItemCreatedEventEntity, UUID> {
|
||||
|
||||
@Timed("app.booking.items.repo.save")
|
||||
|
@ -16,6 +16,7 @@ import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -30,6 +31,7 @@ import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||
|
||||
@RestController
|
||||
@Profile("!only-office")
|
||||
public class HsBookingItemController implements HsBookingItemsApi {
|
||||
|
||||
@Autowired
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingItemRbacRepository extends HsBookingItemRepository<HsBookingItemRbacEntity>,
|
||||
Repository<HsBookingItemRbacEntity, UUID> {
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingItemRealRepository extends HsBookingItemRepository<HsBookingItemRealEntity>,
|
||||
Repository<HsBookingItemRealEntity, UUID> {
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingItemRepository<E extends HsBookingItem> {
|
||||
|
||||
Optional<E> findByUuid(final UUID bookingItemUuid);
|
||||
|
@ -7,8 +7,9 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingProjec
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectResource;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -20,13 +21,14 @@ import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@RestController
|
||||
@Profile("!only-office")
|
||||
public class HsBookingProjectController implements HsBookingProjectsApi {
|
||||
|
||||
@Autowired
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsBookingProjectRbacRepository bookingProjectRepo;
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.project;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingProjectRbacRepository extends HsBookingProjectRepository<HsBookingProjectRbacEntity>,
|
||||
Repository<HsBookingProjectRbacEntity, UUID> {
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.project;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingProjectRealRepository extends HsBookingProjectRepository<HsBookingProjectRealEntity>,
|
||||
Repository<HsBookingProjectRealEntity, UUID> {
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.project;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingProjectRepository<E extends HsBookingProject> {
|
||||
|
||||
@Timed("app.booking.projects.repo.findByUuid")
|
||||
|
@ -89,7 +89,7 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHost
|
||||
@JoinColumn(name = "alarmcontactuuid")
|
||||
private HsOfficeContactRealEntity alarmContact;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.REFRESH }, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
|
||||
private List<HsHostingAssetRealEntity> subHostingAssets;
|
||||
|
||||
|
@ -12,9 +12,10 @@ import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAsse
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -27,6 +28,7 @@ import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@RestController
|
||||
@Profile("!only-office")
|
||||
public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
@Autowired
|
||||
@ -36,7 +38,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsHostingAssetRbacRepository rbacAssetRepo;
|
||||
|
@ -4,6 +4,7 @@ import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntityValidatorRegistry;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@ -12,6 +13,7 @@ import java.util.Map;
|
||||
|
||||
|
||||
@RestController
|
||||
@Profile("!only-office")
|
||||
public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
@ -8,7 +9,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsHostingAssetRbacRepository extends HsHostingAssetRepository<HsHostingAssetRbacEntity>, Repository<HsHostingAssetRbacEntity, UUID> {
|
||||
|
||||
@Timed("app.hostingAsset.repo.findByUuid.rbac")
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
@ -9,6 +10,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsHostingAssetRealRepository extends HsHostingAssetRepository<HsHostingAssetRealEntity>, Repository<HsHostingAssetRealEntity, UUID> {
|
||||
|
||||
@Timed("app.hostingAsset.repo.findByUuid.real")
|
||||
|
@ -1,11 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsHostingAssetRepository<E extends HsHostingAsset> {
|
||||
|
||||
@Timed("app.hosting.assets.repo.findByUuid")
|
||||
|
@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.mapper.ToStringConverter;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
@ -31,8 +31,8 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity newBookingItemRealEntity,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
final StrictMapper StrictMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ abstract class HostingAssetFactory {
|
||||
final EntityManagerWrapper emw;
|
||||
final HsBookingItemRealEntity fromBookingItem;
|
||||
final HsHostingAssetAutoInsertResource asset;
|
||||
final StandardMapper standardMapper;
|
||||
final StrictMapper StrictMapper;
|
||||
|
||||
protected abstract HsHostingAsset create();
|
||||
|
||||
|
@ -9,13 +9,15 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Profile("!only-office")
|
||||
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
|
||||
|
||||
@Autowired
|
||||
@ -25,7 +27,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
||||
private ObjectMapper jsonMapper;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper standardMapper;
|
||||
private StrictMapper StrictMapper;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@ -44,9 +46,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
||||
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
|
||||
final var factory = switch (newBookingItemRealEntity.getType()) {
|
||||
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER ->
|
||||
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
};
|
||||
if (factory != null) {
|
||||
final var statusMessage = factory.createAndPersist();
|
||||
@ -62,9 +64,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity fromBookingItem,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper
|
||||
final StrictMapper StrictMapper
|
||||
) {
|
||||
return new HostingAssetFactory(emw, fromBookingItem, asset, standardMapper) {
|
||||
return new HostingAssetFactory(emw, fromBookingItem, asset, StrictMapper) {
|
||||
|
||||
@Override
|
||||
protected HsHostingAsset create() {
|
||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
@ -19,8 +19,8 @@ public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity newBookingItemRealEntity,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
final StrictMapper StrictMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -32,7 +32,7 @@ public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
|
||||
.map(Enum::name)
|
||||
.orElse(null));
|
||||
}
|
||||
final var managedWebspaceHostingAsset = standardMapper.map(asset, HsHostingAssetRealEntity.class);
|
||||
final var managedWebspaceHostingAsset = StrictMapper.map(asset, HsHostingAssetRealEntity.class);
|
||||
managedWebspaceHostingAsset.setBookingItem(fromBookingItem);
|
||||
emw.createQuery(
|
||||
"SELECT asset FROM HsHostingAssetRealEntity asset WHERE asset.bookingItem.uuid=:bookingItemUuid",
|
||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeBankAccountsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountResource;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.iban4j.BicUtil;
|
||||
import org.iban4j.IbanUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -25,7 +25,7 @@ public class HsOfficeBankAccountController implements HsOfficeBankAccountsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||
|
@ -12,6 +12,7 @@ import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
@ -37,7 +38,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@FieldNameConstants
|
||||
@DisplayAs("Contact")
|
||||
public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContact> {
|
||||
public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContact>, WithRoleId {
|
||||
|
||||
private static Stringify<HsOfficeContact> toString = stringify(HsOfficeContact.class, "contact")
|
||||
.withProp(Fields.caption, HsOfficeContact::getCaption)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.office.contact;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
|
||||
@ -28,7 +28,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeContactRbacRepository contactRepo;
|
||||
@ -131,6 +131,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.putPostalAddress(from(resource.getPostalAddress()));
|
||||
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
||||
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
package net.hostsharing.hsadminng.hs.office.coopassets;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
@ -10,11 +9,22 @@ import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
@ -57,8 +67,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@GeneratedValue(generator = "UUID")
|
||||
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
|
||||
@GeneratedValue
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
@ -122,15 +131,15 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTaggedMemberNumber() {
|
||||
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
public String getTaggedMemberNumber() {
|
||||
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return "%s:%.3s:%+1.2f".formatted(
|
||||
@ -141,7 +150,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("coopAssetsTransaction", HsOfficeCoopAssetsTransactionEntity.class)
|
||||
.withIdentityView(RbacSpec.SQL.projection("reference"))
|
||||
.withIdentityView(SQL.projection("reference"))
|
||||
.withUpdatableColumns("comment")
|
||||
.importEntityAlias("membership", HsOfficeMembershipEntity.class, usingDefaultCase(),
|
||||
dependsOnColumn("membershipUuid"),
|
||||
|
@ -1,14 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.office.coopshares;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
@ -25,6 +24,7 @@ import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.CANCELLATION;
|
||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION;
|
||||
import static net.hostsharing.hsadminng.hs.validation.UuidResolver.resolve;
|
||||
|
||||
@RestController
|
||||
public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi {
|
||||
@ -33,14 +33,16 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeMembershipRepository membershipRepo;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
|
||||
@Timed("app.office.coopShares.api.getListOfCoopShares")
|
||||
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares(
|
||||
final String currentSubject,
|
||||
@ -55,7 +57,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
fromValueDate,
|
||||
toValueDate);
|
||||
|
||||
final var resources = mapper.mapList(entities, HsOfficeCoopSharesTransactionResource.class);
|
||||
final var resources = mapper.mapList(
|
||||
entities,
|
||||
HsOfficeCoopSharesTransactionResource.class,
|
||||
ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(resources);
|
||||
}
|
||||
|
||||
@ -70,7 +75,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
context.define(currentSubject, assumedRoles);
|
||||
validate(requestBody);
|
||||
|
||||
final var entityToSave = mapper.map(requestBody, HsOfficeCoopSharesTransactionEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
final var entityToSave = mapper.map(
|
||||
requestBody,
|
||||
HsOfficeCoopSharesTransactionEntity.class,
|
||||
RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var saved = coopSharesTransactionRepo.save(entityToSave);
|
||||
|
||||
@ -79,7 +87,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
.path("/api/hs/office/coopsharestransactions/{id}")
|
||||
.buildAndExpand(saved.getUuid())
|
||||
.toUri();
|
||||
final var mapped = mapper.map(saved, HsOfficeCoopSharesTransactionResource.class);
|
||||
final var mapped = mapper.map(saved, HsOfficeCoopSharesTransactionResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.created(uri).body(mapped);
|
||||
}
|
||||
|
||||
@ -87,15 +95,18 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
@Transactional(readOnly = true)
|
||||
@Timed("app.office.coopShares.repo.getSingleCoopShareTransactionByUuid")
|
||||
public ResponseEntity<HsOfficeCoopSharesTransactionResource> getSingleCoopShareTransactionByUuid(
|
||||
final String currentSubject, final String assumedRoles, final UUID shareTransactionUuid) {
|
||||
final String currentSubject, final String assumedRoles, final UUID shareTransactionUuid) {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var result = coopSharesTransactionRepo.findByUuid(shareTransactionUuid);
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeCoopSharesTransactionResource.class));
|
||||
final var result = coopSharesTransactionRepo.findByUuid(shareTransactionUuid);
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(mapper.map(
|
||||
result.get(),
|
||||
HsOfficeCoopSharesTransactionResource.class,
|
||||
ENTITY_TO_RESOURCE_POSTMAPPER));
|
||||
|
||||
}
|
||||
|
||||
@ -137,9 +148,16 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
}
|
||||
|
||||
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
if ( resource.getRevertedShareTxUuid() != null ) {
|
||||
entity.setRevertedShareTx(coopSharesTransactionRepo.findByUuid(resource.getRevertedShareTxUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] revertedShareTxUuid %s not found".formatted(resource.getRevertedShareTxUuid()))));
|
||||
entity.setMembership(resolve("membership.uuid", resource.getMembershipUuid(), membershipRepo::findByUuid));
|
||||
if (resource.getRevertedShareTxUuid() != null) {
|
||||
entity.setRevertedShareTx(resolve(
|
||||
"revertedShareTx.uuid",
|
||||
resource.getRevertedShareTxUuid(),
|
||||
coopSharesTransactionRepo::findByUuid));
|
||||
}
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeCoopSharesTransactionEntity, HsOfficeCoopSharesTransactionResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
resource.setMembershipUuid(entity.getMembership().getUuid());
|
||||
};
|
||||
}
|
||||
|
@ -7,13 +7,23 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
@ -132,6 +142,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
NOT_NULL)
|
||||
|
||||
// the membership:ADMIN is not to be confused with the member itself, it's an account manager of the coop
|
||||
.toRole("membership", ADMIN).grantPermission(INSERT)
|
||||
.toRole("membership", ADMIN).grantPermission(UPDATE)
|
||||
.toRole("membership", AGENT).grantPermission(SELECT);
|
||||
|
@ -2,14 +2,16 @@ package net.hostsharing.hsadminng.hs.office.debitor;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeDebitorsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityExistsValidator;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -26,6 +28,7 @@ import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||
import static net.hostsharing.hsadminng.hs.validation.UuidResolver.resolve;
|
||||
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
|
||||
|
||||
@RestController
|
||||
@ -36,16 +39,22 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeDebitorRepository debitorRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeRelationRealRepository relrealRepo;
|
||||
private HsOfficeRelationRealRepository realRelRepo;
|
||||
|
||||
@Autowired
|
||||
private EntityExistsValidator entityValidator;
|
||||
private HsOfficePersonRealRepository realPersonRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeContactRealRepository realContactRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
@ -63,9 +72,9 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
|
||||
final var entities = partnerNumber != null
|
||||
? debitorRepo.findDebitorsByPartnerNumber(cropTag("P-", partnerNumber))
|
||||
: partnerUuid != null
|
||||
? debitorRepo.findDebitorsByPartnerUuid(partnerUuid)
|
||||
: debitorRepo.findDebitorsByOptionalNameLike(name);
|
||||
: partnerUuid != null
|
||||
? debitorRepo.findDebitorsByPartnerUuid(partnerUuid)
|
||||
: debitorRepo.findDebitorsByOptionalNameLike(name);
|
||||
|
||||
final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(resources);
|
||||
@ -81,34 +90,19 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRelUuid() == null,
|
||||
Validate.isTrue(
|
||||
body.getDebitorRel() == null || body.getDebitorRelUuid() == null,
|
||||
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found both");
|
||||
Validate.isTrue(body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
|
||||
Validate.isTrue(
|
||||
body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
|
||||
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found none");
|
||||
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRel().getMark() == null,
|
||||
Validate.isTrue(
|
||||
body.getDebitorRel() == null || body.getDebitorRel().getMark() == null,
|
||||
"ERROR: [400] debitorRel.mark must be null");
|
||||
|
||||
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
|
||||
if (body.getDebitorRel() != null) {
|
||||
final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class);
|
||||
debitorRel.setType(DEBITOR);
|
||||
entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor());
|
||||
entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder());
|
||||
entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact());
|
||||
entityToSave.setDebitorRel(relrealRepo.save(debitorRel));
|
||||
} else {
|
||||
final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid());
|
||||
debitorRelOptional.ifPresentOrElse(
|
||||
debitorRel -> {entityToSave.setDebitorRel(relrealRepo.save(debitorRel));},
|
||||
() -> {
|
||||
throw new ValidationException(
|
||||
"Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid());
|
||||
});
|
||||
}
|
||||
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var savedEntity = debitorRepo.save(entityToSave);
|
||||
em.flush();
|
||||
em.refresh(savedEntity);
|
||||
final var savedEntity = debitorRepo.save(entityToSave).reload(em);
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
@ -181,7 +175,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var current = debitorRepo.findByUuid(debitorUuid).orElseThrow();
|
||||
final var current = debitorRepo.findByUuid(debitorUuid).orElseThrow().reload(em);
|
||||
|
||||
new HsOfficeDebitorEntityPatcher(em, current).apply(body);
|
||||
|
||||
@ -191,7 +185,39 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
final BiConsumer<HsOfficeDebitorInsertResource, HsOfficeDebitorEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
if (resource.getDebitorRel() != null) {
|
||||
final var debitorRel = realRelRepo.save(HsOfficeRelationRealEntity.builder()
|
||||
.type(DEBITOR)
|
||||
.anchor(resolve(
|
||||
"debitorRel.anchor.uuid", resource.getDebitorRel().getAnchorUuid(), realPersonRepo::findByUuid))
|
||||
.holder(resolve(
|
||||
"debitorRel.holder.uuid", resource.getDebitorRel().getHolderUuid(), realPersonRepo::findByUuid))
|
||||
.contact(resolve(
|
||||
"debitorRel.contact.uuid", resource.getDebitorRel().getContactUuid(), realContactRepo::findByUuid))
|
||||
.build());
|
||||
entity.setDebitorRel(debitorRel);
|
||||
} else {
|
||||
final var debitorRelOptional = realRelRepo.findByUuid(resource.getDebitorRelUuid());
|
||||
debitorRelOptional.ifPresentOrElse(
|
||||
debitorRel -> {
|
||||
entity.setDebitorRel(realRelRepo.save(debitorRel));
|
||||
},
|
||||
() -> {
|
||||
throw new ValidationException(
|
||||
"Unable to find debitorRel.uuid: " + resource.getDebitorRelUuid());
|
||||
});
|
||||
}
|
||||
|
||||
if (resource.getRefundBankAccountUuid() != null) {
|
||||
entity.setRefundBankAccount(resolve(
|
||||
"refundBankAccount.uuid", resource.getRefundBankAccountUuid(), bankAccountRepo::findByUuid));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
resource.setDebitorNumber(entity.getTaggedDebitorNumber());
|
||||
resource.getPartner().setPartnerNumber(entity.getPartner().getTaggedPartnerNumber());
|
||||
};
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartner;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
@ -16,7 +17,6 @@ import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.JoinFormula;
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
@ -75,7 +75,6 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
@ -87,16 +86,16 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
value = """
|
||||
(
|
||||
SELECT DISTINCT partner.uuid
|
||||
FROM hs_office.partner_rv partner
|
||||
FROM hs_office.partner partner
|
||||
JOIN hs_office.relation dRel
|
||||
ON dRel.uuid = debitorreluuid AND dRel.type = 'DEBITOR'
|
||||
ON dRel.uuid = debitorRelUuid AND dRel.type = 'DEBITOR'
|
||||
JOIN hs_office.relation pRel
|
||||
ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER'
|
||||
WHERE pRel.holderUuid = dRel.anchorUuid
|
||||
)
|
||||
""")
|
||||
@NotFound(action = NotFoundAction.IGNORE) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
|
||||
private HsOfficePartnerEntity partner;
|
||||
@NotFound(action = NotFoundAction.EXCEPTION) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
|
||||
private HsOfficePartnerRealEntity partner;
|
||||
|
||||
@Column(name = "debitornumbersuffix", length = 2)
|
||||
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
||||
@ -132,9 +131,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
@Override
|
||||
public HsOfficeDebitorEntity load() {
|
||||
BaseEntity.super.load();
|
||||
if (partner != null) {
|
||||
partner.load();
|
||||
}
|
||||
partner.load();
|
||||
debitorRel.load();
|
||||
if (refundBankAccount != null) {
|
||||
refundBankAccount.load();
|
||||
@ -145,7 +142,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
public String getTaggedDebitorNumber() {
|
||||
return ofNullable(partner)
|
||||
.filter(partner -> debitorNumberSuffix != null)
|
||||
.map(HsOfficePartnerEntity::getPartnerNumber)
|
||||
.map(HsOfficePartner::getPartnerNumber)
|
||||
.map(partnerNumber -> DEBITOR_NUMBER_TAG + partnerNumber + debitorNumberSuffix)
|
||||
.orElse(null);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
|
||||
|
||||
@Query("""
|
||||
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
||||
JOIN HsOfficePartnerEntity partner
|
||||
JOIN HsOfficePartnerRealEntity partner
|
||||
ON partner.partnerRel.holder = debitor.debitorRel.anchor
|
||||
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
|
||||
WHERE partner.partnerNumber = :partnerNumber
|
||||
@ -42,7 +42,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
|
||||
|
||||
@Query("""
|
||||
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
||||
JOIN HsOfficePartnerEntity partner
|
||||
JOIN HsOfficePartnerRealEntity partner
|
||||
ON partner.partnerRel.holder = debitor.debitorRel.anchor
|
||||
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
|
||||
JOIN HsOfficePersonRealEntity person
|
||||
|
@ -6,14 +6,16 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembersh
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRbacEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -28,7 +30,10 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePartnerRealRepository partnerRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeMembershipRepository membershipRepo;
|
||||
@ -47,7 +52,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
|
||||
final var entities = partnerNumber != null
|
||||
? membershipRepo.findMembershipsByPartnerNumber(
|
||||
cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, partnerNumber))
|
||||
cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, partnerNumber))
|
||||
: partnerUuid != null
|
||||
? membershipRepo.findMembershipsByPartnerUuid(partnerUuid)
|
||||
: membershipRepo.findAll();
|
||||
@ -68,7 +73,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var entityToSave = mapper.map(body, HsOfficeMembershipEntity.class);
|
||||
final var entityToSave = mapper.map(body, HsOfficeMembershipEntity.class, SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var saved = membershipRepo.save(entityToSave);
|
||||
|
||||
@ -164,5 +169,12 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
if (entity.getValidity().hasUpperBound()) {
|
||||
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
||||
}
|
||||
resource.getPartner().setPartnerNumber(entity.getPartner().getTaggedPartnerNumber()); // TODO.refa: use partner mapper?
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeMembershipInsertResource, HsOfficeMembershipEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.setPartner(partnerRepo.findByUuid(resource.getPartnerUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException(
|
||||
"ERROR: [400] partnerUuid %s not found".formatted(resource.getPartnerUuid()))));
|
||||
};
|
||||
}
|
||||
|
@ -8,11 +8,12 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
@ -63,7 +64,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayAs("Membership")
|
||||
public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEntity>, Stringifyable {
|
||||
public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEntity>, Stringifyable, WithRoleId {
|
||||
|
||||
public static final String MEMBER_NUMBER_TAG = "M-";
|
||||
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
|
||||
@ -84,7 +85,7 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "partneruuid")
|
||||
private HsOfficePartnerEntity partner;
|
||||
private HsOfficePartnerRealEntity partner;
|
||||
|
||||
@Column(name = "membernumbersuffix", length = 2)
|
||||
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
||||
|
@ -2,18 +2,18 @@ package net.hostsharing.hsadminng.hs.office.membership;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
||||
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
|
||||
|
||||
private final StandardMapper mapper;
|
||||
private final StrictMapper mapper;
|
||||
private final HsOfficeMembershipEntity entity;
|
||||
|
||||
public HsOfficeMembershipEntityPatcher(
|
||||
final StandardMapper mapper,
|
||||
final StrictMapper mapper,
|
||||
final HsOfficeMembershipEntity entity) {
|
||||
this.mapper = mapper;
|
||||
this.entity = entity;
|
||||
|
@ -0,0 +1,103 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.Version;
|
||||
import java.util.UUID;
|
||||
|
||||
import static jakarta.persistence.CascadeType.DETACH;
|
||||
import static jakarta.persistence.CascadeType.MERGE;
|
||||
import static jakarta.persistence.CascadeType.PERSIST;
|
||||
import static jakarta.persistence.CascadeType.REFRESH;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@MappedSuperclass
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@DisplayAs("Partner")
|
||||
public class HsOfficePartner<T extends HsOfficePartner<?>> implements Stringifyable, BaseEntity<T> {
|
||||
|
||||
public static final String PARTNER_NUMBER_TAG = "P-";
|
||||
|
||||
protected static Stringify<HsOfficePartner> stringify = stringify(HsOfficePartner.class, "partner")
|
||||
.withIdProp(HsOfficePartner::toShortString)
|
||||
.withProp(p -> ofNullable(p.getPartnerRel())
|
||||
.map(HsOfficeRelation::getHolder)
|
||||
.map(HsOfficePerson::toShortString)
|
||||
.orElse(null))
|
||||
.withProp(p -> ofNullable(p.getPartnerRel())
|
||||
.map(HsOfficeRelation::getContact)
|
||||
.map(HsOfficeContact::toShortString)
|
||||
.orElse(null))
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
|
||||
private Integer partnerNumber;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "partnerreluuid", nullable = false)
|
||||
private HsOfficeRelationRealEntity partnerRel;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "detailsuuid")
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
private HsOfficePartnerDetailsEntity details;
|
||||
|
||||
@Override
|
||||
public T load() {
|
||||
BaseEntity.super.load();
|
||||
partnerRel.load();
|
||||
if (details != null) {
|
||||
details.load();
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public String getTaggedPartnerNumber() {
|
||||
return PARTNER_NUMBER_TAG + partnerNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return getTaggedPartnerNumber();
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@ -26,6 +26,7 @@ import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.EX_PARTNER;
|
||||
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
|
||||
@ -38,10 +39,10 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePartnerRepository partnerRepo;
|
||||
private HsOfficePartnerRbacRepository partnerRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeRelationRealRepository relationRepo;
|
||||
@ -60,7 +61,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
|
||||
final var entities = partnerRepo.findPartnerByOptionalNameLike(name);
|
||||
|
||||
final var resources = mapper.mapList(entities, HsOfficePartnerResource.class);
|
||||
final var resources = mapper.mapList(entities, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(resources);
|
||||
}
|
||||
|
||||
@ -83,7 +84,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
.path("/api/hs/office/partners/{id}")
|
||||
.buildAndExpand(saved.getUuid())
|
||||
.toUri();
|
||||
final var mapped = mapper.map(saved, HsOfficePartnerResource.class);
|
||||
final var mapped = mapper.map(saved, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.created(uri).body(mapped);
|
||||
}
|
||||
|
||||
@ -101,7 +102,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class));
|
||||
final var mapped = mapper.map(result.get(), HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,7 +120,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class));
|
||||
final var mapped = mapper.map(result.get(), HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -161,20 +164,20 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
final var saved = partnerRepo.save(current);
|
||||
optionallyCreateExPartnerRelation(saved, previousPartnerRel);
|
||||
|
||||
final var mapped = mapper.map(saved, HsOfficePartnerResource.class);
|
||||
final var mapped = mapper.map(saved, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
private void optionallyCreateExPartnerRelation(final HsOfficePartnerEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) {
|
||||
private void optionallyCreateExPartnerRelation(final HsOfficePartnerRbacEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) {
|
||||
if (!saved.getPartnerRel().getUuid().equals(previousPartnerRel.getUuid())) {
|
||||
// TODO.impl: we also need to use the new partner-person as the anchor
|
||||
relationRepo.save(previousPartnerRel.toBuilder().uuid(null).type(EX_PARTNER).build());
|
||||
}
|
||||
}
|
||||
|
||||
private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
|
||||
final var entityToSave = new HsOfficePartnerEntity();
|
||||
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
|
||||
private HsOfficePartnerRbacEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
|
||||
final var entityToSave = new HsOfficePartnerRbacEntity();
|
||||
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
|
||||
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
|
||||
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
|
||||
return entityToSave;
|
||||
@ -197,4 +200,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
throw new ReferenceNotFoundException(entityClass, uuid, exc);
|
||||
}
|
||||
}
|
||||
|
||||
final BiConsumer<HsOfficePartnerRbacEntity, HsOfficePartnerResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
resource.setPartnerNumber(entity.getTaggedPartnerNumber());
|
||||
};
|
||||
}
|
||||
|
@ -1,128 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static jakarta.persistence.CascadeType.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "partner_rv")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayAs("Partner")
|
||||
public class HsOfficePartnerEntity implements Stringifyable, BaseEntity<HsOfficePartnerEntity> {
|
||||
|
||||
public static final String PARTNER_NUMBER_TAG = "P-";
|
||||
|
||||
private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner")
|
||||
.withIdProp(HsOfficePartnerEntity::toShortString)
|
||||
.withProp(p -> ofNullable(p.getPartnerRel())
|
||||
.map(HsOfficeRelation::getHolder)
|
||||
.map(HsOfficePerson::toShortString)
|
||||
.orElse(null))
|
||||
.withProp(p -> ofNullable(p.getPartnerRel())
|
||||
.map(HsOfficeRelation::getContact)
|
||||
.map(HsOfficeContact::toShortString)
|
||||
.orElse(null))
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
|
||||
private Integer partnerNumber;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "partnerreluuid", nullable = false)
|
||||
private HsOfficeRelationRealEntity partnerRel;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "detailsuuid")
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
private HsOfficePartnerDetailsEntity details;
|
||||
|
||||
@Override
|
||||
public HsOfficePartnerEntity load() {
|
||||
BaseEntity.super.load();
|
||||
partnerRel.load();
|
||||
details.load();
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTaggedPartnerNumber() {
|
||||
return PARTNER_NUMBER_TAG + partnerNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return getTaggedPartnerNumber();
|
||||
}
|
||||
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("partner", HsOfficePartnerEntity.class)
|
||||
.withIdentityView(SQL.projection("'P-' || partnerNumber"))
|
||||
.withUpdatableColumns("partnerRelUuid")
|
||||
.toRole(GLOBAL, ADMIN).grantPermission(INSERT)
|
||||
|
||||
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationRbacEntity.class,
|
||||
usingDefaultCase(),
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
dependsOnColumn("partnerRelUuid"))
|
||||
.createPermission(DELETE).grantedTo("partnerRel", OWNER)
|
||||
.createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
|
||||
.createPermission(SELECT).grantedTo("partnerRel", TENANT)
|
||||
|
||||
.importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class,
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
dependsOnColumn("detailsUuid"))
|
||||
.createPermission("partnerDetails", DELETE).grantedTo("partnerRel", OWNER)
|
||||
.createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT)
|
||||
.createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); // not TENANT!
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
rbac().generateWithBaseFileName("5-hs-office/504-partner/5043-hs-office-partner-rbac");
|
||||
}
|
||||
}
|
@ -9,10 +9,10 @@ import jakarta.persistence.EntityManager;
|
||||
|
||||
class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> {
|
||||
private final EntityManager em;
|
||||
private final HsOfficePartnerEntity entity;
|
||||
private final HsOfficePartnerRbacEntity entity;
|
||||
HsOfficePartnerEntityPatcher(
|
||||
final EntityManager em,
|
||||
final HsOfficePartnerEntity entity) {
|
||||
final HsOfficePartnerRbacEntity entity) {
|
||||
this.em = em;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import static jakarta.persistence.CascadeType.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "partner_rv")
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@NoArgsConstructor
|
||||
@DisplayAs("RbacPartner")
|
||||
public class HsOfficePartnerRbacEntity extends HsOfficePartner<HsOfficePartnerRbacEntity> {
|
||||
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("partner", HsOfficePartnerRbacEntity.class)
|
||||
.withIdentityView(SQL.projection("'P-' || partnerNumber"))
|
||||
.withUpdatableColumns("partnerRelUuid")
|
||||
.toRole(GLOBAL, ADMIN).grantPermission(INSERT)
|
||||
|
||||
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationRbacEntity.class,
|
||||
usingDefaultCase(),
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
dependsOnColumn("partnerRelUuid"))
|
||||
.createPermission(DELETE).grantedTo("partnerRel", OWNER)
|
||||
.createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
|
||||
.createPermission(SELECT).grantedTo("partnerRel", TENANT)
|
||||
|
||||
.importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class,
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
dependsOnColumn("detailsUuid"))
|
||||
.createPermission("partnerDetails", DELETE).grantedTo("partnerRel", OWNER)
|
||||
.createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT)
|
||||
.createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); // not TENANT!
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
rbac().generateWithBaseFileName("5-hs-office/504-partner/5043-hs-office-partner-rbac");
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsOfficePartnerRbacRepository extends Repository<HsOfficePartnerRbacEntity, UUID> {
|
||||
|
||||
@Timed("app.office.partners.repo.findByUuid.rbac")
|
||||
Optional<HsOfficePartnerRbacEntity> findByUuid(UUID id);
|
||||
|
||||
@Timed("app.office.partners.repo.findAll.rbac")
|
||||
List<HsOfficePartnerRbacEntity> findAll(); // TODO.refa: move to a repo in test sources
|
||||
|
||||
@Query(value = """
|
||||
select partner.uuid, partner.detailsuuid, partner.partnernumber, partner.partnerreluuid, partner.version
|
||||
from hs_office.partner_rv partner
|
||||
join hs_office.relation partnerRel on partnerRel.uuid = partner.partnerreluuid
|
||||
join hs_office.contact contact on contact.uuid = partnerRel.contactuuid
|
||||
join hs_office.person partnerPerson on partnerPerson.uuid = partnerRel.holderuuid
|
||||
left join hs_office.partner_details_rv partnerDetails on partnerDetails.uuid = partner.detailsuuid
|
||||
where :name is null
|
||||
or (partnerDetails.uuid is not null and partnerDetails.birthname like (cast(:name as text) || '%') escape '')
|
||||
or contact.caption like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.tradename like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.givenname like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.familyname like (cast(:name as text) || '%') escape ''
|
||||
""", nativeQuery = true)
|
||||
@Timed("app.office.partners.repo.findPartnerByOptionalNameLike.rbac")
|
||||
List<HsOfficePartnerRbacEntity> findPartnerByOptionalNameLike(String name);
|
||||
|
||||
@Timed("app.office.partners.repo.findPartnerByPartnerNumber.rbac")
|
||||
Optional<HsOfficePartnerRbacEntity> findPartnerByPartnerNumber(Integer partnerNumber);
|
||||
|
||||
@Timed("app.office.partners.repo.save.rbac")
|
||||
HsOfficePartnerRbacEntity save(final HsOfficePartnerRbacEntity entity);
|
||||
|
||||
@Timed("app.office.partners.repo.count.rbac")
|
||||
long count();
|
||||
|
||||
@Timed("app.office.partners.repo.deleteByUuid.rbac")
|
||||
int deleteByUuid(UUID uuid);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "partner")
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@NoArgsConstructor
|
||||
@DisplayAs("RealPartner")
|
||||
public class HsOfficePartnerRealEntity extends HsOfficePartner<HsOfficePartnerRealEntity> {
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsOfficePartnerRealRepository extends Repository<HsOfficePartnerRealEntity, UUID> {
|
||||
|
||||
@Timed("app.office.partners.repo.findByUuid.real")
|
||||
Optional<HsOfficePartnerRealEntity> findByUuid(UUID id);
|
||||
|
||||
@Timed("app.office.partners.repo.findAll.real")
|
||||
List<HsOfficePartnerRbacEntity> findAll(); // TODO.refa: move to a repo in test sources
|
||||
|
||||
@Query(value = """
|
||||
select partner.uuid, partner.detailsuuid, partner.partnernumber, partner.partnerreluuid, partner.version
|
||||
from hs_office.partner partner
|
||||
join hs_office.relation partnerRel on partnerRel.uuid = partner.partnerreluuid
|
||||
join hs_office.contact contact on contact.uuid = partnerRel.contactuuid
|
||||
join hs_office.person partnerPerson on partnerPerson.uuid = partnerRel.holderuuid
|
||||
left join hs_office.partner_details_rv partnerDetails on partnerDetails.uuid = partner.detailsuuid
|
||||
where :name is null
|
||||
or (partnerDetails.uuid is not null and partnerDetails.birthname like (cast(:name as text) || '%') escape '')
|
||||
or contact.caption like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.tradename like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.givenname like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.familyname like (cast(:name as text) || '%') escape ''
|
||||
""", nativeQuery = true)
|
||||
@Timed("app.office.partners.repo.findPartnerByOptionalNameLike.real")
|
||||
List<HsOfficePartnerRealEntity> findPartnerByOptionalNameLike(String name);
|
||||
|
||||
@Timed("app.office.partners.repo.findPartnerByPartnerNumber.real")
|
||||
Optional<HsOfficePartnerRealEntity> findPartnerByPartnerNumber(Integer partnerNumber);
|
||||
|
||||
@Timed("app.office.partners.repo.save.real")
|
||||
HsOfficePartnerRealEntity save(final HsOfficePartnerRealEntity entity);
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEntity, UUID> {
|
||||
|
||||
@Timed("app.office.partners.repo.findByUuid")
|
||||
Optional<HsOfficePartnerEntity> findByUuid(UUID id);
|
||||
|
||||
@Timed("app.office.partners.repo.findAll")
|
||||
List<HsOfficePartnerEntity> findAll(); // TODO.refa: move to a repo in test sources
|
||||
|
||||
@Query("""
|
||||
SELECT partner FROM HsOfficePartnerEntity partner
|
||||
JOIN HsOfficeRelationRealEntity rel ON rel.uuid = partner.partnerRel.uuid
|
||||
JOIN HsOfficeContactRealEntity contact ON contact.uuid = rel.contact.uuid
|
||||
JOIN HsOfficePersonRealEntity person ON person.uuid = rel.holder.uuid
|
||||
WHERE :name is null
|
||||
OR partner.details.birthName like concat(cast(:name as text), '%')
|
||||
OR contact.caption like concat(cast(:name as text), '%')
|
||||
OR person.tradeName like concat(cast(:name as text), '%')
|
||||
OR person.givenName like concat(cast(:name as text), '%')
|
||||
OR person.familyName like concat(cast(:name as text), '%')
|
||||
""")
|
||||
@Timed("app.office.partners.repo.findPartnerByOptionalNameLike")
|
||||
List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name);
|
||||
|
||||
@Timed("app.office.partners.repo.findPartnerByPartnerNumber")
|
||||
Optional<HsOfficePartnerEntity> findPartnerByPartnerNumber(Integer partnerNumber);
|
||||
|
||||
@Timed("app.office.partners.repo.save")
|
||||
HsOfficePartnerEntity save(final HsOfficePartnerEntity entity);
|
||||
|
||||
@Timed("app.office.partners.repo.count")
|
||||
long count();
|
||||
|
||||
@Timed("app.office.partners.repo.deleteByUuid")
|
||||
int deleteByUuid(UUID uuid);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.office.person;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource;
|
||||
@ -24,7 +24,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePersonRbacRepository personRepo;
|
||||
|
@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.person;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
|
||||
@ -17,7 +16,6 @@ import jakarta.persistence.Table;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@FieldNameConstants
|
||||
@DisplayAs("RealPerson")
|
||||
public class HsOfficePersonRealEntity extends HsOfficePerson<HsOfficePersonRealEntity> {
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ public enum HsOfficePersonType {
|
||||
UNKNOWN_PERSON_TYPE("??"),
|
||||
NATURAL_PERSON("NP"), // a human being
|
||||
LEGAL_PERSON("LP"), // incorporated legal entity like A/S, GmbH, e.K., eG, e.V.
|
||||
ORGANIZATIONAL_UNIT("OU"), // groups of persons within an organization, e.g. "Admin-Team", "Buchhaltung"
|
||||
INCORPORATED_FIRM("IF"), // registered business partnership like OHG, Partnerschaftsgesellschaft
|
||||
UNINCORPORATED_FIRM("UF"), // unregistered partnership, association etc. like GbR, ARGE, community of heirs
|
||||
PUBLIC_INSTITUTION("PI"); // entities under public law like government entities, KdöR, AöR
|
||||
|
@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelation
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -32,7 +32,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeRelationRbacRepository rbacRelationRepo;
|
||||
@ -185,6 +185,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRealEntity> CONTACT_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.putPostalAddress(from(resource.getPostalAddress()));
|
||||
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
||||
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
||||
};
|
||||
|
@ -2,11 +2,14 @@ package net.hostsharing.hsadminng.hs.office.sepamandate;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMandatesApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -15,6 +18,7 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -29,7 +33,13 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeDebitorRepository debitorRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeSepaMandateRepository sepaMandateRepo;
|
||||
@ -137,10 +147,22 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
|
||||
if (entity.getValidity().hasUpperBound()) {
|
||||
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
||||
}
|
||||
resource.setDebitor(mapper.map(entity.getDebitor(), HsOfficeDebitorResource.class));
|
||||
resource.getDebitor().setDebitorNumber(entity.getDebitor().getTaggedDebitorNumber());
|
||||
resource.getDebitor().getPartner().setPartnerNumber(entity.getDebitor().getPartner().getTaggedPartnerNumber());
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo()));
|
||||
entity.setDebitor(debitorRepo.findByUuid(resource.getDebitorUuid()).orElseThrow( () ->
|
||||
new ValidationException(
|
||||
"debitor.uuid='" + resource.getDebitorUuid() + "' not found or not accessible"
|
||||
)
|
||||
));
|
||||
entity.setBankAccount(bankAccountRepo.findByUuid(resource.getBankAccountUuid()).orElseThrow( () ->
|
||||
new ValidationException(
|
||||
"bankAccount.uuid='" + resource.getBankAccountUuid() + "' not found or not accessible"
|
||||
)
|
||||
));
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
@UtilityClass
|
||||
public class UuidResolver {
|
||||
|
||||
public static <T> T resolve(final String jsonPath, final UUID uuid, final Function<UUID, Optional<T>> findByUuid) {
|
||||
return findByUuid.apply(uuid)
|
||||
.orElseThrow(() -> new ValidationException("Unable to find " + jsonPath + ": " + uuid));
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package net.hostsharing.hsadminng.mapper;
|
||||
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A nicer API for ModelMapper in standard mode.
|
||||
*/
|
||||
@Component
|
||||
public class StandardMapper extends Mapper {
|
||||
|
||||
public StandardMapper(@Autowired final EntityManagerWrapper em) {
|
||||
super(em);
|
||||
getConfiguration().setAmbiguityIgnored(true);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.persistence;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BaseEntity<T extends BaseEntity<?>> {
|
||||
@ -15,4 +16,10 @@ public interface BaseEntity<T extends BaseEntity<?>> {
|
||||
//noinspection unchecked
|
||||
return (T) this;
|
||||
};
|
||||
|
||||
default T reload(final EntityManager em) {
|
||||
em.flush();
|
||||
em.refresh(this);
|
||||
return load();
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
package net.hostsharing.hsadminng.persistence;
|
||||
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.validation.ValidationException;
|
||||
|
||||
@Service
|
||||
public class EntityExistsValidator {
|
||||
|
||||
@Autowired
|
||||
private EntityManagerWrapper em;
|
||||
|
||||
public <T extends BaseEntity<T>> void validateEntityExists(final String property, final T entitySkeleton) {
|
||||
final var foundEntity = em.find(entityClass(entitySkeleton), entitySkeleton.getUuid());
|
||||
if ( foundEntity == null) {
|
||||
throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends BaseEntity<T>> Class<?> entityClass(final T entityOrProxy) {
|
||||
final var entityClass = entityClass(entityOrProxy.getClass());
|
||||
if (entityClass == null) {
|
||||
throw new IllegalArgumentException("@Entity not found in superclass hierarchy of " + entityOrProxy.getClass());
|
||||
}
|
||||
return entityClass;
|
||||
}
|
||||
|
||||
private static Class<?> entityClass(final Class<?> entityOrProxyClass) {
|
||||
return entityOrProxyClass.isAnnotationPresent(Entity.class)
|
||||
? entityOrProxyClass
|
||||
: entityOrProxyClass.getSuperclass() == null
|
||||
? null
|
||||
: entityClass(entityOrProxyClass.getSuperclass());
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.grant;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacGrantsApi;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -23,7 +23,7 @@ public class RbacGrantController implements RbacGrantsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private RbacGrantRepository rbacGrantRepository;
|
||||
|
@ -30,7 +30,7 @@ public class RbacGrantsDiagramService {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
|
||||
writer.write("""
|
||||
### all grants to %s
|
||||
|
||||
|
||||
```mermaid
|
||||
%s
|
||||
```
|
||||
@ -49,8 +49,18 @@ public class RbacGrantsDiagramService {
|
||||
NON_TEST_ENTITIES;
|
||||
|
||||
public static final EnumSet<Include> ALL = EnumSet.allOf(Include.class);
|
||||
public static final EnumSet<Include> ALL_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, TEST_ENTITIES, PERMISSIONS);
|
||||
public static final EnumSet<Include> ALL_NON_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, NON_TEST_ENTITIES, PERMISSIONS);
|
||||
public static final EnumSet<Include> ALL_TEST_ENTITY_RELATED = EnumSet.of(
|
||||
USERS,
|
||||
DETAILS,
|
||||
NOT_ASSUMED,
|
||||
TEST_ENTITIES,
|
||||
PERMISSIONS);
|
||||
public static final EnumSet<Include> ALL_NON_TEST_ENTITY_RELATED = EnumSet.of(
|
||||
USERS,
|
||||
DETAILS,
|
||||
NOT_ASSUMED,
|
||||
NON_TEST_ENTITIES,
|
||||
PERMISSIONS);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@ -66,9 +76,9 @@ public class RbacGrantsDiagramService {
|
||||
|
||||
public String allGrantsTocurrentSubject(final EnumSet<Include> includes) {
|
||||
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
|
||||
for ( UUID subjectUuid: context.fetchCurrentSubjectOrAssumedRolesUuids() ) {
|
||||
for (UUID subjectUuid : context.fetchCurrentSubjectOrAssumedRolesUuids()) {
|
||||
traverseGrantsTo(graph, subjectUuid, includes);
|
||||
}
|
||||
}
|
||||
return toMermaidFlowchart(graph, includes);
|
||||
}
|
||||
|
||||
@ -78,7 +88,7 @@ public class RbacGrantsDiagramService {
|
||||
if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm:")) {
|
||||
return;
|
||||
}
|
||||
if ( !g.getDescendantIdName().startsWith("role:rbac.global")) {
|
||||
if (!g.getDescendantIdName().startsWith("role:rbac.global")) {
|
||||
if (!includes.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(":rbactest.")) {
|
||||
return;
|
||||
}
|
||||
@ -94,12 +104,17 @@ public class RbacGrantsDiagramService {
|
||||
}
|
||||
|
||||
public String allGrantsFrom(final UUID targetObject, final String op, final EnumSet<Include> includes) {
|
||||
final var refUuid = (UUID) em.createNativeQuery("SELECT uuid FROM rbac.permission WHERE objectuuid=:targetObject AND op=:op")
|
||||
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
|
||||
|
||||
@SuppressWarnings("unchecked") // List -> List<List<UUID>>
|
||||
final var refUuidLists = (List<List<UUID>>) em.createNativeQuery(
|
||||
"select uuid from rbac.permission where objectUuid=:targetObject and op=:op",
|
||||
List.class)
|
||||
.setParameter("targetObject", targetObject)
|
||||
.setParameter("op", op)
|
||||
.getSingleResult();
|
||||
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
|
||||
traverseGrantsFrom(graph, refUuid, includes);
|
||||
.getResultList();
|
||||
refUuidLists.stream().flatMap(Collection::stream)
|
||||
.forEach(refUuid -> traverseGrantsFrom(graph, refUuid, includes));
|
||||
return toMermaidFlowchart(graph, includes);
|
||||
}
|
||||
|
||||
@ -125,20 +140,20 @@ public class RbacGrantsDiagramService {
|
||||
final var entities =
|
||||
includes.contains(DETAILS)
|
||||
? graph.stream()
|
||||
.flatMap(g -> Stream.of(
|
||||
new Node(g.getAscendantIdName(), g.getAscendingUuid()),
|
||||
new Node(g.getDescendantIdName(), g.getDescendantUuid()))
|
||||
)
|
||||
.collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName))
|
||||
.entrySet().stream()
|
||||
.map(entity -> "subgraph " + cleanId(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n "
|
||||
+ entity.getValue().stream()
|
||||
.map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n "))
|
||||
.sorted()
|
||||
.distinct()
|
||||
.collect(joining("\n\n ")))
|
||||
.collect(joining("\n\nend\n\n"))
|
||||
+ "\n\nend\n\n"
|
||||
.flatMap(g -> Stream.of(
|
||||
new Node(g.getAscendantIdName(), g.getAscendingUuid()),
|
||||
new Node(g.getDescendantIdName(), g.getDescendantUuid()))
|
||||
)
|
||||
.collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName))
|
||||
.entrySet().stream()
|
||||
.map(entity -> "subgraph " + cleanId(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n "
|
||||
+ entity.getValue().stream()
|
||||
.map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n "))
|
||||
.sorted()
|
||||
.distinct()
|
||||
.collect(joining("\n\n ")))
|
||||
.collect(joining("\n\nend\n\n"))
|
||||
+ "\n\nend\n\n"
|
||||
: "";
|
||||
|
||||
final var grants = graph.stream()
|
||||
@ -193,7 +208,7 @@ public class RbacGrantsDiagramService {
|
||||
final var refType = refType(idName);
|
||||
|
||||
if (refType.equals("user")) {
|
||||
final var displayName = idName.substring(refType.length()+1);
|
||||
final var displayName = idName.substring(refType.length() + 1);
|
||||
return "(" + displayName + "\nref:" + uuid + ")";
|
||||
}
|
||||
if (refType.equals("role")) {
|
||||
@ -215,15 +230,20 @@ public class RbacGrantsDiagramService {
|
||||
@NotNull
|
||||
private static String cleanId(final String idName) {
|
||||
return idName.replaceAll("@.*", "")
|
||||
.replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", "").replace(">", ":").replace("|", "_");
|
||||
.replace("[", "")
|
||||
.replace("]", "")
|
||||
.replace("(", "")
|
||||
.replace(")", "")
|
||||
.replace(",", "")
|
||||
.replace(">", ":")
|
||||
.replace("|", "_");
|
||||
}
|
||||
|
||||
|
||||
static class LimitedHashSet<T> extends HashSet<T> {
|
||||
|
||||
@Override
|
||||
public boolean add(final T t) {
|
||||
if (size() < GRANT_LIMIT ) {
|
||||
if (size() < GRANT_LIMIT) {
|
||||
return super.add(t);
|
||||
} else {
|
||||
return false;
|
||||
|
@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.role;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacRolesApi;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacRoleResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -19,7 +19,7 @@ public class RbacRoleController implements RbacRolesApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private RbacRoleRepository rbacRoleRepository;
|
||||
|
@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.subject;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacSubjectsApi;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectPermissionResource;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectResource;
|
||||
@ -22,7 +22,7 @@ public class RbacSubjectController implements RbacSubjectsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private RbacSubjectRepository rbacSubjectRepository;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.rbac.test.cust;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.test.generated.api.v1.api.TestCustomersApi;
|
||||
import net.hostsharing.hsadminng.test.generated.api.v1.model.TestCustomerResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -21,7 +21,7 @@ public class TestCustomerController implements TestCustomersApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private TestCustomerRepository testCustomerRepository;
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.rbac.test.pac;
|
||||
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.test.generated.api.v1.api.TestPackagesApi;
|
||||
@ -21,7 +21,7 @@ public class TestPackageController implements TestPackagesApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private TestPackageRepository testPackageRepository;
|
||||
|
@ -90,6 +90,7 @@ components:
|
||||
type: boolean
|
||||
vatReverseCharge:
|
||||
type: boolean
|
||||
# TODO.feat: alternatively the complete refundBankAccount
|
||||
refundBankAccount.uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
|
@ -9,6 +9,7 @@ components:
|
||||
- UNKNOWN_PERSON
|
||||
- NATURAL_PERSON
|
||||
- LEGAL_PERSON
|
||||
- ORGANIZATIONAL_UNIT
|
||||
- INCORPORATED_FIRM
|
||||
- UNINCORPORATED_FIRM
|
||||
- PUBLIC_INSTITUTION
|
||||
|
@ -8,17 +8,33 @@ management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
# HOWTO: view _clickable_ Spring Actuator (Micrometer) Metrics endpoints: http://localhost:8081/actuator/metric-links
|
||||
include: info, health, metrics, metric-links
|
||||
# HOWTO: view _clickable_ Spring Actuator (Micrometer) Metrics endpoints:
|
||||
# http://localhost:8081/actuator/metric-links
|
||||
|
||||
# HOWTO: view all configured endpoints of the running application:
|
||||
# http://localhost:8081/actuator/mappings
|
||||
|
||||
# HOWTO: view the effective application configuration properties:
|
||||
# http://localhost:8081/actuator/configprops
|
||||
|
||||
include: info, health, metrics, metric-links, mappings, openapi, swaggerui, configprops, env
|
||||
endpoint:
|
||||
env:
|
||||
# TODO.spec: check this, maybe set to when_authorized?
|
||||
show-values: always
|
||||
configprops:
|
||||
# TODO.spec: check this, maybe set to when_authorized?
|
||||
show-values: always
|
||||
observations:
|
||||
annotations:
|
||||
enabled: true
|
||||
|
||||
spring:
|
||||
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
password: password
|
||||
url: jdbc:postgresql://localhost:5432/postgres
|
||||
url: ${HSADMINNG_POSTGRES_JDBC_URL}
|
||||
username: postgres
|
||||
|
||||
sql:
|
||||
@ -30,8 +46,12 @@ spring:
|
||||
hibernate:
|
||||
dialect: net.hostsharing.hsadminng.config.PostgresCustomDialect
|
||||
|
||||
liquibase:
|
||||
contexts: dev
|
||||
liquibase:
|
||||
contexts: ${spring.profiles.active}
|
||||
|
||||
# keep this in sync with test/.../application.yml
|
||||
springdoc:
|
||||
use-management-port: true
|
||||
|
||||
hsadminng:
|
||||
postgres:
|
||||
@ -46,4 +66,3 @@ metrics:
|
||||
http:
|
||||
server:
|
||||
requests: true
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
-- ============================================================================
|
||||
-- NUMERIC-HASH-FUNCTIONS
|
||||
--changeset michael.hoennig:hash endDelimiter:--//
|
||||
--changeset michael.hoennig:hash runOnChange:true validCheckSum:ANY endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do $$
|
||||
|
@ -870,18 +870,23 @@ $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:rbac-base-PGSQL-ROLES context:dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:rbac-base-PGSQL-ROLES runOnChange:true validCheckSum:ANY context:!external-db endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do $$
|
||||
begin
|
||||
if '${HSADMINNG_POSTGRES_ADMIN_USERNAME}'='admin' then
|
||||
create role admin;
|
||||
if not exists (select from pg_catalog.pg_roles where rolname = 'admin') then
|
||||
create role admin;
|
||||
end if;
|
||||
grant all privileges on all tables in schema public to admin;
|
||||
end if;
|
||||
|
||||
if '${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}'='restricted' then
|
||||
create role restricted;
|
||||
if not exists (select from pg_catalog.pg_roles where rolname = 'restricted') then
|
||||
create role restricted;
|
||||
end if;
|
||||
|
||||
grant all privileges on all tables in schema public to restricted;
|
||||
end if;
|
||||
end $$;
|
||||
|
@ -170,7 +170,7 @@ commit;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:rbac-global-ADMIN-USERS context:dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:rbac-global-ADMIN-USERS context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
/*
|
||||
Create two users and assign both to the administrators' role.
|
||||
@ -192,7 +192,7 @@ $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:rbac-global-TEST context:dev,tc runAlways:true endDelimiter:--//
|
||||
--changeset michael.hoennig:rbac-global-TEST context:!without-test-data runAlways:true endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
|
@ -67,7 +67,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:test-customer-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:test-customer-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -59,7 +59,7 @@ $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:test-package-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:test-package-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -52,7 +52,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-domain-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-domain-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -4,13 +4,13 @@
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-contact-MIGRATION-mapping endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-contact-MIGRATION-legacy-mapping endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE hs_office.contact_legacy_id
|
||||
(
|
||||
uuid uuid NOT NULL REFERENCES hs_office.contact(uuid),
|
||||
contact_id integer NOT NULL
|
||||
uuid uuid PRIMARY KEY NOT NULL REFERENCES hs_office.contact(uuid),
|
||||
contact_id integer UNIQUE NOT NULL
|
||||
);
|
||||
--//
|
||||
|
||||
|
@ -55,7 +55,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-contact-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-contact-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -8,6 +8,7 @@ CREATE TYPE hs_office.PersonType AS ENUM (
|
||||
'??', -- unknown
|
||||
'NP', -- natural person
|
||||
'LP', -- legal person
|
||||
'OU', -- organizational unit
|
||||
'IF', -- incorporated firm
|
||||
'UF', -- unincorporated firm
|
||||
'PI'); -- public institution
|
||||
|
@ -34,7 +34,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-person-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-person-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -80,7 +80,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-relation-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-relation-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -4,13 +4,13 @@
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-partner-MIGRATION-mapping endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-partner-MIGRATION-legacy-mapping endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE hs_office.partner_legacy_id
|
||||
(
|
||||
uuid uuid NOT NULL REFERENCES hs_office.partner(uuid),
|
||||
bp_id integer NOT NULL
|
||||
uuid uuid PRIMARY KEY NOT NULL REFERENCES hs_office.partner(uuid),
|
||||
bp_id integer UNIQUE NOT NULL
|
||||
);
|
||||
--//
|
||||
|
||||
|
@ -66,7 +66,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-partner-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-partner-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -26,7 +26,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-bankaccount-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-bankaccount-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -45,7 +45,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-debitor-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-debitor-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -4,13 +4,13 @@
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-sepamandate-MIGRATION-mapping endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-sepamandate-MIGRATION-legacy-mapping endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE hs_office.sepamandate_legacy_id
|
||||
(
|
||||
uuid uuid NOT NULL REFERENCES hs_office.sepamandate(uuid),
|
||||
sepa_mandate_id integer NOT NULL
|
||||
uuid uuid PRIMARY KEY NOT NULL REFERENCES hs_office.sepamandate(uuid),
|
||||
sepa_mandate_id integer UNIQUE NOT NULL
|
||||
);
|
||||
--//
|
||||
|
||||
|
@ -38,7 +38,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-sepaMandate-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-sepaMandate-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -28,7 +28,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-membership-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-membership-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -4,13 +4,13 @@
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-coopshares-MIGRATION-mapping endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-coopshares-MIGRATION-legacy-mapping endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE hs_office.coopsharetx_legacy_id
|
||||
(
|
||||
uuid uuid NOT NULL REFERENCES hs_office.coopsharetx(uuid),
|
||||
member_share_id integer NOT NULL
|
||||
uuid uuid PRIMARY KEY NOT NULL REFERENCES hs_office.coopsharetx(uuid),
|
||||
member_share_id integer UNIQUE NOT NULL
|
||||
);
|
||||
--//
|
||||
|
||||
|
@ -38,12 +38,15 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-coopSharesTransaction-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-coopSharesTransaction-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
begin
|
||||
call base.defineContext('creating coopSharesTransaction test-data');
|
||||
call base.defineContext('creating coopSharesTransaction test-data',
|
||||
null,
|
||||
'superuser-alex@hostsharing.net',
|
||||
'rbac.global#global:ADMIN');
|
||||
SET CONSTRAINTS ALL DEFERRED;
|
||||
|
||||
call hs_office.coopsharetx_create_test_data(10001, '01');
|
||||
|
@ -4,13 +4,13 @@
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-coopassets-MIGRATION-mapping endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-coopassets-MIGRATION-legacy-mapping endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE hs_office.coopassettx_legacy_id
|
||||
(
|
||||
uuid uuid NOT NULL REFERENCES hs_office.coopassettx(uuid),
|
||||
member_asset_id integer NOT NULL
|
||||
uuid uuid PRIMARY KEY NOT NULL REFERENCES hs_office.coopassettx(uuid),
|
||||
member_asset_id integer UNIQUE NOT NULL
|
||||
);
|
||||
--//
|
||||
|
||||
|
@ -44,12 +44,15 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-office-coopAssetsTransaction-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-office-coopAssetsTransaction-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
begin
|
||||
call base.defineContext('creating coopAssetsTransaction test-data');
|
||||
call base.defineContext('creating coopAssetsTransaction test-data',
|
||||
null,
|
||||
'superuser-alex@hostsharing.net',
|
||||
'rbac.global#global:ADMIN');
|
||||
SET CONSTRAINTS ALL DEFERRED;
|
||||
|
||||
call hs_office.coopassettx_create_test_data(10001, '01');
|
||||
|
@ -34,7 +34,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-booking-project-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-booking-project-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -40,7 +40,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-booking-item-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-booking-item-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -4,13 +4,13 @@
|
||||
-- Once we don't need the external remote views anymore, create revert changesets.
|
||||
|
||||
-- ============================================================================
|
||||
--changeset hs-hosting-asset-MIGRATION-mapping:1 endDelimiter:--//
|
||||
--changeset hs-hosting-asset-MIGRATION-legacy-mapping:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE hs_hosting.asset_legacy_id
|
||||
(
|
||||
uuid uuid NOT NULL REFERENCES hs_hosting.asset(uuid),
|
||||
legacy_id integer NOT NULL
|
||||
uuid uuid PRIMARY KEY NOT NULL REFERENCES hs_hosting.asset(uuid),
|
||||
legacy_id integer NOT NULL -- not unique because we sometimes create multiple asset types for the same legacy asset
|
||||
);
|
||||
--//
|
||||
|
||||
|
@ -105,7 +105,7 @@ end; $$;
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset michael.hoennig:hs-hosting-asset-TEST-DATA-GENERATION –context=dev,tc endDelimiter:--//
|
||||
--changeset michael.hoennig:hs-hosting-asset-TEST-DATA-GENERATION context:!without-test-data endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
do language plpgsql $$
|
||||
|
@ -0,0 +1,14 @@
|
||||
|
||||
--liquibase formatted sql
|
||||
|
||||
-- ============================================================================
|
||||
--changeset timotheus.pokorra:hs-global-integration-znuny endDelimiter:--//
|
||||
CREATE OR REPLACE VIEW hs_integration.subscription AS
|
||||
SELECT DISTINCT
|
||||
relation.mark as subscription,
|
||||
contact.emailaddresses->>'main' as email
|
||||
FROM hs_office.contact AS contact
|
||||
JOIN hs_office.relation AS relation ON relation.contactuuid = contact.uuid AND relation.type = 'SUBSCRIBER'
|
||||
ORDER BY subscription, email;
|
||||
|
||||
--//
|
@ -29,6 +29,7 @@ databaseChangeLog:
|
||||
file: db/changelog/0-base/030-historization.sql
|
||||
- include:
|
||||
file: db/changelog/0-base/090-log-slow-queries-extensions.sql
|
||||
|
||||
- include:
|
||||
file: db/changelog/1-rbac/1000-rbac-schema.sql
|
||||
- include:
|
||||
@ -49,26 +50,38 @@ databaseChangeLog:
|
||||
file: db/changelog/1-rbac/1059-rbac-statistics.sql
|
||||
- include:
|
||||
file: db/changelog/1-rbac/1080-rbac-global.sql
|
||||
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/200-rbactest-schema.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/201-rbactest-customer/2010-rbactest-customer.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/201-rbactest-customer/2013-rbactest-customer-rbac.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/201-rbactest-customer/2018-rbactest-customer-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/202-rbactest-package/2020-rbactest-package.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/202-rbactest-package/2023-rbactest-package-rbac.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/202-rbactest-package/2028-rbactest-package-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/203-rbactest-domain/2030-rbactest-domain.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/203-rbactest-domain/2033-rbactest-domain-rbac.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/2-rbactest/203-rbactest-domain/2038-rbactest-domain-test-data.sql
|
||||
context: "!without-test-data"
|
||||
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/500-hs-office-schema.sql
|
||||
- include:
|
||||
@ -79,18 +92,21 @@ databaseChangeLog:
|
||||
file: db/changelog/5-hs-office/501-contact/5016-hs-office-contact-migration.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/501-contact/5018-hs-office-contact-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/502-person/5020-hs-office-person.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/502-person/5023-hs-office-person-rbac.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/502-person/5028-hs-office-person-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/503-relation/5030-hs-office-relation.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/503-relation/5038-hs-office-relation-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/504-partner/5040-hs-office-partner.sql
|
||||
- include:
|
||||
@ -101,18 +117,21 @@ databaseChangeLog:
|
||||
file: db/changelog/5-hs-office/504-partner/5046-hs-office-partner-migration.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/504-partner/5048-hs-office-partner-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/505-bankaccount/5050-hs-office-bankaccount.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/505-bankaccount/5053-hs-office-bankaccount-rbac.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/505-bankaccount/5058-hs-office-bankaccount-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/506-debitor/5060-hs-office-debitor.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/506-debitor/5063-hs-office-debitor-rbac.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/506-debitor/5068-hs-office-debitor-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/507-sepamandate/5070-hs-office-sepamandate.sql
|
||||
- include:
|
||||
@ -121,12 +140,14 @@ databaseChangeLog:
|
||||
file: db/changelog/5-hs-office/507-sepamandate/5076-hs-office-sepamandate-migration.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/507-sepamandate/5078-hs-office-sepamandate-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/510-membership/5100-hs-office-membership.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/510-membership/5108-hs-office-membership-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/511-coopshares/5110-hs-office-coopshares.sql
|
||||
- include:
|
||||
@ -135,6 +156,7 @@ databaseChangeLog:
|
||||
file: db/changelog/5-hs-office/511-coopshares/5116-hs-office-coopshares-migration.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/511-coopshares/5118-hs-office-coopshares-test-data.sql
|
||||
context: "!without-test-data"
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/512-coopassets/5120-hs-office-coopassets.sql
|
||||
- include:
|
||||
@ -143,37 +165,58 @@ databaseChangeLog:
|
||||
file: db/changelog/5-hs-office/512-coopassets/5126-hs-office-coopassets-migration.sql
|
||||
- include:
|
||||
file: db/changelog/5-hs-office/512-coopassets/5128-hs-office-coopassets-test-data.sql
|
||||
context: "!without-test-data"
|
||||
|
||||
- include:
|
||||
file: db/changelog/6-hs-booking/600-hs-booking-schema.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/6-hs-booking/610-booking-debitor/6100-hs-booking-debitor.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/6-hs-booking/620-booking-project/6200-hs-booking-project.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/6-hs-booking/620-booking-project/6203-hs-booking-project-rbac.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/6-hs-booking/620-booking-project/6208-hs-booking-project-test-data.sql
|
||||
context: "!only-office and !without-test-data"
|
||||
- include:
|
||||
file: db/changelog/6-hs-booking/630-booking-item/6300-hs-booking-item.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/6-hs-booking/630-booking-item/6303-hs-booking-item-rbac.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/6-hs-booking/630-booking-item/6308-hs-booking-item-test-data.sql
|
||||
context: "!only-office and !without-test-data"
|
||||
|
||||
- include:
|
||||
file: db/changelog/7-hs-hosting/700-hs-hosting-schema.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/7-hs-hosting/701-hosting-asset/7010-hs-hosting-asset.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/7-hs-hosting/701-hosting-asset/7016-hs-hosting-asset-migration.sql
|
||||
context: "!only-office"
|
||||
- include:
|
||||
file: db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql
|
||||
context: "!only-office and !without-test-data"
|
||||
|
||||
- include:
|
||||
file: db/changelog/9-hs-global/9000-statistics.sql
|
||||
context: "!only-office"
|
||||
|
||||
- include:
|
||||
file: db/changelog/9-hs-global/9100-hs-integration-schema.sql
|
||||
- include:
|
||||
file: db/changelog/9-hs-global/9110-integration-kimai.sql
|
||||
- include:
|
||||
file: db/changelog/9-hs-global/9120-integration-znuny.sql
|
||||
file: db/changelog/9-hs-global/9120-integration-znuny.sql
|
||||
- include:
|
||||
file: db/changelog/9-hs-global/9130-integration-mlmmj.sql
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user