Merge remote-tracking branch 'origin/bugfix/hosting-assets-import-into-external-db' into feature/add-scenario-test-for-deceased-partner-with-community-of-heirs
This commit is contained in:
commit
a8964017ca
55
.aliases
55
.aliases
@ -1,4 +1,4 @@
|
|||||||
# For using the alias gw-importOfficeData or gw-importHostingAssets,
|
# For using the alias gw-importHostingAssets,
|
||||||
# copy the file .tc-environment to .environment (ignored by git)
|
# copy the file .tc-environment to .environment (ignored by git)
|
||||||
# and amend them according to your external DB.
|
# and amend them according to your external DB.
|
||||||
|
|
||||||
@ -71,7 +71,6 @@ function importLegacyData() {
|
|||||||
./gradlew $target --rerun
|
./gradlew $target --rerun
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
alias gw-importOfficeData='importLegacyData importOfficeData'
|
|
||||||
alias gw-importHostingAssets='importLegacyData importHostingAssets'
|
alias gw-importHostingAssets='importLegacyData importHostingAssets'
|
||||||
|
|
||||||
alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
|
alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
|
||||||
@ -90,11 +89,54 @@ 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 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-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources'
|
||||||
alias gw-test='. .aliases; ./gradlew test'
|
alias gw-check='. .aliases; . .tc-environment; gw test check -x pitest'
|
||||||
alias gw-check='. .aliases; gw test check -x pitest'
|
|
||||||
|
# HOWTO: run all 'normal' tests (by default without scenario+import-tests): `gw-test`
|
||||||
|
# You can also mention specific targets: `gw-test importHostingAssets`, in that case only these tests are executed.
|
||||||
|
# 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 -f /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 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
|
# etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries
|
||||||
alias gw-importOfficeData-in-docker-compose='
|
alias gw-importHostingAssets-in-docker-compose='
|
||||||
docker-compose -f etc/docker-compose.yml down &&
|
docker-compose -f etc/docker-compose.yml down &&
|
||||||
docker-compose -f etc/docker-compose.yml up -d && sleep 10 &&
|
docker-compose -f etc/docker-compose.yml up -d && sleep 10 &&
|
||||||
time gw-importHostingAssets'
|
time gw-importHostingAssets'
|
||||||
@ -104,5 +146,6 @@ if [ ! -f .environment ]; then
|
|||||||
fi
|
fi
|
||||||
source .environment
|
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'
|
alias scenario-reports-open='open https://hsngdev.hs-example.de/scenarios/office'
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="postgres" />
|
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="postgres" />
|
||||||
<entry key="HSADMINNG_POSTGRES_JDBC_URL" value="jdbc:postgresql://localhost:5432/postgres" />
|
<entry key="HSADMINNG_POSTGRES_JDBC_URL" value="jdbc:postgresql://localhost:5432/postgres" />
|
||||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
||||||
|
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="executionName" />
|
<option name="executionName" />
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
<ExternalSystemSettings>
|
<ExternalSystemSettings>
|
||||||
<option name="env">
|
<option name="env">
|
||||||
<map>
|
<map>
|
||||||
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
|
|
||||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
||||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
||||||
|
<entry key="HSADMINNG_SUPERUSER" value="import-superuser@hostsharing.net" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="executionName" />
|
<option name="executionName" />
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
|
|
||||||
<ExternalSystemSettings>
|
|
||||||
<option name="env">
|
|
||||||
<map>
|
|
||||||
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
|
|
||||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
|
||||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="executionName" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="externalSystemIdString" value="GRADLE" />
|
|
||||||
<option name="scriptParameters" value="" />
|
|
||||||
<option name="taskDescriptions">
|
|
||||||
<list />
|
|
||||||
</option>
|
|
||||||
<option name="taskNames">
|
|
||||||
<list>
|
|
||||||
<option value=":importOfficeData" />
|
|
||||||
<option value="--tests" />
|
|
||||||
<option value=""net.hostsharing.hsadminng.hs.migration.ImportOfficeData"" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
<option name="vmOptions" />
|
|
||||||
</ExternalSystemSettings>
|
|
||||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
|
||||||
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
|
||||||
<extension name="coverage" sample_coverage="false" />
|
|
||||||
</EXTENSION>
|
|
||||||
<DebugAllEnabled>false</DebugAllEnabled>
|
|
||||||
<RunAsTest>true</RunAsTest>
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
|
|
||||||
<ExternalSystemSettings>
|
|
||||||
<option name="env">
|
|
||||||
<map>
|
|
||||||
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
|
|
||||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
|
||||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="executionName" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="externalSystemIdString" value="GRADLE" />
|
|
||||||
<option name="scriptParameters" value="" />
|
|
||||||
<option name="taskDescriptions">
|
|
||||||
<list />
|
|
||||||
</option>
|
|
||||||
<option name="taskNames">
|
|
||||||
<list>
|
|
||||||
<option value=":importOfficeData" />
|
|
||||||
<option value="--tests" />
|
|
||||||
<option value=""net.hostsharing.hsadminng.hs.office.migration.ImportOfficeData"" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
<option name="vmOptions" />
|
|
||||||
</ExternalSystemSettings>
|
|
||||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
|
||||||
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
|
||||||
<extension name="coverage" sample_coverage="false" />
|
|
||||||
</EXTENSION>
|
|
||||||
<DebugAllEnabled>false</DebugAllEnabled>
|
|
||||||
<RunAsTest>true</RunAsTest>
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
|
|
||||||
<ExternalSystemSettings>
|
|
||||||
<option name="env">
|
|
||||||
<map>
|
|
||||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
|
||||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="executionName" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="externalSystemIdString" value="GRADLE" />
|
|
||||||
<option name="scriptParameters" value="" />
|
|
||||||
<option name="taskDescriptions">
|
|
||||||
<list />
|
|
||||||
</option>
|
|
||||||
<option name="taskNames">
|
|
||||||
<list>
|
|
||||||
<option value=":importOfficeData" />
|
|
||||||
<option value="--tests" />
|
|
||||||
<option value=""net.hostsharing.hsadminng.hs.migration.ImportOfficeData"" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
<option name="vmOptions" />
|
|
||||||
</ExternalSystemSettings>
|
|
||||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
|
||||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
|
||||||
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
|
||||||
<extension name="coverage" sample_coverage="false" />
|
|
||||||
</EXTENSION>
|
|
||||||
<DebugAllEnabled>false</DebugAllEnabled>
|
|
||||||
<RunAsTest>true</RunAsTest>
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
@ -1,8 +1,7 @@
|
|||||||
unset HSADMINNG_POSTGRES_JDBC_URL # dynamically set, different for normal tests and imports
|
source .unset-environment
|
||||||
export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin
|
|
||||||
export HSADMINNG_POSTGRES_ADMIN_PASSWORD=
|
|
||||||
export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted
|
export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted
|
||||||
export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net
|
export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin
|
||||||
export HSADMINNG_MIGRATION_DATA_PATH=migration
|
export HSADMINNG_SUPERUSER=import-superuser@hostsharing.net
|
||||||
export LIQUIBASE_CONTEXT=
|
|
||||||
export LANG=en_US.UTF-8
|
export LANG=en_US.UTF-8
|
||||||
|
@ -4,5 +4,5 @@ unset HSADMINNG_POSTGRES_ADMIN_PASSWORD
|
|||||||
unset HSADMINNG_POSTGRES_RESTRICTED_USERNAME
|
unset HSADMINNG_POSTGRES_RESTRICTED_USERNAME
|
||||||
unset HSADMINNG_SUPERUSER
|
unset HSADMINNG_SUPERUSER
|
||||||
unset HSADMINNG_MIGRATION_DATA_PATH
|
unset HSADMINNG_MIGRATION_DATA_PATH
|
||||||
unset LIQUIBASE_CONTEXT
|
unset HSADMINNG_OFFICE_DATA_SQL_FILE
|
||||||
|
|
||||||
|
25
Jenkinsfile
vendored
25
Jenkinsfile
vendored
@ -35,19 +35,34 @@ pipeline {
|
|||||||
|
|
||||||
stage ('Tests') {
|
stage ('Tests') {
|
||||||
parallel {
|
parallel {
|
||||||
stage('Unit-/Integration/Acceptance-Tests') {
|
stage('Unit-Tests') {
|
||||||
steps {
|
steps {
|
||||||
sh './gradlew check --no-daemon -x pitest -x dependencyCheckAnalyze -x importOfficeData -x importHostingAssets'
|
sh './gradlew unitTest --no-daemon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Import-Tests') {
|
stage('General-Tests') {
|
||||||
steps {
|
steps {
|
||||||
sh './gradlew importOfficeData importHostingAssets --no-daemon'
|
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('Test-Imports') {
|
||||||
|
steps {
|
||||||
|
sh './gradlew importHostingAssets --no-daemon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage ('Scenario-Tests') {
|
stage ('Scenario-Tests') {
|
||||||
steps {
|
steps {
|
||||||
sh './gradlew scenarioTests --no-daemon'
|
sh './gradlew scenarioTest --no-daemon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
148
README.md
148
README.md
@ -27,7 +27,13 @@ For architecture consider the files in the `doc` and `adr` folder.
|
|||||||
- [OWASP Security Vulnerability Check](#owasp-security-vulnerability-check)
|
- [OWASP Security Vulnerability Check](#owasp-security-vulnerability-check)
|
||||||
- [Dependency-License-Compatibility](#dependency-license-compatibility)
|
- [Dependency-License-Compatibility](#dependency-license-compatibility)
|
||||||
- [Dependency Version Upgrade](#dependency-version-upgrade)
|
- [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 ...](#how-to-...)
|
||||||
|
- [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 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?)
|
- [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)
|
- [Install and Run Podman](#install-and-run-podman)
|
||||||
@ -40,6 +46,7 @@ For architecture consider the files in the `doc` and `adr` folder.
|
|||||||
- [How to Amend Liquibase SQL Changesets?](#how-to-amend-liquibase-sql-changesets?)
|
- [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 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 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)
|
- [Further Documentation](#further-documentation)
|
||||||
<!-- generated TOC end. -->
|
<!-- 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:
|
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
|
- 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)
|
(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/`.
|
- 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*.
|
- 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:
|
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,32 +72,55 @@ 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 # 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 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:
|
# if the container has not been built yet, run this:
|
||||||
pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432
|
pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432
|
||||||
|
|
||||||
# if the container has been built already and you want to keep the data, run this:
|
# if the container has been built already and you want to keep the data, run this:
|
||||||
pg-sql-start
|
pg-sql-start
|
||||||
|
|
||||||
gw bootRun # compiles and runs the application 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=
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
|
||||||
# the following command should reply with "pong":
|
# the following command should reply with "pong":
|
||||||
curl -f http://localhost:8080/api/ping
|
curl -f -s http://localhost:8080/api/ping
|
||||||
|
|
||||||
# the following command should return a JSON array with just all customers:
|
# the following command should return a JSON array with just all customers:
|
||||||
curl -f\
|
curl -f -s\
|
||||||
-H 'current-subject: superuser-alex@hostsharing.net' \
|
-H 'current-subject: superuser-alex@hostsharing.net' \
|
||||||
http://localhost:8080/api/test/customers \
|
http://localhost:8080/api/test/customers \
|
||||||
| jq # just if `jq` is installed, to prettyprint the output
|
| jq # just if `jq` is installed, to prettyprint the output
|
||||||
|
|
||||||
# the following command should return a JSON array with just all packages visible for the admin of the customer yyy:
|
# the following command should return a JSON array with just all packages visible for the admin of the customer yyy:
|
||||||
curl -f\
|
curl -f -s\
|
||||||
-H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: rbactest.customer#yyy:ADMIN' \
|
-H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: rbactest.customer#yyy:ADMIN' \
|
||||||
http://localhost:8080/api/test/packages \
|
http://localhost:8080/api/test/packages \
|
||||||
| jq
|
| jq
|
||||||
|
|
||||||
# add a new customer
|
# add a new customer
|
||||||
curl -f\
|
curl -f -s\
|
||||||
-H 'current-subject: superuser-alex@hostsharing.net' -H "Content-Type: application/json" \
|
-H 'current-subject: superuser-alex@hostsharing.net' -H "Content-Type: application/json" \
|
||||||
-d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \
|
-d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \
|
||||||
-X POST http://localhost:8080/api/test/customers \
|
-X POST http://localhost:8080/api/test/customers \
|
||||||
@ -101,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.
|
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.
|
If you still need to install some of these tools, find some hints in the next chapters.
|
||||||
|
|
||||||
@ -181,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:
|
Given this is in PATH as `md-toc`, use:
|
||||||
|
|
||||||
```shell
|
```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:
|
To render the Markdown files, especially to watch embedded PlantUML diagrams, you can use one of the following methods:
|
||||||
@ -419,36 +450,42 @@ Some of these rules are checked with *ArchUnit* unit tests.
|
|||||||
|
|
||||||
### Run Tests from Command Line
|
### 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
|
```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:
|
Force running all tests:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
gw cleanTest test
|
gw-test --rerun
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To find more options about running tests, try `howto test`.
|
||||||
|
|
||||||
|
|
||||||
### Spotless Code Formatting
|
### Spotless Code Formatting
|
||||||
|
|
||||||
Code formatting for Java is checked via *spotless*.
|
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:
|
To apply formatting rules, use:
|
||||||
|
|
||||||
```shell
|
```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
|
### JaCoCo Test Code Coverage Check
|
||||||
|
|
||||||
This project uses the JaCoCo test code coverage report with limit checks.
|
This project uses the JaCoCo test code coverage report with limit checks.
|
||||||
@ -486,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 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.
|
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
|
#### Remark
|
||||||
|
|
||||||
In this project, there is little business logic in *Java* code;
|
In this project, there is a large amount of code is in *plsql*, especially for RBAC.
|
||||||
most business code is in *plsql*
|
*Java* ist mostly used for mapping and validating REST calls to database queries.
|
||||||
and *Java* ist mostly used for mapping REST calls to database queries.
|
|
||||||
This mapping ist mostly done through *Spring* annotations and other implicit code.
|
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.
|
Therefore, there are only few unit tests and thus mutation testing has limited value.
|
||||||
@ -526,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.
|
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.
|
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.
|
If any dependency violates the configured [list of allowed licenses](etc/allowed-licenses.json), the build will fail.
|
||||||
@ -556,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).
|
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:
|
Dependency versions can be automatically upgraded to the latest available version:
|
||||||
|
|
||||||
@ -584,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.
|
and would not need the `rbac.role` table anymore.
|
||||||
We would also reduce the depth of the expensive recursive CTE-query.
|
We would also reduce the depth of the expensive recursive CTE-query.
|
||||||
|
|
||||||
This has to be explored further.
|
This has to be explored further. For now, we just keep it in mind and avoid roles+grants
|
||||||
For now, we just keep it in mind and
|
which would not fit into a simplified system with a fixed role-type-system.
|
||||||
|
|
||||||
|
|
||||||
### The Mapper is Error-Prone
|
### The Mapper is Error-Prone
|
||||||
|
|
||||||
@ -609,6 +646,36 @@ Besides the following *How Tos* you can also find several *How Tos* in the sourc
|
|||||||
grep -r HOWTO src
|
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?
|
### 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:
|
To access the default database schema as used during development, add this line to your `.pgpass` file in your users home directory:
|
||||||
@ -807,6 +874,29 @@ postgres-autodoc
|
|||||||
The output will list the generated files.
|
The output will list the generated files.
|
||||||
|
|
||||||
|
|
||||||
|
### How to Add (Real) Admin Users
|
||||||
|
|
||||||
|
```sql
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
-- replace with your admin account names
|
||||||
|
admin_users TEXT[] := ARRAY['admin-1', 'admin-2', 'admin-3'];
|
||||||
|
admin TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- run as superuser
|
||||||
|
call base.defineContext('adding real admin users', null, null, null);
|
||||||
|
|
||||||
|
-- for all new admin accounts
|
||||||
|
FOREACH admin IN ARRAY admin_users LOOP
|
||||||
|
call rbac.grantRoleToSubjectUnchecked(
|
||||||
|
rbac.findRoleId(rbac.global_ADMIN()), -- granted by role
|
||||||
|
rbac.findRoleId(rbac.global_ADMIN()), -- role to grant
|
||||||
|
rbac.create_subject(admin)); -- creates the new admin account
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Further Documentation
|
## Further Documentation
|
||||||
|
|
||||||
- the `doc` directory contains architecture concepts and a glossary
|
- the `doc` directory contains architecture concepts and a glossary
|
||||||
|
249
bin/cas-curl
Executable file
249
bin/cas-curl
Executable file
@ -0,0 +1,249 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "$2" == "--show-password" ]; then
|
||||||
|
HSADMINNG_CAS_SHOW_PASSWORD=yes
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
HSADMINNG_CAS_SHOW_PASSWORD=
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" == "--trace" ]; then
|
||||||
|
function trace() {
|
||||||
|
echo "$*" >&2
|
||||||
|
}
|
||||||
|
function doCurl() {
|
||||||
|
set -x
|
||||||
|
if [ -z "$HSADMINNG_CAS_ASSUME" ]; then
|
||||||
|
curl --fail-with-body \
|
||||||
|
--header "Authorization: $HSADMINNG_CAS_TICKET" \
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
curl --fail-with-body \
|
||||||
|
--header "Authorization: $HSADMINNG_CAS_TICKET" \
|
||||||
|
--header "assumed-roles: $HSADMINNG_CAS_ASSUME" \
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
set +x
|
||||||
|
}
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
function trace() {
|
||||||
|
: # noop
|
||||||
|
}
|
||||||
|
function doCurl() {
|
||||||
|
curl --fail-with-body --header "Authorization: $HSADMINNG_CAS_TICKET" "$@"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
export HSADMINNG_CAS_ASSUME_HEADER
|
||||||
|
if [ -f ~/.cas-curl-assume ]; then
|
||||||
|
HSADMINNG_CAS_ASSUME="$(cat ~/.cas-curl-assume)"
|
||||||
|
else
|
||||||
|
HSADMINNG_CAS_ASSUME=
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$HSADMINNG_CAS_LOGIN" ] || [ -z "$HSADMINNG_CAS_VALIDATE" ] || \
|
||||||
|
[ -z "$HSADMINNG_CAS_SERVICE_ID" ]; then
|
||||||
|
cat >&2 <<EOF
|
||||||
|
ERROR: environment incomplete
|
||||||
|
|
||||||
|
please set the following environment variables:
|
||||||
|
export HSADMINNG_CAS_LOGIN=https://login.hostsharing.net/cas/v1/tickets
|
||||||
|
export HSADMINNG_CAS_VALIDATE=https://login.hostsharing.net/cas/proxyValidate
|
||||||
|
export HSADMINNG_CAS_USERNAME=<<optionally, your username, or leave empty after '='>>
|
||||||
|
export HSADMINNG_CAS_PASSWORD=<<optionally, your password, or leave empty after '='>>
|
||||||
|
export HSADMINNG_CAS_SERVICE_ID=https://hsadminng.hostsharing.net:443/
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function casCurlDocumentation() {
|
||||||
|
cat <<EOF
|
||||||
|
curl-wrapper utilizing CAS-authentication for hsadmin-ng
|
||||||
|
usage: $0 [--trace] [--show-password] <<command>> [parameters]
|
||||||
|
|
||||||
|
commands:
|
||||||
|
EOF
|
||||||
|
# filters out help texts (containing double-# and following lines with leading single-#) from the commands itself
|
||||||
|
# (the '' makes sure that this line is not found, just the lines with actual help texts)
|
||||||
|
sed -n '/#''#/ {x; p; x; s/#''#//; p; :a; n; /^[[:space:]]*#/!b; s/^[[:space:]]*#//; p; ba}' <$0
|
||||||
|
}
|
||||||
|
|
||||||
|
function casLogin() {
|
||||||
|
# ticket granting ticket exists and not expired?
|
||||||
|
if find ~/.cas-login-tgt -type f -size +0c -mmin -60 2>/dev/null | grep -q .; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$HSADMINNG_CAS_USERNAME" ]; then
|
||||||
|
read -e -p "Username: " HSADMINNG_CAS_USERNAME
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$HSADMINNG_CAS_PASSWORD" ]; then
|
||||||
|
read -s -e -p "Password: " HSADMINNG_CAS_PASSWORD
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$HSADMINNG_CAS_SHOW_PASSWORD" == "--show-password" ]; then
|
||||||
|
HSADMINNG_CAS_PASSWORD_DISPLAY=$HSADMINNG_CAS_PASSWORD
|
||||||
|
else
|
||||||
|
HSADMINNG_CAS_PASSWORD_DISPLAY="<<password hidden - use --show-password to show>>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Do NOT use doCurl here! We do neither want to print the password nor pass a CAS service ticket.
|
||||||
|
trace "+ curl --fail-with-body -s -i -X POST \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
-d \"username=$HSADMINNG_CAS_USERNAME&password=$HSADMINNG_CAS_PASSWORD_DISPLAY\" \
|
||||||
|
$HSADMINNG_CAS_LOGIN -o ~/.cas-login-tgt.response -D -"
|
||||||
|
HSADMINNG_CAS_TGT=`curl --fail-with-body -s -i -X POST \
|
||||||
|
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
-d "username=$HSADMINNG_CAS_USERNAME&password=$HSADMINNG_CAS_PASSWORD" \
|
||||||
|
$HSADMINNG_CAS_LOGIN -o ~/.cas-login-tgt.response -D - \
|
||||||
|
| grep -i "^Location: " | sed -e 's/^Location: //' -e 's/\\r//'`
|
||||||
|
if [ -z "$HSADMINNG_CAS_TGT" ]; then
|
||||||
|
echo "ERROR: could not get ticket granting ticket" >&2
|
||||||
|
cat ~/.cas-login-tgt.response >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "$HSADMINNG_CAS_TGT" >~/.cas-login-tgt
|
||||||
|
trace "$HSADMINNG_CAS_TGT"
|
||||||
|
}
|
||||||
|
|
||||||
|
function casLogout() {
|
||||||
|
rm -f ~/.cas-login-tgt
|
||||||
|
}
|
||||||
|
|
||||||
|
function casTicket() {
|
||||||
|
HSADMINNG_CAS_TGT=$(<~/.cas-login-tgt)
|
||||||
|
if [[ -z "$HSADMINNG_CAS_TGT" ]]; then
|
||||||
|
echo "ERROR: cannot get CAS ticket granting ticket for $HSADMINNG_CAS_USERNAME" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
trace "CAS-TGT: $HSADMINNG_CAS_TGT"
|
||||||
|
|
||||||
|
trace "fetching CAS service ticket"
|
||||||
|
trace "curl -s -d \"service=$HSADMINNG_CAS_SERVICE_ID\" $HSADMINNG_CAS_TGT"
|
||||||
|
HSADMINNG_CAS_TICKET=$(curl -s -d "service=$HSADMINNG_CAS_SERVICE_ID" $HSADMINNG_CAS_TGT)
|
||||||
|
if [[ -z "$HSADMINNG_CAS_TICKET" ]]; then
|
||||||
|
echo "ERROR: cannot get CAS service ticket" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo $HSADMINNG_CAS_TICKET
|
||||||
|
}
|
||||||
|
|
||||||
|
function casValidate() {
|
||||||
|
HSADMINNG_CAS_TICKET=`casTicket`
|
||||||
|
|
||||||
|
trace "validating CAS-TICKET: $HSADMINNG_CAS_TICKET"
|
||||||
|
# Do NOT use doCurl here! We do not pass a CAS service ticket.
|
||||||
|
trace curl -i -s $HSADMINNG_CAS_VALIDATE?ticket=${HSADMINNG_CAS_TICKET}\&service=${HSADMINNG_CAS_SERVICE_ID}
|
||||||
|
HSADMINNG_CAS_USER=`curl -i -s $HSADMINNG_CAS_VALIDATE?ticket=${HSADMINNG_CAS_TICKET}\&service=${HSADMINNG_CAS_SERVICE_ID} | grep -oPm1 "(?<=<cas:user>)[^<]+"`
|
||||||
|
if [ -z "$HSADMINNG_CAS_USER" ]; then
|
||||||
|
echo "validation failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "CAS-User: $HSADMINNG_CAS_USER"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1,,}" in
|
||||||
|
|
||||||
|
# -- generic commands --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
""|"-h"|"--help"|"help") ## prints documentation about commands and options
|
||||||
|
casCurlDocumentation
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
|
||||||
|
"env") ## prints all related HSADMINNG_CAS_... environment variables; use '--show-password' to show the password as well
|
||||||
|
# example: cas-curl env --show-password
|
||||||
|
echo "HSADMINNG_CAS_LOGIN: $HSADMINNG_CAS_LOGIN"
|
||||||
|
echo "HSADMINNG_CAS_VALIDATE: $HSADMINNG_CAS_VALIDATE"
|
||||||
|
echo "HSADMINNG_CAS_USERNAME: $HSADMINNG_CAS_USERNAME"
|
||||||
|
if [ "$2" == "--show-password" ]; then
|
||||||
|
echo "HSADMINNG_CAS_PASSWORD: $HSADMINNG_CAS_PASSWORD"
|
||||||
|
elif [ -z "$HSADMINNG_CAS_PASSWORD" ]; then
|
||||||
|
echo "HSADMINNG_CAS_PASSWORD: <<not given>>"
|
||||||
|
else
|
||||||
|
echo "HSADMINNG_CAS_PASSWORD: <<given, but hidden - add --show-password to show>>"
|
||||||
|
fi
|
||||||
|
echo "HSADMINNG_CAS_SERVICE_ID: $HSADMINNG_CAS_SERVICE_ID"
|
||||||
|
;;
|
||||||
|
|
||||||
|
# --- authentication-related commands ------------------------------------------------------------
|
||||||
|
|
||||||
|
"login") ## reads username+password and fetches ticket granting ticket (bypasses HSADMINNG_CAS_USERNAME+HSADMINNG_CAS_PASSWORD)
|
||||||
|
# example: cas-curl login
|
||||||
|
casLogout
|
||||||
|
export HSADMINNG_CAS_USERNAME=
|
||||||
|
export HSADMINNG_CAS_PASSWORD=
|
||||||
|
casLogin
|
||||||
|
;;
|
||||||
|
"assume") ## assumes the given comma-separated roles
|
||||||
|
# example using object-id-name: cas-curl assume 'hs_office.relation#ExampleMandant-with-PARTNER-ExamplePartner:AGENT'
|
||||||
|
# example using object-uuid: cas-curl assume 'hs_office.relation#1d3bc468-c5c8-11ef-9d0d-4751ecfda2b7:AGENT'
|
||||||
|
shift
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "ERROR: requires comma-separated list of roles to assume" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "$1" >~/.cas-curl-assume
|
||||||
|
;;
|
||||||
|
"unassume") ## do not assume any particular role anymore, use the plain user as RBAC subject
|
||||||
|
rm ~/.cas-curl-assume
|
||||||
|
;;
|
||||||
|
"validate") ## validates current ticket granting ticket and prints currently logged in user
|
||||||
|
casValidate
|
||||||
|
;;
|
||||||
|
"logout") ## logout, deletes ticket granting ticket
|
||||||
|
casLogout
|
||||||
|
;;
|
||||||
|
|
||||||
|
# --- HTTP-commands ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
"get") ## HTTP GET, add URL as parameter
|
||||||
|
# example: cas-curl GET http://localhost:8080/api/hs/office/partners/P-10003 | jq
|
||||||
|
# hint: '| jq' is just for human-readable formatted JSON output
|
||||||
|
shift
|
||||||
|
casLogin
|
||||||
|
HSADMINNG_CAS_TICKET=`casTicket`
|
||||||
|
doCurl "$*"
|
||||||
|
;;
|
||||||
|
"post") ## HTTP POST, add curl options to specify the request body and the URL as last parameter
|
||||||
|
# example: cas-curl POST \
|
||||||
|
# -d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \
|
||||||
|
# http://localhost:8080/api/test/customers | jq
|
||||||
|
# hint: '| jq' is just for human-readable formatted JSON output
|
||||||
|
shift
|
||||||
|
casLogin
|
||||||
|
HSADMINNG_CAS_TICKET=`casTicket`
|
||||||
|
doCurl --header "Content-Type: application/json" -X POST "$@"
|
||||||
|
;;
|
||||||
|
"patch") ## HTTP PATCH, add curl options to specify the request body and the URL as last parameterparameter
|
||||||
|
# example: cas-curl PATCH \
|
||||||
|
# -d '{ "reference":80002 }' \
|
||||||
|
# http://localhost:8080/api/test/customers/ae90ac2a-4728-4ca9-802e-a0d0108b2324 | jq
|
||||||
|
# hint: '| jq' is just for human-readable formatted JSON output
|
||||||
|
shift
|
||||||
|
casLogin
|
||||||
|
HSADMINNG_CAS_TICKET=`casTicket`
|
||||||
|
doCurl --header "Content-Type: application/json" -X POST "$*"
|
||||||
|
;;
|
||||||
|
"delete") ## HTTP DELETE, add curl options to specify the request body and the URL as last parameter
|
||||||
|
# example: cas-curl DELETE http://localhost:8080/api/hs/office/persons/ae90ac2a-4728-4ca9-802e-a0d0108b2324
|
||||||
|
shift
|
||||||
|
casLogin
|
||||||
|
HSADMINNG_CAS_TICKET=`casTicket`
|
||||||
|
curl -X POST "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cat >&2 <<EOF
|
||||||
|
unknown command: '$1'
|
||||||
|
valid commands: help, login, logout, validate, get, post, patch, delete
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
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:]])
|
135
build.gradle
135
build.gradle
@ -1,17 +1,20 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '3.3.4'
|
id 'org.springframework.boot' version '3.4.1'
|
||||||
id 'io.spring.dependency-management' version '1.1.6'
|
id 'io.spring.dependency-management' version '1.1.7' // manages implicit dependencies
|
||||||
id 'io.openapiprocessor.openapi-processor' version '2023.2'
|
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'
|
id 'com.github.jk1.dependency-license-report' version '2.9' // checks dependency-license compatibility
|
||||||
id "org.owasp.dependencycheck" version "10.0.4"
|
id "org.owasp.dependencycheck" version "12.0.1" // checks dependencies for known vulnerabilities
|
||||||
id "com.diffplug.spotless" version "6.25.0"
|
id "com.diffplug.spotless" version "7.0.2" // formats + checks formatting for source-code
|
||||||
id 'jacoco'
|
id 'jacoco' // determines code-coverage of tests
|
||||||
id 'info.solidsoft.pitest' version '1.15.0'
|
id 'info.solidsoft.pitest' version '1.15.0' // performs mutation testing
|
||||||
id 'se.patrikerdes.use-latest-versions' version '0.2.18'
|
id 'se.patrikerdes.use-latest-versions' version '0.2.18' // updates module and plugin versions
|
||||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
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'
|
group = 'net.hostsharing'
|
||||||
version = '0.0.1-SNAPSHOT'
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
|
||||||
@ -20,6 +23,9 @@ wrapper {
|
|||||||
gradleVersion = '8.5'
|
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 {
|
configurations {
|
||||||
compileOnly {
|
compileOnly {
|
||||||
extendsFrom annotationProcessor
|
extendsFrom annotationProcessor
|
||||||
@ -60,25 +66,25 @@ dependencies {
|
|||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.2'
|
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.10.0'
|
||||||
implementation 'org.springdoc:springdoc-openapi:2.6.0'
|
implementation 'org.springdoc:springdoc-openapi:2.8.3'
|
||||||
implementation 'org.postgresql:postgresql:42.7.4'
|
implementation 'org.postgresql:postgresql'
|
||||||
implementation 'org.liquibase:liquibase-core:4.29.2'
|
implementation 'org.liquibase:liquibase-core'
|
||||||
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.8.3'
|
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.9.0'
|
||||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.0'
|
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||||
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
||||||
implementation 'org.apache.commons:commons-text:1.12.0'
|
implementation 'org.apache.commons:commons-text:1.13.0'
|
||||||
implementation 'net.java.dev.jna:jna:5.15.0'
|
implementation 'net.java.dev.jna:jna:5.16.0'
|
||||||
implementation 'org.modelmapper:modelmapper:3.2.1'
|
implementation 'org.modelmapper:modelmapper:3.2.2'
|
||||||
implementation 'org.iban4j:iban4j:3.2.10-RELEASE'
|
implementation 'org.iban4j:iban4j:3.2.10-RELEASE'
|
||||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3'
|
||||||
implementation 'org.webjars:swagger-ui:5.17.14'
|
|
||||||
implementation 'org.reflections:reflections:0.10.2'
|
implementation 'org.reflections:reflections:0.10.2'
|
||||||
|
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
testCompileOnly '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'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
testAnnotationProcessor 'org.projectlombok:lombok'
|
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||||
@ -90,9 +96,10 @@ dependencies {
|
|||||||
testImplementation 'org.testcontainers:postgresql'
|
testImplementation 'org.testcontainers:postgresql'
|
||||||
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
||||||
testImplementation 'io.rest-assured:spring-mock-mvc'
|
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.pitest:pitest-junit5-plugin:1.2.1'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||||
|
testImplementation 'org.wiremock:wiremock-standalone:3.10.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
@ -173,7 +180,9 @@ openapiProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceSets.main.java.srcDir 'build/generated/sources/openapi'
|
sourceSets.main.java.srcDir 'build/generated/sources/openapi'
|
||||||
|
|
||||||
abstract class ProcessSpring extends DefaultTask {}
|
abstract class ProcessSpring extends DefaultTask {}
|
||||||
|
|
||||||
tasks.register('processSpring', ProcessSpring)
|
tasks.register('processSpring', ProcessSpring)
|
||||||
['processSpringRoot',
|
['processSpringRoot',
|
||||||
'processSpringRbac',
|
'processSpringRbac',
|
||||||
@ -204,7 +213,7 @@ openApiGenerate.dependsOn processSpring
|
|||||||
spotless {
|
spotless {
|
||||||
java {
|
java {
|
||||||
removeUnusedImports()
|
removeUnusedImports()
|
||||||
indentWithSpaces(4)
|
leadingTabsToSpaces(4)
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
|
|
||||||
@ -218,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
|
// HACK: no idea why spotless uses the output of these tasks, but we get warnings without those
|
||||||
project.tasks.spotlessJava.dependsOn(
|
project.tasks.spotlessJava.dependsOn(
|
||||||
tasks.generateLicenseReport,
|
tasks.generateLicenseReport,
|
||||||
tasks.pitest,
|
// tasks.pitest, TODO.test: PiTest currently does not work, needs to be fixed
|
||||||
tasks.jacocoTestReport,
|
tasks.jacocoTestReport,
|
||||||
tasks.processResources,
|
tasks.processResources,
|
||||||
tasks.processTestResources)
|
tasks.processTestResources)
|
||||||
@ -247,19 +256,21 @@ licenseReport {
|
|||||||
}
|
}
|
||||||
project.tasks.check.dependsOn(checkLicense)
|
project.tasks.check.dependsOn(checkLicense)
|
||||||
|
|
||||||
// JaCoCo Test Code Coverage
|
// HOWTO: run all tests except import- and scenario-tests: gw test
|
||||||
jacoco {
|
|
||||||
toolVersion = "0.8.10"
|
|
||||||
}
|
|
||||||
test {
|
test {
|
||||||
finalizedBy jacocoTestReport // generate report after tests
|
finalizedBy jacocoTestReport // generate report after tests
|
||||||
excludes = [
|
excludes = [
|
||||||
'net.hostsharing.hsadminng.**.generated.**',
|
'net.hostsharing.hsadminng.**.generated.**',
|
||||||
]
|
]
|
||||||
useJUnitPlatform {
|
useJUnitPlatform {
|
||||||
excludeTags 'importOfficeData', 'importHostingData', 'scenarioTest'
|
excludeTags 'importHostingAssets', 'scenarioTest'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JaCoCo Test Code Coverage for unit-tests
|
||||||
|
jacoco {
|
||||||
|
toolVersion = "0.8.10"
|
||||||
|
}
|
||||||
jacocoTestReport {
|
jacocoTestReport {
|
||||||
dependsOn test
|
dependsOn test
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
@ -324,13 +335,63 @@ jacocoTestCoverageVerification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('importOfficeData', Test) {
|
// HOWTO: run all unit-tests which don't need a database: gw-test unitTest
|
||||||
|
tasks.register('unitTest', Test) {
|
||||||
useJUnitPlatform {
|
useJUnitPlatform {
|
||||||
includeTags 'importOfficeData'
|
excludeTags 'importHostingAssets', 'scenarioTest', 'generalIntegrationTest',
|
||||||
|
'officeIntegrationTest', 'bookingIntegrationTest', 'hostingIntegrationTest'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'verification'
|
group 'verification'
|
||||||
description 'run the import jobs as tests'
|
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
|
mustRunAfter spotlessJava
|
||||||
}
|
}
|
||||||
@ -346,7 +407,7 @@ tasks.register('importHostingAssets', Test) {
|
|||||||
mustRunAfter spotlessJava
|
mustRunAfter spotlessJava
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('scenarioTests', Test) {
|
tasks.register('scenarioTest', Test) {
|
||||||
useJUnitPlatform {
|
useJUnitPlatform {
|
||||||
includeTags 'scenarioTest'
|
includeTags 'scenarioTest'
|
||||||
}
|
}
|
||||||
@ -367,7 +428,7 @@ pitest {
|
|||||||
]
|
]
|
||||||
|
|
||||||
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
|
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
|
||||||
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*']
|
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*', '**ImportHostingAssets']
|
||||||
|
|
||||||
pitestVersion = '1.17.0'
|
pitestVersion = '1.17.0'
|
||||||
junit5PluginVersion = '1.1.0'
|
junit5PluginVersion = '1.1.0'
|
||||||
@ -382,7 +443,7 @@ pitest {
|
|||||||
outputFormats = ['XML', 'HTML']
|
outputFormats = ['XML', 'HTML']
|
||||||
timestampedReports = false
|
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!
|
project.tasks.pitest.doFirst { // Why not doLast? See README.md!
|
||||||
println "PiTest Mutation Report: file:///${project.rootDir}/build/reports/pitest/index.html"
|
println "PiTest Mutation Report: file:///${project.rootDir}/build/reports/pitest/index.html"
|
||||||
}
|
}
|
||||||
@ -446,7 +507,7 @@ tasks.register('convertMarkdownToHtml') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
convertMarkdownToHtml.dependsOn scenarioTests
|
convertMarkdownToHtml.dependsOn scenarioTest
|
||||||
|
|
||||||
// shortcut for compiling all files
|
// shortcut for compiling all files
|
||||||
tasks.register('compile') {
|
tasks.register('compile') {
|
||||||
|
@ -50,6 +50,7 @@ classDiagram
|
|||||||
UNKNOWN: nur für Import
|
UNKNOWN: nur für Import
|
||||||
NATURAL_PERSON: natürliche Person
|
NATURAL_PERSON: natürliche Person
|
||||||
LEGAL_PERSON: z.B. GmbH, e.K., eG, e.V.
|
LEGAL_PERSON: z.B. GmbH, e.K., eG, e.V.
|
||||||
|
ORGANIZATIONAL_UNIT: z.B. "Admin-Team", "Buchhaltung"
|
||||||
INCORORATED_FIRM: z.B. OHG, Partnerschaftsgesellschaft
|
INCORORATED_FIRM: z.B. OHG, Partnerschaftsgesellschaft
|
||||||
UNINCORPORATED_FIRM: z.B. GbR, ARGE, Erbengemeinschaft
|
UNINCORPORATED_FIRM: z.B. GbR, ARGE, Erbengemeinschaft
|
||||||
PUBLIC_INSTITUTION: KdöR, AöR [ohne Registergericht/Registernummer]
|
PUBLIC_INSTITUTION: KdöR, AöR [ohne Registergericht/Registernummer]
|
||||||
|
@ -5,9 +5,23 @@
|
|||||||
{ "moduleLicense": "Apache-2.0" },
|
{ "moduleLicense": "Apache-2.0" },
|
||||||
{ "moduleLicense": "Apache License 2.0" },
|
{ "moduleLicense": "Apache License 2.0" },
|
||||||
{ "moduleLicense": "Apache License v2.0" },
|
{ "moduleLicense": "Apache License v2.0" },
|
||||||
|
{ "moduleLicense": "Apache License Version 2.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": "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 License" },
|
||||||
{ "moduleLicense": "BSD-2-Clause" },
|
{ "moduleLicense": "BSD-2-Clause" },
|
||||||
{ "moduleLicense": "BSD-3-Clause" },
|
{ "moduleLicense": "BSD-3-Clause" },
|
||||||
@ -46,14 +60,8 @@
|
|||||||
{
|
{
|
||||||
"moduleLicense": "Public Domain, per Creative Commons CC0",
|
"moduleLicense": "Public Domain, per Creative Commons CC0",
|
||||||
"moduleVersion": "2.0.3"
|
"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>
|
||||||
<suppress>
|
<suppress>
|
||||||
<notes><![CDATA[
|
<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>
|
]]></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>
|
</suppress>
|
||||||
|
|
||||||
</suppressions>
|
</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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package net.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class AuthenticatedHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
|
||||||
|
private final Map<String, String> customHeaders = new HashMap<>();
|
||||||
|
|
||||||
|
public AuthenticatedHttpServletRequestWrapper(HttpServletRequest request) {
|
||||||
|
super(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHeader(final String name, final String value) {
|
||||||
|
customHeaders.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader(final String name) {
|
||||||
|
// Check custom headers first
|
||||||
|
final var customHeaderValue = customHeaders.get(name);
|
||||||
|
if (customHeaderValue != null) {
|
||||||
|
return customHeaderValue;
|
||||||
|
}
|
||||||
|
// Fall back to the original headers
|
||||||
|
return super.getHeader(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<String> getHeaderNames() {
|
||||||
|
// Combine original headers and custom headers
|
||||||
|
final var headerNames = new HashSet<>(customHeaders.keySet());
|
||||||
|
final var originalHeaderNames = super.getHeaderNames();
|
||||||
|
while (originalHeaderNames.hasMoreElements()) {
|
||||||
|
headerNames.add(originalHeaderNames.nextElement());
|
||||||
|
}
|
||||||
|
return Collections.enumeration(headerNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<String> getHeaders(final String name) {
|
||||||
|
// Combine original headers and custom header
|
||||||
|
final var values = new HashSet<String>();
|
||||||
|
if (customHeaders.containsKey(name)) {
|
||||||
|
values.add(customHeaders.get(name));
|
||||||
|
}
|
||||||
|
final var originalValues = super.getHeaders(name);
|
||||||
|
while (originalValues.hasMoreElements()) {
|
||||||
|
values.add(originalValues.nextElement());
|
||||||
|
}
|
||||||
|
return Collections.enumeration(values);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package net.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.Filter;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletRequest;
|
||||||
|
import jakarta.servlet.ServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class AuthenticationFilter implements Filter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Authenticator authenticator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) {
|
||||||
|
final var httpRequest = (HttpServletRequest) request;
|
||||||
|
final var httpResponse = (HttpServletResponse) response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final var currentSubject = authenticator.authenticate(httpRequest);
|
||||||
|
|
||||||
|
final var authenticatedRequest = new AuthenticatedHttpServletRequestWrapper(httpRequest);
|
||||||
|
authenticatedRequest.addHeader("current-subject", currentSubject);
|
||||||
|
|
||||||
|
chain.doFilter(authenticatedRequest, response);
|
||||||
|
} catch (final BadCredentialsException exc) {
|
||||||
|
// TODO.impl: should not be necessary if ResponseStatusException worked
|
||||||
|
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package net.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
public interface Authenticator {
|
||||||
|
|
||||||
|
String authenticate(final HttpServletRequest httpRequest);
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package net.hostsharing.hsadminng.config;
|
||||||
|
|
||||||
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class CasAuthenticator implements Authenticator {
|
||||||
|
|
||||||
|
@Value("${hsadminng.cas.server}")
|
||||||
|
private String casServerUrl;
|
||||||
|
|
||||||
|
@Value("${hsadminng.cas.service}")
|
||||||
|
private String serviceUrl;
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Timed("app.cas.authenticate")
|
||||||
|
public String authenticate(final HttpServletRequest httpRequest) {
|
||||||
|
final var userName = StringUtils.isBlank(casServerUrl)
|
||||||
|
? bypassCurrentSubject(httpRequest)
|
||||||
|
: casValidation(httpRequest);
|
||||||
|
final var authentication = new UsernamePasswordAuthenticationToken(userName, null, null);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
return authentication.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String bypassCurrentSubject(final HttpServletRequest httpRequest) {
|
||||||
|
final var userName = httpRequest.getHeader("current-subject");
|
||||||
|
System.err.println("CasAuthenticator.bypassCurrentSubject: " + userName);
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String casValidation(final HttpServletRequest httpRequest)
|
||||||
|
throws SAXException, IOException, ParserConfigurationException {
|
||||||
|
|
||||||
|
final var ticket = httpRequest.getHeader("Authorization");
|
||||||
|
final var url = casServerUrl + "/p3/serviceValidate" +
|
||||||
|
"?service=" + serviceUrl +
|
||||||
|
"&ticket=" + ticket;
|
||||||
|
|
||||||
|
System.err.println("CasAuthenticator.casValidation using URL: " + url);
|
||||||
|
|
||||||
|
final var response = restTemplate.getForObject(url, String.class);
|
||||||
|
|
||||||
|
final var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||||
|
.parse(new java.io.ByteArrayInputStream(response.getBytes()));
|
||||||
|
if (doc.getElementsByTagName("cas:authenticationSuccess").getLength() == 0) {
|
||||||
|
// TODO.impl: for unknown reasons, this results in a 403 FORBIDDEN
|
||||||
|
// throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "CAS service ticket could not be validated");
|
||||||
|
System.err.println("CAS service ticket could not be validated");
|
||||||
|
System.err.println("CAS-validation-URL: " + url);
|
||||||
|
System.err.println(response);
|
||||||
|
throw new BadCredentialsException("CAS service ticket could not be validated");
|
||||||
|
}
|
||||||
|
final var userName = doc.getElementsByTagName("cas:user").item(0).getTextContent();
|
||||||
|
System.err.println("CAS-user: " + userName);
|
||||||
|
return userName;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -22,6 +23,14 @@ public class WebSecurityConfig {
|
|||||||
.requestMatchers("/actuator/**").permitAll()
|
.requestMatchers("/actuator/**").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Profile("!test")
|
||||||
|
public Authenticator casServiceTicketValidator() {
|
||||||
|
return new CasAuthenticator();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ public class Context {
|
|||||||
cast(:currentTask as varchar(127)),
|
cast(:currentTask as varchar(127)),
|
||||||
cast(:currentRequest as text),
|
cast(:currentRequest as text),
|
||||||
cast(:currentSubject as varchar(63)),
|
cast(:currentSubject as varchar(63)),
|
||||||
cast(:assumedRoles as varchar(1023)));
|
cast(:assumedRoles as text));
|
||||||
""");
|
""");
|
||||||
query.setParameter("currentTask", shortenToMaxLength(currentTask, 127));
|
query.setParameter("currentTask", shortenToMaxLength(currentTask, 127));
|
||||||
query.setParameter("currentRequest", currentRequest);
|
query.setParameter("currentRequest", currentRequest);
|
||||||
|
@ -97,6 +97,7 @@ public class RestResponseEntityExceptionHandler
|
|||||||
return errorResponse(request, HttpStatus.valueOf(statusCode.value()),
|
return errorResponse(request, HttpStatus.valueOf(statusCode.value()),
|
||||||
Optional.ofNullable(response.getBody()).map(Object::toString).orElse(firstMessageLine(exc)));
|
Optional.ofNullable(response.getBody()).map(Object::toString).orElse(firstMessageLine(exc)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked,rawtypes")
|
@SuppressWarnings("unchecked,rawtypes")
|
||||||
protected ResponseEntity handleHttpMessageNotReadable(
|
protected ResponseEntity handleHttpMessageNotReadable(
|
||||||
@ -131,7 +132,7 @@ public class RestResponseEntityExceptionHandler
|
|||||||
final HttpStatusCode status,
|
final HttpStatusCode status,
|
||||||
final WebRequest request) {
|
final WebRequest request) {
|
||||||
final var errorList = exc
|
final var errorList = exc
|
||||||
.getAllValidationResults()
|
.getParameterValidationResults()
|
||||||
.stream()
|
.stream()
|
||||||
.map(ParameterValidationResult::getResolvableErrors)
|
.map(ParameterValidationResult::getResolvableErrors)
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
|
@ -5,6 +5,7 @@ import lombok.Builder;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
|
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@DisplayAs("BookingDebitor")
|
@DisplayAs("BookingDebitor")
|
||||||
public class HsBookingDebitorEntity implements Stringifyable {
|
public class HsBookingDebitorEntity implements Stringifyable, WithRoleId {
|
||||||
|
|
||||||
public static final String DEBITOR_NUMBER_TAG = "D-";
|
public static final String DEBITOR_NUMBER_TAG = "D-";
|
||||||
|
|
||||||
|
@ -2,11 +2,13 @@ package net.hostsharing.hsadminng.hs.booking.debitor;
|
|||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsBookingDebitorRepository extends Repository<HsBookingDebitorEntity, UUID> {
|
public interface HsBookingDebitorRepository extends Repository<HsBookingDebitorEntity, UUID> {
|
||||||
|
|
||||||
@Timed("app.booking.debitor.repo.findByUuid")
|
@Timed("app.booking.debitor.repo.findByUuid")
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item;
|
package net.hostsharing.hsadminng.hs.booking.item;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface BookingItemCreatedEventRepository extends Repository<BookingItemCreatedEventEntity, UUID> {
|
public interface BookingItemCreatedEventRepository extends Repository<BookingItemCreatedEventEntity, UUID> {
|
||||||
|
|
||||||
@Timed("app.booking.items.repo.save")
|
@Timed("app.booking.items.repo.save")
|
||||||
|
@ -16,6 +16,7 @@ import net.hostsharing.hsadminng.mapper.StrictMapper;
|
|||||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
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;
|
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Profile("!only-office")
|
||||||
public class HsBookingItemController implements HsBookingItemsApi {
|
public class HsBookingItemController implements HsBookingItemsApi {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -5,8 +5,8 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRbacEntity;
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRbacEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
|
|
||||||
import jakarta.persistence.AttributeOverride;
|
import jakarta.persistence.AttributeOverride;
|
||||||
import jakarta.persistence.AttributeOverrides;
|
import jakarta.persistence.AttributeOverrides;
|
||||||
@ -15,20 +15,20 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NULLABLE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NULLABLE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "hs_booking", name = "item_rv")
|
@Table(schema = "hs_booking", name = "item_rv")
|
||||||
@ -41,7 +41,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
})
|
})
|
||||||
public class HsBookingItemRbacEntity extends HsBookingItem {
|
public class HsBookingItemRbacEntity extends HsBookingItem {
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("bookingItem", HsBookingItemRbacEntity.class)
|
return rbacViewFor("bookingItem", HsBookingItemRbacEntity.class)
|
||||||
.withIdentityView(SQL.projection("caption"))
|
.withIdentityView(SQL.projection("caption"))
|
||||||
.withRestrictedViewOrderBy(SQL.expression("validity"))
|
.withRestrictedViewOrderBy(SQL.expression("validity"))
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item;
|
package net.hostsharing.hsadminng.hs.booking.item;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsBookingItemRbacRepository extends HsBookingItemRepository<HsBookingItemRbacEntity>,
|
public interface HsBookingItemRbacRepository extends HsBookingItemRepository<HsBookingItemRbacEntity>,
|
||||||
Repository<HsBookingItemRbacEntity, UUID> {
|
Repository<HsBookingItemRbacEntity, UUID> {
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item;
|
package net.hostsharing.hsadminng.hs.booking.item;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsBookingItemRealRepository extends HsBookingItemRepository<HsBookingItemRealEntity>,
|
public interface HsBookingItemRealRepository extends HsBookingItemRepository<HsBookingItemRealEntity>,
|
||||||
Repository<HsBookingItemRealEntity, UUID> {
|
Repository<HsBookingItemRealEntity, UUID> {
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item;
|
package net.hostsharing.hsadminng.hs.booking.item;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsBookingItemRepository<E extends HsBookingItem> {
|
public interface HsBookingItemRepository<E extends HsBookingItem> {
|
||||||
|
|
||||||
Optional<E> findByUuid(final UUID bookingItemUuid);
|
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.HsBookingProjectInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource;
|
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.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.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@ -20,13 +21,14 @@ import java.util.UUID;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Profile("!only-office")
|
||||||
public class HsBookingProjectController implements HsBookingProjectsApi {
|
public class HsBookingProjectController implements HsBookingProjectsApi {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsBookingProjectRbacRepository bookingProjectRepo;
|
private HsBookingProjectRbacRepository bookingProjectRepo;
|
||||||
|
@ -6,30 +6,30 @@ import lombok.Setter;
|
|||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.fetchedBySql;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "hs_booking", name = "project_rv")
|
@Table(schema = "hs_booking", name = "project_rv")
|
||||||
@ -39,7 +39,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class HsBookingProjectRbacEntity extends HsBookingProject {
|
public class HsBookingProjectRbacEntity extends HsBookingProject {
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("project", HsBookingProjectRbacEntity.class)
|
return rbacViewFor("project", HsBookingProjectRbacEntity.class)
|
||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || base.cleanIdentifier(bookingProject.caption) as idName
|
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || base.cleanIdentifier(bookingProject.caption) as idName
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.project;
|
package net.hostsharing.hsadminng.hs.booking.project;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsBookingProjectRbacRepository extends HsBookingProjectRepository<HsBookingProjectRbacEntity>,
|
public interface HsBookingProjectRbacRepository extends HsBookingProjectRepository<HsBookingProjectRbacEntity>,
|
||||||
Repository<HsBookingProjectRbacEntity, UUID> {
|
Repository<HsBookingProjectRbacEntity, UUID> {
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.project;
|
package net.hostsharing.hsadminng.hs.booking.project;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsBookingProjectRealRepository extends HsBookingProjectRepository<HsBookingProjectRealEntity>,
|
public interface HsBookingProjectRealRepository extends HsBookingProjectRepository<HsBookingProjectRealEntity>,
|
||||||
Repository<HsBookingProjectRealEntity, UUID> {
|
Repository<HsBookingProjectRealEntity, UUID> {
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.project;
|
package net.hostsharing.hsadminng.hs.booking.project;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsBookingProjectRepository<E extends HsBookingProject> {
|
public interface HsBookingProjectRepository<E extends HsBookingProject> {
|
||||||
|
|
||||||
@Timed("app.booking.projects.repo.findByUuid")
|
@Timed("app.booking.projects.repo.findByUuid")
|
||||||
|
@ -89,7 +89,7 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHost
|
|||||||
@JoinColumn(name = "alarmcontactuuid")
|
@JoinColumn(name = "alarmcontactuuid")
|
||||||
private HsOfficeContactRealEntity alarmContact;
|
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")
|
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
|
||||||
private List<HsHostingAssetRealEntity> subHostingAssets;
|
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.HsHostingAssetResource;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||||
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
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 net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@ -27,6 +28,7 @@ import java.util.UUID;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Profile("!only-office")
|
||||||
public class HsHostingAssetController implements HsHostingAssetsApi {
|
public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -36,7 +38,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsHostingAssetRbacRepository rbacAssetRepo;
|
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.asset.validators.HostingAssetEntityValidatorRegistry;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi;
|
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
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.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Profile("!only-office")
|
||||||
public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
|
public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -6,31 +6,31 @@ import lombok.Setter;
|
|||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRbacEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRbacEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef.inCaseOf;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef.inCaseOf;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NULLABLE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NULLABLE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.GUEST;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.GUEST;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.REFERRER;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.REFERRER;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "hs_hosting", name = "asset_rv")
|
@Table(schema = "hs_hosting", name = "asset_rv")
|
||||||
@ -40,7 +40,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class HsHostingAssetRbacEntity extends HsHostingAsset {
|
public class HsHostingAssetRbacEntity extends HsHostingAsset {
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("asset", HsHostingAssetRbacEntity.class)
|
return rbacViewFor("asset", HsHostingAssetRbacEntity.class)
|
||||||
.withIdentityView(SQL.projection("identifier"))
|
.withIdentityView(SQL.projection("identifier"))
|
||||||
.withRestrictedViewOrderBy(SQL.expression("identifier"))
|
.withRestrictedViewOrderBy(SQL.expression("identifier"))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
@ -8,7 +9,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsHostingAssetRbacRepository extends HsHostingAssetRepository<HsHostingAssetRbacEntity>, Repository<HsHostingAssetRbacEntity, UUID> {
|
public interface HsHostingAssetRbacRepository extends HsHostingAssetRepository<HsHostingAssetRbacEntity>, Repository<HsHostingAssetRbacEntity, UUID> {
|
||||||
|
|
||||||
@Timed("app.hostingAsset.repo.findByUuid.rbac")
|
@Timed("app.hostingAsset.repo.findByUuid.rbac")
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.Repository;
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsHostingAssetRealRepository extends HsHostingAssetRepository<HsHostingAssetRealEntity>, Repository<HsHostingAssetRealEntity, UUID> {
|
public interface HsHostingAssetRealRepository extends HsHostingAssetRepository<HsHostingAssetRealEntity>, Repository<HsHostingAssetRealEntity, UUID> {
|
||||||
|
|
||||||
@Timed("app.hostingAsset.repo.findByUuid.real")
|
@Timed("app.hostingAsset.repo.findByUuid.real")
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Profile("!only-office")
|
||||||
public interface HsHostingAssetRepository<E extends HsHostingAsset> {
|
public interface HsHostingAssetRepository<E extends HsHostingAsset> {
|
||||||
|
|
||||||
@Timed("app.hosting.assets.repo.findByUuid")
|
@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.hosting.asset.HsHostingAssetType;
|
||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
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.mapper.ToStringConverter;
|
||||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
|
|
||||||
@ -31,8 +31,8 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
|
|||||||
final EntityManagerWrapper emw,
|
final EntityManagerWrapper emw,
|
||||||
final HsBookingItemRealEntity newBookingItemRealEntity,
|
final HsBookingItemRealEntity newBookingItemRealEntity,
|
||||||
final HsHostingAssetAutoInsertResource asset,
|
final HsHostingAssetAutoInsertResource asset,
|
||||||
final StandardMapper standardMapper) {
|
final StrictMapper StrictMapper) {
|
||||||
super(emw, newBookingItemRealEntity, asset, standardMapper);
|
super(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.booking.item.HsBookingItemRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
|
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;
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
|
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ abstract class HostingAssetFactory {
|
|||||||
final EntityManagerWrapper emw;
|
final EntityManagerWrapper emw;
|
||||||
final HsBookingItemRealEntity fromBookingItem;
|
final HsBookingItemRealEntity fromBookingItem;
|
||||||
final HsHostingAssetAutoInsertResource asset;
|
final HsHostingAssetAutoInsertResource asset;
|
||||||
final StandardMapper standardMapper;
|
final StrictMapper StrictMapper;
|
||||||
|
|
||||||
protected abstract HsHostingAsset create();
|
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.BookingItemCreatedAppEvent;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
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 net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@Profile("!only-office")
|
||||||
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
|
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -25,7 +27,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
|||||||
private ObjectMapper jsonMapper;
|
private ObjectMapper jsonMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper standardMapper;
|
private StrictMapper StrictMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@ -44,9 +46,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
|||||||
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
|
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
|
||||||
final var factory = switch (newBookingItemRealEntity.getType()) {
|
final var factory = switch (newBookingItemRealEntity.getType()) {
|
||||||
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER ->
|
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER ->
|
||||||
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, standardMapper);
|
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||||
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||||
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||||
};
|
};
|
||||||
if (factory != null) {
|
if (factory != null) {
|
||||||
final var statusMessage = factory.createAndPersist();
|
final var statusMessage = factory.createAndPersist();
|
||||||
@ -62,9 +64,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
|||||||
final EntityManagerWrapper emw,
|
final EntityManagerWrapper emw,
|
||||||
final HsBookingItemRealEntity fromBookingItem,
|
final HsBookingItemRealEntity fromBookingItem,
|
||||||
final HsHostingAssetAutoInsertResource asset,
|
final HsHostingAssetAutoInsertResource asset,
|
||||||
final StandardMapper standardMapper
|
final StrictMapper StrictMapper
|
||||||
) {
|
) {
|
||||||
return new HostingAssetFactory(emw, fromBookingItem, asset, standardMapper) {
|
return new HostingAssetFactory(emw, fromBookingItem, asset, StrictMapper) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HsHostingAsset create() {
|
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.booking.item.HsBookingItemRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
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 net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
|
|
||||||
import jakarta.validation.ValidationException;
|
import jakarta.validation.ValidationException;
|
||||||
@ -19,8 +19,8 @@ public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
|
|||||||
final EntityManagerWrapper emw,
|
final EntityManagerWrapper emw,
|
||||||
final HsBookingItemRealEntity newBookingItemRealEntity,
|
final HsBookingItemRealEntity newBookingItemRealEntity,
|
||||||
final HsHostingAssetAutoInsertResource asset,
|
final HsHostingAssetAutoInsertResource asset,
|
||||||
final StandardMapper standardMapper) {
|
final StrictMapper StrictMapper) {
|
||||||
super(emw, newBookingItemRealEntity, asset, standardMapper);
|
super(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -32,7 +32,7 @@ public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
|
|||||||
.map(Enum::name)
|
.map(Enum::name)
|
||||||
.orElse(null));
|
.orElse(null));
|
||||||
}
|
}
|
||||||
final var managedWebspaceHostingAsset = standardMapper.map(asset, HsHostingAssetRealEntity.class);
|
final var managedWebspaceHostingAsset = StrictMapper.map(asset, HsHostingAssetRealEntity.class);
|
||||||
managedWebspaceHostingAsset.setBookingItem(fromBookingItem);
|
managedWebspaceHostingAsset.setBookingItem(fromBookingItem);
|
||||||
emw.createQuery(
|
emw.createQuery(
|
||||||
"SELECT asset FROM HsHostingAssetRealEntity asset WHERE asset.bookingItem.uuid=:bookingItemUuid",
|
"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.api.HsOfficeBankAccountsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountInsertResource;
|
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.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.BicUtil;
|
||||||
import org.iban4j.IbanUtil;
|
import org.iban4j.IbanUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -25,7 +25,7 @@ public class HsOfficeBankAccountController implements HsOfficeBankAccountsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeBankAccountRepository bankAccountRepo;
|
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||||
|
@ -4,7 +4,7 @@ import lombok.*;
|
|||||||
import lombok.experimental.FieldNameConstants;
|
import lombok.experimental.FieldNameConstants;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
|
|
||||||
@ -12,10 +12,10 @@ import jakarta.persistence.*;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -57,7 +57,7 @@ public class HsOfficeBankAccountEntity implements BaseEntity<HsOfficeBankAccount
|
|||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
|
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
|
||||||
.withIdentityView(SQL.projection("iban"))
|
.withIdentityView(SQL.projection("iban"))
|
||||||
.withUpdatableColumns("holder", "iban", "bic")
|
.withUpdatableColumns("holder", "iban", "bic")
|
||||||
|
@ -12,6 +12,7 @@ import lombok.experimental.SuperBuilder;
|
|||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
|
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
@ -37,7 +38,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
|||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@FieldNameConstants
|
@FieldNameConstants
|
||||||
@DisplayAs("Contact")
|
@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")
|
private static Stringify<HsOfficeContact> toString = stringify(HsOfficeContact.class, "contact")
|
||||||
.withProp(Fields.caption, HsOfficeContact::getCaption)
|
.withProp(Fields.caption, HsOfficeContact::getCaption)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.contact;
|
package net.hostsharing.hsadminng.hs.office.contact;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
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.context.Context;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
|
||||||
@ -28,7 +28,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeContactRbacRepository contactRepo;
|
private HsOfficeContactRbacRepository contactRepo;
|
||||||
@ -131,6 +131,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||||
|
entity.putPostalAddress(from(resource.getPostalAddress()));
|
||||||
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
||||||
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
||||||
};
|
};
|
||||||
|
@ -3,17 +3,17 @@ package net.hostsharing.hsadminng.hs.office.contact;
|
|||||||
import lombok.*;
|
import lombok.*;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "hs_office", name = "contact_rv")
|
@Table(schema = "hs_office", name = "contact_rv")
|
||||||
@ -24,7 +24,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
@DisplayAs("RbacContact")
|
@DisplayAs("RbacContact")
|
||||||
public class HsOfficeContactRbacEntity extends HsOfficeContact {
|
public class HsOfficeContactRbacEntity extends HsOfficeContact {
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("contact", HsOfficeContactRbacEntity.class)
|
return rbacViewFor("contact", HsOfficeContactRbacEntity.class)
|
||||||
.withIdentityView(SQL.projection("caption"))
|
.withIdentityView(SQL.projection("caption"))
|
||||||
.withUpdatableColumns("caption", "postalAddress", "emailAddresses", "phoneNumbers")
|
.withUpdatableColumns("caption", "postalAddress", "emailAddresses", "phoneNumbers")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package net.hostsharing.hsadminng.hs.office.coopassets;
|
package net.hostsharing.hsadminng.hs.office.coopassets;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@ -9,28 +8,39 @@ import lombok.Setter;
|
|||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
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.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
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.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -57,8 +67,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
|||||||
.quotedValues(false);
|
.quotedValues(false);
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(generator = "UUID")
|
@GeneratedValue
|
||||||
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
|
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
|
|
||||||
@Version
|
@Version
|
||||||
@ -122,15 +131,15 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTaggedMemberNumber() {
|
|
||||||
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return stringify.apply(this);
|
return stringify.apply(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTaggedMemberNumber() {
|
||||||
|
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toShortString() {
|
public String toShortString() {
|
||||||
return "%s:%.3s:%+1.2f".formatted(
|
return "%s:%.3s:%+1.2f".formatted(
|
||||||
@ -139,9 +148,9 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
|||||||
ofNullable(assetValue).orElse(BigDecimal.ZERO));
|
ofNullable(assetValue).orElse(BigDecimal.ZERO));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("coopAssetsTransaction", HsOfficeCoopAssetsTransactionEntity.class)
|
return rbacViewFor("coopAssetsTransaction", HsOfficeCoopAssetsTransactionEntity.class)
|
||||||
.withIdentityView(RbacView.SQL.projection("reference"))
|
.withIdentityView(SQL.projection("reference"))
|
||||||
.withUpdatableColumns("comment")
|
.withUpdatableColumns("comment")
|
||||||
.importEntityAlias("membership", HsOfficeMembershipEntity.class, usingDefaultCase(),
|
.importEntityAlias("membership", HsOfficeMembershipEntity.class, usingDefaultCase(),
|
||||||
dependsOnColumn("membershipUuid"),
|
dependsOnColumn("membershipUuid"),
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.coopshares;
|
package net.hostsharing.hsadminng.hs.office.coopshares;
|
||||||
|
|
||||||
import jakarta.persistence.EntityNotFoundException;
|
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
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.api.HsOfficeCoopSharesApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
|
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.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
|
||||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
|
||||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
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.CANCELLATION;
|
||||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION;
|
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION;
|
||||||
|
import static net.hostsharing.hsadminng.hs.validation.UuidResolver.resolve;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi {
|
public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi {
|
||||||
@ -33,14 +33,16 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
|
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HsOfficeMembershipRepository membershipRepo;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
|
|
||||||
@Timed("app.office.coopShares.api.getListOfCoopShares")
|
@Timed("app.office.coopShares.api.getListOfCoopShares")
|
||||||
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares(
|
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares(
|
||||||
final String currentSubject,
|
final String currentSubject,
|
||||||
@ -55,7 +57,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
fromValueDate,
|
fromValueDate,
|
||||||
toValueDate);
|
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);
|
return ResponseEntity.ok(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +75,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
context.define(currentSubject, assumedRoles);
|
context.define(currentSubject, assumedRoles);
|
||||||
validate(requestBody);
|
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);
|
final var saved = coopSharesTransactionRepo.save(entityToSave);
|
||||||
|
|
||||||
@ -79,7 +87,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
.path("/api/hs/office/coopsharestransactions/{id}")
|
.path("/api/hs/office/coopsharestransactions/{id}")
|
||||||
.buildAndExpand(saved.getUuid())
|
.buildAndExpand(saved.getUuid())
|
||||||
.toUri();
|
.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);
|
return ResponseEntity.created(uri).body(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +103,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeCoopSharesTransactionResource.class));
|
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) -> {
|
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||||
|
entity.setMembership(resolve("membership.uuid", resource.getMembershipUuid(), membershipRepo::findByUuid));
|
||||||
if (resource.getRevertedShareTxUuid() != null) {
|
if (resource.getRevertedShareTxUuid() != null) {
|
||||||
entity.setRevertedShareTx(coopSharesTransactionRepo.findByUuid(resource.getRevertedShareTxUuid())
|
entity.setRevertedShareTx(resolve(
|
||||||
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] revertedShareTxUuid %s not found".formatted(resource.getRevertedShareTxUuid()))));
|
"revertedShareTx.uuid",
|
||||||
|
resource.getRevertedShareTxUuid(),
|
||||||
|
coopSharesTransactionRepo::findByUuid));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final BiConsumer<HsOfficeCoopSharesTransactionEntity, HsOfficeCoopSharesTransactionResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||||
|
resource.setMembershipUuid(entity.getMembership().getUuid());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,28 +7,38 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
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.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
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.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -123,7 +133,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
|
|||||||
return "%s:%.3s:%+d".formatted(getMemberNumberTagged(), transactionType, shareCount);
|
return "%s:%.3s:%+d".formatted(getMemberNumberTagged(), transactionType, shareCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("coopSharesTransaction", HsOfficeCoopSharesTransactionEntity.class)
|
return rbacViewFor("coopSharesTransaction", HsOfficeCoopSharesTransactionEntity.class)
|
||||||
.withIdentityView(SQL.projection("reference"))
|
.withIdentityView(SQL.projection("reference"))
|
||||||
.withUpdatableColumns("comment")
|
.withUpdatableColumns("comment")
|
||||||
@ -132,6 +142,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
|
|||||||
directlyFetchedByDependsOnColumn(),
|
directlyFetchedByDependsOnColumn(),
|
||||||
NOT_NULL)
|
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(INSERT)
|
||||||
.toRole("membership", ADMIN).grantPermission(UPDATE)
|
.toRole("membership", ADMIN).grantPermission(UPDATE)
|
||||||
.toRole("membership", AGENT).grantPermission(SELECT);
|
.toRole("membership", AGENT).grantPermission(SELECT);
|
||||||
|
@ -2,14 +2,16 @@ package net.hostsharing.hsadminng.hs.office.debitor;
|
|||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
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.api.HsOfficeDebitorsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorInsertResource;
|
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.HsOfficeDebitorPatchResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
|
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.HsOfficeRelationRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||||
import net.hostsharing.hsadminng.persistence.EntityExistsValidator;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -26,6 +28,7 @@ import java.util.UUID;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
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;
|
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -36,16 +39,22 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeDebitorRepository debitorRepo;
|
private HsOfficeDebitorRepository debitorRepo;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeRelationRealRepository relrealRepo;
|
private HsOfficeRelationRealRepository realRelRepo;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private EntityExistsValidator entityValidator;
|
private HsOfficePersonRealRepository realPersonRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HsOfficeContactRealRepository realContactRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||||
|
|
||||||
@PersistenceContext
|
@PersistenceContext
|
||||||
private EntityManager em;
|
private EntityManager em;
|
||||||
@ -81,34 +90,19 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
|||||||
|
|
||||||
context.define(currentSubject, assumedRoles);
|
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");
|
"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");
|
"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");
|
"ERROR: [400] debitorRel.mark must be null");
|
||||||
|
|
||||||
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
|
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||||
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 savedEntity = debitorRepo.save(entityToSave);
|
final var savedEntity = debitorRepo.save(entityToSave).reload(em);
|
||||||
em.flush();
|
|
||||||
em.refresh(savedEntity);
|
|
||||||
|
|
||||||
final var uri =
|
final var uri =
|
||||||
MvcUriComponentsBuilder.fromController(getClass())
|
MvcUriComponentsBuilder.fromController(getClass())
|
||||||
@ -181,7 +175,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
|||||||
|
|
||||||
context.define(currentSubject, assumedRoles);
|
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);
|
new HsOfficeDebitorEntityPatcher(em, current).apply(body);
|
||||||
|
|
||||||
@ -191,7 +185,39 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
|||||||
return ResponseEntity.ok(mapped);
|
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) -> {
|
final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||||
resource.setDebitorNumber(entity.getTaggedDebitorNumber());
|
resource.setDebitorNumber(entity.getTaggedDebitorNumber());
|
||||||
|
resource.getPartner().setPartnerNumber(entity.getPartner().getTaggedPartnerNumber());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,16 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
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.HsOfficeRelation;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
|
||||||
import org.hibernate.annotations.JoinFormula;
|
import org.hibernate.annotations.JoinFormula;
|
||||||
import org.hibernate.annotations.NotFound;
|
import org.hibernate.annotations.NotFound;
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
@ -40,17 +40,17 @@ import static jakarta.persistence.CascadeType.PERSIST;
|
|||||||
import static jakarta.persistence.CascadeType.REFRESH;
|
import static jakarta.persistence.CascadeType.REFRESH;
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NULLABLE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NULLABLE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.fetchedBySql;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -75,7 +75,6 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
|||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
|
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
|
|
||||||
@Version
|
@Version
|
||||||
@ -87,16 +86,16 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
|||||||
value = """
|
value = """
|
||||||
(
|
(
|
||||||
SELECT DISTINCT partner.uuid
|
SELECT DISTINCT partner.uuid
|
||||||
FROM hs_office.partner_rv partner
|
FROM hs_office.partner partner
|
||||||
JOIN hs_office.relation_rv dRel
|
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_rv pRel
|
JOIN hs_office.relation pRel
|
||||||
ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER'
|
ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER'
|
||||||
WHERE pRel.holderUuid = dRel.anchorUuid
|
WHERE pRel.holderUuid = dRel.anchorUuid
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
@NotFound(action = NotFoundAction.IGNORE) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
|
@NotFound(action = NotFoundAction.EXCEPTION) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
|
||||||
private HsOfficePartnerEntity partner;
|
private HsOfficePartnerRealEntity partner;
|
||||||
|
|
||||||
@Column(name = "debitornumbersuffix", length = 2)
|
@Column(name = "debitornumbersuffix", length = 2)
|
||||||
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
||||||
@ -132,9 +131,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
|||||||
@Override
|
@Override
|
||||||
public HsOfficeDebitorEntity load() {
|
public HsOfficeDebitorEntity load() {
|
||||||
BaseEntity.super.load();
|
BaseEntity.super.load();
|
||||||
if (partner != null) {
|
|
||||||
partner.load();
|
partner.load();
|
||||||
}
|
|
||||||
debitorRel.load();
|
debitorRel.load();
|
||||||
if (refundBankAccount != null) {
|
if (refundBankAccount != null) {
|
||||||
refundBankAccount.load();
|
refundBankAccount.load();
|
||||||
@ -145,7 +142,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
|||||||
public String getTaggedDebitorNumber() {
|
public String getTaggedDebitorNumber() {
|
||||||
return ofNullable(partner)
|
return ofNullable(partner)
|
||||||
.filter(partner -> debitorNumberSuffix != null)
|
.filter(partner -> debitorNumberSuffix != null)
|
||||||
.map(HsOfficePartnerEntity::getPartnerNumber)
|
.map(HsOfficePartner::getPartnerNumber)
|
||||||
.map(partnerNumber -> DEBITOR_NUMBER_TAG + partnerNumber + debitorNumberSuffix)
|
.map(partnerNumber -> DEBITOR_NUMBER_TAG + partnerNumber + debitorNumberSuffix)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
@ -160,7 +157,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
|||||||
return getTaggedDebitorNumber();
|
return getTaggedDebitorNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
|
return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
|
||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT debitor.uuid AS uuid,
|
SELECT debitor.uuid AS uuid,
|
||||||
|
@ -19,7 +19,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
|
|||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
||||||
JOIN HsOfficePartnerEntity partner
|
JOIN HsOfficePartnerRealEntity partner
|
||||||
ON partner.partnerRel.holder = debitor.debitorRel.anchor
|
ON partner.partnerRel.holder = debitor.debitorRel.anchor
|
||||||
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
|
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
|
||||||
WHERE partner.partnerNumber = :partnerNumber
|
WHERE partner.partnerNumber = :partnerNumber
|
||||||
@ -42,7 +42,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
|
|||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
||||||
JOIN HsOfficePartnerEntity partner
|
JOIN HsOfficePartnerRealEntity partner
|
||||||
ON partner.partnerRel.holder = debitor.debitorRel.anchor
|
ON partner.partnerRel.holder = debitor.debitorRel.anchor
|
||||||
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
|
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
|
||||||
JOIN HsOfficePersonRealEntity person
|
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.HsOfficeMembershipInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
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.generated.api.v1.model.HsOfficeMembershipResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRbacEntity;
|
||||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
|
||||||
|
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@ -28,7 +30,10 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HsOfficePartnerRealRepository partnerRepo;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeMembershipRepository membershipRepo;
|
private HsOfficeMembershipRepository membershipRepo;
|
||||||
@ -47,7 +52,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
|||||||
|
|
||||||
final var entities = partnerNumber != null
|
final var entities = partnerNumber != null
|
||||||
? membershipRepo.findMembershipsByPartnerNumber(
|
? membershipRepo.findMembershipsByPartnerNumber(
|
||||||
cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, partnerNumber))
|
cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, partnerNumber))
|
||||||
: partnerUuid != null
|
: partnerUuid != null
|
||||||
? membershipRepo.findMembershipsByPartnerUuid(partnerUuid)
|
? membershipRepo.findMembershipsByPartnerUuid(partnerUuid)
|
||||||
: membershipRepo.findAll();
|
: membershipRepo.findAll();
|
||||||
@ -68,7 +73,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
|||||||
|
|
||||||
context.define(currentSubject, assumedRoles);
|
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);
|
final var saved = membershipRepo.save(entityToSave);
|
||||||
|
|
||||||
@ -164,5 +169,12 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
|||||||
if (entity.getValidity().hasUpperBound()) {
|
if (entity.getValidity().hasUpperBound()) {
|
||||||
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
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.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
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.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
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.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
import org.hibernate.annotations.Type;
|
import org.hibernate.annotations.Type;
|
||||||
@ -38,21 +39,21 @@ import static io.hypersistence.utils.hibernate.type.range.Range.emptyRange;
|
|||||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
|
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
|
||||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
|
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.fetchedBySql;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -63,7 +64,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@DisplayAs("Membership")
|
@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 MEMBER_NUMBER_TAG = "M-";
|
||||||
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
|
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
|
||||||
@ -84,7 +85,7 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
|
|||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "partneruuid")
|
@JoinColumn(name = "partneruuid")
|
||||||
private HsOfficePartnerEntity partner;
|
private HsOfficePartnerRealEntity partner;
|
||||||
|
|
||||||
@Column(name = "membernumbersuffix", length = 2)
|
@Column(name = "membernumbersuffix", length = 2)
|
||||||
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
||||||
@ -160,7 +161,7 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("membership", HsOfficeMembershipEntity.class)
|
return rbacViewFor("membership", HsOfficeMembershipEntity.class)
|
||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT m.uuid AS uuid,
|
SELECT m.uuid AS uuid,
|
||||||
|
@ -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.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
||||||
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
||||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
|
||||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||||
|
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
|
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
|
||||||
|
|
||||||
private final StandardMapper mapper;
|
private final StrictMapper mapper;
|
||||||
private final HsOfficeMembershipEntity entity;
|
private final HsOfficeMembershipEntity entity;
|
||||||
|
|
||||||
public HsOfficeMembershipEntityPatcher(
|
public HsOfficeMembershipEntityPatcher(
|
||||||
final StandardMapper mapper,
|
final StrictMapper mapper,
|
||||||
final HsOfficeMembershipEntity entity) {
|
final HsOfficeMembershipEntity entity) {
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
this.entity = entity;
|
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.HsOfficeRelationRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
|
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 net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@ -26,6 +26,7 @@ import jakarta.persistence.EntityManager;
|
|||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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.hs.office.relation.HsOfficeRelationType.EX_PARTNER;
|
||||||
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
|
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
|
||||||
@ -38,10 +39,10 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficePartnerRepository partnerRepo;
|
private HsOfficePartnerRbacRepository partnerRepo;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeRelationRealRepository relationRepo;
|
private HsOfficeRelationRealRepository relationRepo;
|
||||||
@ -60,7 +61,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
|||||||
|
|
||||||
final var entities = partnerRepo.findPartnerByOptionalNameLike(name);
|
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);
|
return ResponseEntity.ok(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
|||||||
.path("/api/hs/office/partners/{id}")
|
.path("/api/hs/office/partners/{id}")
|
||||||
.buildAndExpand(saved.getUuid())
|
.buildAndExpand(saved.getUuid())
|
||||||
.toUri();
|
.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);
|
return ResponseEntity.created(uri).body(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +102,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
|||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
return ResponseEntity.notFound().build();
|
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
|
@Override
|
||||||
@ -118,7 +120,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
|||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
return ResponseEntity.notFound().build();
|
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
|
@Override
|
||||||
@ -161,20 +164,20 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
|||||||
final var saved = partnerRepo.save(current);
|
final var saved = partnerRepo.save(current);
|
||||||
optionallyCreateExPartnerRelation(saved, previousPartnerRel);
|
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);
|
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())) {
|
if (!saved.getPartnerRel().getUuid().equals(previousPartnerRel.getUuid())) {
|
||||||
// TODO.impl: we also need to use the new partner-person as the anchor
|
// TODO.impl: we also need to use the new partner-person as the anchor
|
||||||
relationRepo.save(previousPartnerRel.toBuilder().uuid(null).type(EX_PARTNER).build());
|
relationRepo.save(previousPartnerRel.toBuilder().uuid(null).type(EX_PARTNER).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
|
private HsOfficePartnerRbacEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
|
||||||
final var entityToSave = new HsOfficePartnerEntity();
|
final var entityToSave = new HsOfficePartnerRbacEntity();
|
||||||
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
|
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
|
||||||
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
|
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
|
||||||
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
|
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
|
||||||
return entityToSave;
|
return entityToSave;
|
||||||
@ -197,4 +200,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
|||||||
throw new ReferenceNotFoundException(entityClass, uuid, exc);
|
throw new ReferenceNotFoundException(entityClass, uuid, exc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final BiConsumer<HsOfficePartnerRbacEntity, HsOfficePartnerResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||||
|
resource.setPartnerNumber(entity.getTaggedPartnerNumber());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ package net.hostsharing.hsadminng.hs.office.partner;
|
|||||||
import lombok.*;
|
import lombok.*;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
|
|
||||||
@ -13,10 +13,10 @@ import java.io.IOException;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -67,7 +67,7 @@ public class HsOfficePartnerDetailsEntity implements BaseEntity<HsOfficePartnerD
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
|
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
|
||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT partnerDetails.uuid as uuid, partner_iv.idName as idName
|
SELECT partnerDetails.uuid as uuid, partner_iv.idName as idName
|
||||||
|
@ -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.RbacView;
|
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.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.RbacView.Column.dependsOnColumn;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.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 RbacView 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> {
|
class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> {
|
||||||
private final EntityManager em;
|
private final EntityManager em;
|
||||||
private final HsOfficePartnerEntity entity;
|
private final HsOfficePartnerRbacEntity entity;
|
||||||
HsOfficePartnerEntityPatcher(
|
HsOfficePartnerEntityPatcher(
|
||||||
final EntityManager em,
|
final EntityManager em,
|
||||||
final HsOfficePartnerEntity entity) {
|
final HsOfficePartnerRbacEntity entity) {
|
||||||
this.em = em;
|
this.em = em;
|
||||||
this.entity = entity;
|
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);
|
|
||||||
}
|
|
@ -9,6 +9,7 @@ import lombok.experimental.FieldNameConstants;
|
|||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
|
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -30,7 +31,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
|||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@FieldNameConstants
|
@FieldNameConstants
|
||||||
@DisplayAs("Person")
|
@DisplayAs("Person")
|
||||||
public class HsOfficePerson<T extends HsOfficePerson<?> & BaseEntity<?>> implements BaseEntity<T>, Stringifyable {
|
public class HsOfficePerson<T extends HsOfficePerson<?> & BaseEntity<?>> implements BaseEntity<T>, Stringifyable, WithRoleId {
|
||||||
|
|
||||||
private static Stringify<HsOfficePerson> toString = stringify(HsOfficePerson.class, "person")
|
private static Stringify<HsOfficePerson> toString = stringify(HsOfficePerson.class, "person")
|
||||||
.withProp(Fields.personType, HsOfficePerson::getPersonType)
|
.withProp(Fields.personType, HsOfficePerson::getPersonType)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.person;
|
package net.hostsharing.hsadminng.hs.office.person;
|
||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
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.context.Context;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource;
|
||||||
@ -24,7 +24,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficePersonRbacRepository personRepo;
|
private HsOfficePersonRbacRepository personRepo;
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.person;
|
package net.hostsharing.hsadminng.hs.office.person;
|
||||||
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import lombok.experimental.FieldNameConstants;
|
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "hs_office", name = "person_rv")
|
@Table(schema = "hs_office", name = "person_rv")
|
||||||
@ -22,11 +21,10 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@FieldNameConstants
|
|
||||||
@DisplayAs("RbacPerson")
|
@DisplayAs("RbacPerson")
|
||||||
public class HsOfficePersonRbacEntity extends HsOfficePerson<HsOfficePersonRbacEntity> {
|
public class HsOfficePersonRbacEntity extends HsOfficePerson<HsOfficePersonRbacEntity> {
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("person", HsOfficePersonRbacEntity.class)
|
return rbacViewFor("person", HsOfficePersonRbacEntity.class)
|
||||||
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
|
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
|
||||||
.withUpdatableColumns("personType", "title", "salutation", "tradeName", "givenName", "familyName")
|
.withUpdatableColumns("personType", "title", "salutation", "tradeName", "givenName", "familyName")
|
||||||
|
@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.person;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.experimental.FieldNameConstants;
|
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
|
|
||||||
@ -17,7 +16,6 @@ import jakarta.persistence.Table;
|
|||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@FieldNameConstants
|
|
||||||
@DisplayAs("RealPerson")
|
@DisplayAs("RealPerson")
|
||||||
public class HsOfficePersonRealEntity extends HsOfficePerson<HsOfficePersonRealEntity> {
|
public class HsOfficePersonRealEntity extends HsOfficePerson<HsOfficePersonRealEntity> {
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ public enum HsOfficePersonType {
|
|||||||
UNKNOWN_PERSON_TYPE("??"),
|
UNKNOWN_PERSON_TYPE("??"),
|
||||||
NATURAL_PERSON("NP"), // a human being
|
NATURAL_PERSON("NP"), // a human being
|
||||||
LEGAL_PERSON("LP"), // incorporated legal entity like A/S, GmbH, e.K., eG, e.V.
|
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
|
INCORPORATED_FIRM("IF"), // registered business partnership like OHG, Partnerschaftsgesellschaft
|
||||||
UNINCORPORATED_FIRM("UF"), // unregistered partnership, association etc. like GbR, ARGE, community of heirs
|
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
|
PUBLIC_INSTITUTION("PI"); // entities under public law like government entities, KdöR, AöR
|
||||||
|
@ -6,6 +6,7 @@ import lombok.experimental.SuperBuilder;
|
|||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
|
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
|||||||
@Setter
|
@Setter
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@FieldNameConstants
|
@FieldNameConstants
|
||||||
public class HsOfficeRelation implements BaseEntity<HsOfficeRelation>, Stringifyable {
|
public class HsOfficeRelation implements BaseEntity<HsOfficeRelation>, Stringifyable, WithRoleId {
|
||||||
|
|
||||||
private static Stringify<HsOfficeRelation> toString = stringify(HsOfficeRelation.class, "rel")
|
private static Stringify<HsOfficeRelation> toString = stringify(HsOfficeRelation.class, "rel")
|
||||||
.withProp(Fields.anchor, HsOfficeRelation::getAnchor)
|
.withProp(Fields.anchor, HsOfficeRelation::getAnchor)
|
||||||
|
@ -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.generated.api.v1.model.*;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -32,7 +32,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeRelationRbacRepository rbacRelationRepo;
|
private HsOfficeRelationRbacRepository rbacRelationRepo;
|
||||||
@ -185,6 +185,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRealEntity> CONTACT_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRealEntity> CONTACT_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||||
|
entity.putPostalAddress(from(resource.getPostalAddress()));
|
||||||
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
||||||
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
||||||
};
|
};
|
||||||
|
@ -7,31 +7,31 @@ import lombok.experimental.SuperBuilder;
|
|||||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacEntity;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef.inCaseOf;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef.inCaseOf;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef.inOtherCases;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef.inOtherCases;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.REFERRER;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.REFERRER;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "hs_office", name = "relation_rv")
|
@Table(schema = "hs_office", name = "relation_rv")
|
||||||
@ -42,7 +42,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
@DisplayAs("RbacRelation")
|
@DisplayAs("RbacRelation")
|
||||||
public class HsOfficeRelationRbacEntity extends HsOfficeRelation {
|
public class HsOfficeRelationRbacEntity extends HsOfficeRelation {
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("relation", HsOfficeRelationRbacEntity.class)
|
return rbacViewFor("relation", HsOfficeRelationRbacEntity.class)
|
||||||
.withIdentityView(SQL.projection("""
|
.withIdentityView(SQL.projection("""
|
||||||
(select idName from hs_office.person_iv p where p.uuid = anchorUuid)
|
(select idName from hs_office.person_iv p where p.uuid = anchorUuid)
|
||||||
|
@ -42,22 +42,22 @@ public interface HsOfficeRelationRbacRepository extends Repository<HsOfficeRelat
|
|||||||
toSqlLikeOperand(mark), toSqlLikeOperand(personData), toSqlLikeOperand(contactData));
|
toSqlLikeOperand(mark), toSqlLikeOperand(personData), toSqlLikeOperand(contactData));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use ELIKE instead of lower(...) LIKE ...? Or use jsonb_path with RegEx like emailAddressRegEx in ContactRepo?
|
// TODO: Or use jsonb_path with RegEx like emailAddressRegEx in ContactRepo?
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT rel FROM HsOfficeRelationRbacEntity AS rel
|
SELECT rel FROM HsOfficeRelationRbacEntity AS rel
|
||||||
WHERE (:relationType IS NULL OR CAST(rel.type AS String) = :relationType)
|
WHERE (:relationType IS NULL OR CAST(rel.type AS String) = :relationType)
|
||||||
AND ( :personUuid IS NULL
|
AND ( :personUuid IS NULL
|
||||||
OR rel.anchor.uuid = :personUuid OR rel.holder.uuid = :personUuid )
|
OR rel.anchor.uuid = :personUuid OR rel.holder.uuid = :personUuid )
|
||||||
AND ( :mark IS NULL OR lower(rel.mark) LIKE :mark )
|
AND ( :mark IS NULL OR rel.mark ILIKE :mark )
|
||||||
AND ( :personData IS NULL
|
AND ( :personData IS NULL
|
||||||
OR lower(rel.anchor.tradeName) LIKE :personData OR lower(rel.holder.tradeName) LIKE :personData
|
OR rel.anchor.tradeName ILIKE :personData OR rel.holder.tradeName ILIKE :personData
|
||||||
OR lower(rel.anchor.familyName) LIKE :personData OR lower(rel.holder.familyName) LIKE :personData
|
OR rel.anchor.familyName ILIKE :personData OR rel.holder.familyName ILIKE :personData
|
||||||
OR lower(rel.anchor.givenName) LIKE :personData OR lower(rel.holder.givenName) LIKE :personData )
|
OR rel.anchor.givenName ILIKE :personData OR rel.holder.givenName ILIKE :personData )
|
||||||
AND ( :contactData IS NULL
|
AND ( :contactData IS NULL
|
||||||
OR lower(rel.contact.caption) LIKE :contactData
|
OR rel.contact.caption ILIKE :contactData
|
||||||
OR lower(CAST(rel.contact.postalAddress AS String)) LIKE :contactData
|
OR CAST(rel.contact.postalAddress AS String) ILIKE :contactData
|
||||||
OR lower(CAST(rel.contact.emailAddresses AS String)) LIKE :contactData
|
OR CAST(rel.contact.emailAddresses AS String) ILIKE :contactData
|
||||||
OR lower(CAST(rel.contact.phoneNumbers AS String)) LIKE :contactData )
|
OR CAST(rel.contact.phoneNumbers AS String) ILIKE :contactData )
|
||||||
""")
|
""")
|
||||||
@Timed("app.office.relations.repo.findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl.rbac")
|
@Timed("app.office.relations.repo.findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl.rbac")
|
||||||
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl(
|
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl(
|
||||||
|
@ -14,10 +14,6 @@ public interface HsOfficeRelationRealRepository extends Repository<HsOfficeRelat
|
|||||||
@Timed("app.repo.relations.findByUuid.real")
|
@Timed("app.repo.relations.findByUuid.real")
|
||||||
Optional<HsOfficeRelationRealEntity> findByUuid(UUID id);
|
Optional<HsOfficeRelationRealEntity> findByUuid(UUID id);
|
||||||
|
|
||||||
default List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) {
|
|
||||||
return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType == null ? null : relationType.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT p.* FROM hs_office.relation AS p
|
SELECT p.* FROM hs_office.relation AS p
|
||||||
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
||||||
@ -25,13 +21,51 @@ public interface HsOfficeRelationRealRepository extends Repository<HsOfficeRelat
|
|||||||
@Timed("app.repo.relations.findRelationRelatedToPersonUuid.real")
|
@Timed("app.repo.relations.findRelationRelatedToPersonUuid.real")
|
||||||
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds relations by a conjunction of optional criteria, including anchorPerson, holderPerson and contact data.
|
||||||
|
* *
|
||||||
|
* @param personUuid the optional UUID of the anchorPerson or holderPerson
|
||||||
|
* @param relationType the type of the relation
|
||||||
|
* @param mark the mark (use '%' for wildcard), case ignored
|
||||||
|
* @param personData a string to match the persons tradeName, familyName or givenName (use '%' for wildcard), case ignored
|
||||||
|
* @param contactData a string to match the contacts caption, postalAddress, emailAddresses or phoneNumbers (use '%' for wildcard), case ignored
|
||||||
|
* @return a list of (accessible) relations which match all given criteria
|
||||||
|
*/
|
||||||
|
default List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData(
|
||||||
|
final UUID personUuid,
|
||||||
|
final HsOfficeRelationType relationType,
|
||||||
|
final String mark,
|
||||||
|
final String personData,
|
||||||
|
final String contactData) {
|
||||||
|
return findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl(
|
||||||
|
personUuid, toStringOrNull(relationType),
|
||||||
|
toSqlLikeOperand(mark), toSqlLikeOperand(personData), toSqlLikeOperand(contactData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Or use jsonb_path with RegEx like emailAddressRegEx in ContactRepo?
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT p.* FROM hs_office.relation AS p
|
SELECT rel FROM HsOfficeRelationRealEntity AS rel
|
||||||
WHERE (:relationType IS NULL OR p.type = cast(:relationType AS hs_office.RelationType))
|
WHERE (:relationType IS NULL OR CAST(rel.type AS String) = :relationType)
|
||||||
AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid)
|
AND ( :personUuid IS NULL
|
||||||
""", nativeQuery = true)
|
OR rel.anchor.uuid = :personUuid OR rel.holder.uuid = :personUuid )
|
||||||
@Timed("app.repo.relations.findRelationRelatedToPersonUuidAndRelationTypeString.real")
|
AND ( :mark IS NULL OR rel.mark ILIKE :mark )
|
||||||
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType);
|
AND ( :personData IS NULL
|
||||||
|
OR rel.anchor.tradeName ILIKE :personData OR rel.holder.tradeName ILIKE :personData
|
||||||
|
OR rel.anchor.familyName ILIKE :personData OR rel.holder.familyName ILIKE :personData
|
||||||
|
OR rel.anchor.givenName ILIKE :personData OR rel.holder.givenName ILIKE :personData )
|
||||||
|
AND ( :contactData IS NULL
|
||||||
|
OR rel.contact.caption ILIKE :contactData
|
||||||
|
OR CAST(rel.contact.postalAddress AS String) ILIKE :contactData
|
||||||
|
OR CAST(rel.contact.emailAddresses AS String) ILIKE :contactData
|
||||||
|
OR CAST(rel.contact.phoneNumbers AS String) ILIKE :contactData )
|
||||||
|
""")
|
||||||
|
@Timed("app.office.relations.repo.findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl.real")
|
||||||
|
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl(
|
||||||
|
final UUID personUuid,
|
||||||
|
final String relationType,
|
||||||
|
final String mark,
|
||||||
|
final String personData,
|
||||||
|
final String contactData);
|
||||||
|
|
||||||
@Timed("app.repo.relations.save.real")
|
@Timed("app.repo.relations.save.real")
|
||||||
HsOfficeRelationRealEntity save(final HsOfficeRelationRealEntity entity);
|
HsOfficeRelationRealEntity save(final HsOfficeRelationRealEntity entity);
|
||||||
@ -41,4 +75,11 @@ public interface HsOfficeRelationRealRepository extends Repository<HsOfficeRelat
|
|||||||
|
|
||||||
@Timed("app.repo.relations.deleteByUuid.real")
|
@Timed("app.repo.relations.deleteByUuid.real")
|
||||||
int deleteByUuid(UUID uuid);
|
int deleteByUuid(UUID uuid);
|
||||||
|
private static String toSqlLikeOperand(final String text) {
|
||||||
|
return text == null ? null : ("%" + text.toLowerCase() + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toStringOrNull(final HsOfficeRelationType relationType) {
|
||||||
|
return relationType == null ? null : relationType.name();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,14 @@ package net.hostsharing.hsadminng.hs.office.sepamandate;
|
|||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
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.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.HsOfficeSepaMandateInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
|
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.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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.EntityManager;
|
||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import jakarta.validation.ValidationException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@ -29,7 +33,13 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HsOfficeDebitorRepository debitorRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeSepaMandateRepository sepaMandateRepo;
|
private HsOfficeSepaMandateRepository sepaMandateRepo;
|
||||||
@ -137,10 +147,22 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
|
|||||||
if (entity.getValidity().hasUpperBound()) {
|
if (entity.getValidity().hasUpperBound()) {
|
||||||
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
||||||
}
|
}
|
||||||
|
resource.setDebitor(mapper.map(entity.getDebitor(), HsOfficeDebitorResource.class));
|
||||||
resource.getDebitor().setDebitorNumber(entity.getDebitor().getTaggedDebitorNumber());
|
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) -> {
|
final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||||
entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo()));
|
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"
|
||||||
|
)
|
||||||
|
));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity
|
|||||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||||
import net.hostsharing.hsadminng.repr.Stringify;
|
import net.hostsharing.hsadminng.repr.Stringify;
|
||||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||||
import org.hibernate.annotations.Type;
|
import org.hibernate.annotations.Type;
|
||||||
@ -20,16 +20,16 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
|
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@ -100,7 +100,7 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, BaseEntity<HsOf
|
|||||||
return reference;
|
return reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacSpec rbac() {
|
||||||
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
|
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
|
||||||
.withIdentityView(query("""
|
.withIdentityView(query("""
|
||||||
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
|
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
|
||||||
|
@ -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 org.hibernate.Hibernate;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public interface BaseEntity<T extends BaseEntity<?>> {
|
public interface BaseEntity<T extends BaseEntity<?>> {
|
||||||
@ -15,4 +16,10 @@ public interface BaseEntity<T extends BaseEntity<?>> {
|
|||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (T) this;
|
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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +1,22 @@
|
|||||||
package net.hostsharing.hsadminng.ping;
|
package net.hostsharing.hsadminng.ping;
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class PingController {
|
public class PingController {
|
||||||
|
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@RequestMapping(value = "/api/ping", method = RequestMethod.GET)
|
@RequestMapping(value = "/api/ping", method = RequestMethod.GET)
|
||||||
public String ping() {
|
public String ping(
|
||||||
return "pong\n";
|
@RequestHeader(name = "current-subject") @NotNull String currentSubject,
|
||||||
|
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles
|
||||||
|
) {
|
||||||
|
return "pong " + currentSubject + "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,20 +7,20 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.NEW;
|
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.NEW;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.GUEST;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.GUEST;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||||
|
|
||||||
public class InsertTriggerGenerator {
|
public class InsertTriggerGenerator {
|
||||||
|
|
||||||
private final RbacView rbacDef;
|
private final RbacSpec rbacDef;
|
||||||
private final String liquibaseTagPrefix;
|
private final String liquibaseTagPrefix;
|
||||||
|
|
||||||
public InsertTriggerGenerator(final RbacView rbacDef, final String liqibaseTagPrefix) {
|
public InsertTriggerGenerator(final RbacSpec rbacDef, final String liqibaseTagPrefix) {
|
||||||
this.rbacDef = rbacDef;
|
this.rbacDef = rbacDef;
|
||||||
this.liquibaseTagPrefix = liqibaseTagPrefix;
|
this.liquibaseTagPrefix = liqibaseTagPrefix;
|
||||||
}
|
}
|
||||||
@ -203,8 +203,8 @@ public class InsertTriggerGenerator {
|
|||||||
plPgSql.chopEmptyLines();
|
plPgSql.chopEmptyLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateInsertPermissionChecksForSingleGrant(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) {
|
private void generateInsertPermissionChecksForSingleGrant(final StringWriter plPgSql, final RbacSpec.RbacGrantDefinition g) {
|
||||||
final RbacView.EntityAlias superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias();
|
final RbacSpec.EntityAlias superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias();
|
||||||
|
|
||||||
final var caseCondition = g.isConditional()
|
final var caseCondition = g.isConditional()
|
||||||
? ("NEW.type in (" + toStringList(g.getForCases()) + ") and ")
|
? ("NEW.type in (" + toStringList(g.getForCases()) + ") and ")
|
||||||
@ -275,15 +275,15 @@ public class InsertTriggerGenerator {
|
|||||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
|
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toStringList(final Set<RbacView.CaseDef> cases) {
|
private String toStringList(final Set<RbacSpec.CaseDef> cases) {
|
||||||
return cases.stream().map(c -> "'" + c.value + "'").collect(joining(", "));
|
return cases.stream().map(c -> "'" + c.value + "'").collect(joining(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGrantToADifferentTable(final RbacView.RbacGrantDefinition g) {
|
private boolean isGrantToADifferentTable(final RbacSpec.RbacGrantDefinition g) {
|
||||||
return !rbacDef.getRootEntityAlias().getRawTableNameWithSchema().equals(g.getSuperRoleDef().getEntityAlias().getRawTableNameWithSchema());
|
return !rbacDef.getRootEntityAlias().getRawTableNameWithSchema().equals(g.getSuperRoleDef().getEntityAlias().getRawTableNameWithSchema());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<RbacView.RbacGrantDefinition> getInsertGrants() {
|
private Stream<RbacSpec.RbacGrantDefinition> getInsertGrants() {
|
||||||
return rbacDef.getGrantDefs().stream()
|
return rbacDef.getGrantDefs().stream()
|
||||||
.filter(g -> g.grantType() == PERM_TO_ROLE)
|
.filter(g -> g.grantType() == PERM_TO_ROLE)
|
||||||
.filter(g -> g.getPermDef().toCreate && g.getPermDef().getPermission() == INSERT);
|
.filter(g -> g.getPermDef().toCreate && g.getPermDef().getPermission() == INSERT);
|
||||||
@ -298,14 +298,14 @@ public class InsertTriggerGenerator {
|
|||||||
g.getSuperRoleDef().getEntityAlias().isGlobal() && g.getSuperRoleDef().getRole() == GUEST);
|
g.getSuperRoleDef().getEntityAlias().isGlobal() && g.getSuperRoleDef().getRole() == GUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<RbacView.RbacGrantDefinition> getOptionalInsertGrant() {
|
private Optional<RbacSpec.RbacGrantDefinition> getOptionalInsertGrant() {
|
||||||
return getInsertGrants()
|
return getInsertGrants()
|
||||||
.reduce(singleton());
|
.reduce(singleton());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<RbacView.RbacRoleDefinition> getOptionalInsertSuperRole() {
|
private Optional<RbacSpec.RbacRoleDefinition> getOptionalInsertSuperRole() {
|
||||||
return getInsertGrants()
|
return getInsertGrants()
|
||||||
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
|
.map(RbacSpec.RbacGrantDefinition::getSuperRoleDef)
|
||||||
.reduce(singleton());
|
.reduce(singleton());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,12 +319,12 @@ public class InsertTriggerGenerator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String toVar(final RbacView.RbacRoleDefinition roleDef) {
|
private static String toVar(final RbacSpec.RbacRoleDefinition roleDef) {
|
||||||
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().name());
|
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String toRoleDescriptor(final RbacView.RbacRoleDefinition roleDef, final String ref) {
|
private String toRoleDescriptor(final RbacSpec.RbacRoleDefinition roleDef, final String ref) {
|
||||||
final var functionName = roleDef.descriptorFunctionName();
|
final var functionName = roleDef.descriptorFunctionName();
|
||||||
if (roleDef.getEntityAlias().isGlobal()) {
|
if (roleDef.getEntityAlias().isGlobal()) {
|
||||||
return functionName + "()";
|
return functionName + "()";
|
||||||
|
@ -3,12 +3,12 @@ package net.hostsharing.hsadminng.rbac.generator;
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||||
|
|
||||||
public class RbacIdentityViewGenerator {
|
public class RbacIdentityViewGenerator {
|
||||||
private final RbacView rbacDef;
|
private final RbacSpec rbacDef;
|
||||||
private final String liquibaseTagPrefix;
|
private final String liquibaseTagPrefix;
|
||||||
private final String simpleEntityVarName;
|
private final String simpleEntityVarName;
|
||||||
private final String rawTableName;
|
private final String rawTableName;
|
||||||
|
|
||||||
public RbacIdentityViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
public RbacIdentityViewGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||||
this.rbacDef = rbacDef;
|
this.rbacDef = rbacDef;
|
||||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||||
this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
||||||
|
@ -7,7 +7,7 @@ public class RbacObjectGenerator {
|
|||||||
private final String liquibaseTagPrefix;
|
private final String liquibaseTagPrefix;
|
||||||
private final String rawTableName;
|
private final String rawTableName;
|
||||||
|
|
||||||
public RbacObjectGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
public RbacObjectGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||||
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package net.hostsharing.hsadminng.rbac.generator;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||||
|
|
||||||
|
class RbacRbacSystemRebuildGenerator {
|
||||||
|
|
||||||
|
private final RbacSpec rbacDef;
|
||||||
|
private final Set<RbacGrantDefinition> rbacGrants = new HashSet<>();
|
||||||
|
private final String liquibaseTagPrefix;
|
||||||
|
private final String rawTableName;
|
||||||
|
|
||||||
|
RbacRbacSystemRebuildGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||||
|
this.rbacDef = rbacDef;
|
||||||
|
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||||
|
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateTo(final StringWriter plPgSql) {
|
||||||
|
plPgSql.writeLn("""
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacRbacSystemRebuildGenerator:${liquibaseTagPrefix}-rbac-rebuild endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- HOWTO: Rebuild RBAC-system for table ${rawTableName} after changing its RBAC specification.
|
||||||
|
--
|
||||||
|
-- begin transaction;
|
||||||
|
-- call base.defineContext('re-creating RBAC for table ${rawTableName}', null, <<insert executing global admin user here>>);
|
||||||
|
-- call ${rawTableName}_rebuild_rbac_system();
|
||||||
|
-- commit;
|
||||||
|
--
|
||||||
|
-- How it works:
|
||||||
|
-- 1. All grants previously created from the RBAC specification of this table will be deleted.
|
||||||
|
-- These grants are identified by `${rawTableName}.grantedByTriggerOf IS NOT NULL`.
|
||||||
|
-- User-induced grants (`${rawTableName}.grantedByTriggerOf IS NULL`) are NOT deleted.
|
||||||
|
-- 2. New role types will be created, but existing role types which are not specified anymore,
|
||||||
|
-- will NOT be deleted!
|
||||||
|
-- 3. All newly specified grants will be created.
|
||||||
|
--
|
||||||
|
-- IMPORTANT:
|
||||||
|
-- Make sure not to skip any previously defined role-types or you might break indirect grants!
|
||||||
|
-- E.g. If, in an updated version of the RBAC system for a table, you remove the AGENT role type
|
||||||
|
-- and now directly grant the TENANT role to the ADMIN role, all external grants to the AGENT role
|
||||||
|
-- of this table would be in a dead end.
|
||||||
|
|
||||||
|
create or replace procedure ${rawTableName}_rebuild_rbac_system()
|
||||||
|
language plpgsql as $$
|
||||||
|
DECLARE
|
||||||
|
DECLARE
|
||||||
|
row ${rawTableName};
|
||||||
|
grantsAfter numeric;
|
||||||
|
grantsBefore numeric;
|
||||||
|
BEGIN
|
||||||
|
SELECT count(*) INTO grantsBefore FROM rbac.grant;
|
||||||
|
|
||||||
|
FOR row IN SELECT * FROM ${rawTableName} LOOP
|
||||||
|
-- first delete all generated grants for this row from the previously defined RBAC system
|
||||||
|
DELETE FROM rbac.grant g
|
||||||
|
WHERE g.grantedbytriggerof = row.uuid;
|
||||||
|
|
||||||
|
-- then build the grants according to the currently defined RBAC rules
|
||||||
|
CALL ${rawTableName}_build_rbac_system(row);
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
select count(*) into grantsAfter from rbac.grant;
|
||||||
|
|
||||||
|
-- print how the total count of grants has changed
|
||||||
|
raise notice 'total grant count before -> after: % -> %', grantsBefore, grantsAfter;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
--//
|
||||||
|
|
||||||
|
""",
|
||||||
|
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
||||||
|
with("rawTableName", rawTableName));
|
||||||
|
}
|
||||||
|
}
|
@ -6,11 +6,11 @@ import static net.hostsharing.hsadminng.rbac.generator.StringWriter.indented;
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||||
|
|
||||||
public class RbacRestrictedViewGenerator {
|
public class RbacRestrictedViewGenerator {
|
||||||
private final RbacView rbacDef;
|
private final RbacSpec rbacDef;
|
||||||
private final String liquibaseTagPrefix;
|
private final String liquibaseTagPrefix;
|
||||||
private final String rawTableName;
|
private final String rawTableName;
|
||||||
|
|
||||||
public RbacRestrictedViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
public RbacRestrictedViewGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||||
this.rbacDef = rbacDef;
|
this.rbacDef = rbacDef;
|
||||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||||
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||||
|
@ -8,7 +8,7 @@ public class RbacRoleDescriptorsGenerator {
|
|||||||
private final String simpleEntityVarName;
|
private final String simpleEntityVarName;
|
||||||
private final String rawTableName;
|
private final String rawTableName;
|
||||||
|
|
||||||
public RbacRoleDescriptorsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
public RbacRoleDescriptorsGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||||
this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
||||||
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||||
|
@ -22,19 +22,18 @@ import static java.util.Arrays.asList;
|
|||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
import static java.util.Collections.max;
|
import static java.util.Collections.max;
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.ROLE_TO_ROLE;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.ROLE_TO_ROLE;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.Part.AUTO_FETCH;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.Part.AUTO_FETCH;
|
||||||
import static org.apache.commons.collections4.SetUtils.hashSet;
|
import static org.apache.commons.collections4.SetUtils.hashSet;
|
||||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
// TODO.refa: rename to RbacDSL
|
public class RbacSpec {
|
||||||
public class RbacView {
|
|
||||||
|
|
||||||
public static final String GLOBAL = "rbac.global";
|
public static final String GLOBAL = "rbac.global";
|
||||||
public static final String OUTPUT_BASEDIR = "src/main/resources/db/changelog";
|
public static final String OUTPUT_BASEDIR = "src/main/resources/db/changelog";
|
||||||
@ -90,11 +89,11 @@ public class RbacView {
|
|||||||
* @param <E>
|
* @param <E>
|
||||||
* a JPA entity class extending RbacObject
|
* a JPA entity class extending RbacObject
|
||||||
*/
|
*/
|
||||||
public static <E extends BaseEntity<?>> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
|
public static <E extends BaseEntity<?>> RbacSpec rbacViewFor(final String alias, final Class<E> entityClass) {
|
||||||
return new RbacView(alias, entityClass);
|
return new RbacSpec(alias, entityClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
RbacView(final String alias, final Class<? extends BaseEntity<?>> entityClass) {
|
RbacSpec(final String alias, final Class<? extends BaseEntity<?>> entityClass) {
|
||||||
rootEntityAlias = new EntityAlias(alias, entityClass);
|
rootEntityAlias = new EntityAlias(alias, entityClass);
|
||||||
entityAliases.put(alias, rootEntityAlias);
|
entityAliases.put(alias, rootEntityAlias);
|
||||||
new RbacSubjectReference(CREATOR);
|
new RbacSubjectReference(CREATOR);
|
||||||
@ -110,7 +109,7 @@ public class RbacView {
|
|||||||
* @return
|
* @return
|
||||||
* the `this` instance itself to allow chained calls.
|
* the `this` instance itself to allow chained calls.
|
||||||
*/
|
*/
|
||||||
public RbacView withUpdatableColumns(final String... columnNames) {
|
public RbacSpec withUpdatableColumns(final String... columnNames) {
|
||||||
Collections.addAll(updatableColumns, columnNames);
|
Collections.addAll(updatableColumns, columnNames);
|
||||||
verifyVersionColumnExists();
|
verifyVersionColumnExists();
|
||||||
return this;
|
return this;
|
||||||
@ -134,7 +133,7 @@ public class RbacView {
|
|||||||
* @return
|
* @return
|
||||||
* the `this` instance itself to allow chained calls.
|
* the `this` instance itself to allow chained calls.
|
||||||
*/
|
*/
|
||||||
public RbacView withIdentityView(final SQL sqlExpression) {
|
public RbacSpec withIdentityView(final SQL sqlExpression) {
|
||||||
this.identityViewSqlQuery = sqlExpression;
|
this.identityViewSqlQuery = sqlExpression;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -150,7 +149,7 @@ public class RbacView {
|
|||||||
* @return
|
* @return
|
||||||
* the `this` instance itself to allow chained calls.
|
* the `this` instance itself to allow chained calls.
|
||||||
*/
|
*/
|
||||||
public RbacView withRestrictedViewOrderBy(final SQL orderBySqlExpression) {
|
public RbacSpec withRestrictedViewOrderBy(final SQL orderBySqlExpression) {
|
||||||
this.orderBySqlExpression = orderBySqlExpression;
|
this.orderBySqlExpression = orderBySqlExpression;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -166,7 +165,7 @@ public class RbacView {
|
|||||||
* @return
|
* @return
|
||||||
* the `this` instance itself to allow chained calls.
|
* the `this` instance itself to allow chained calls.
|
||||||
*/
|
*/
|
||||||
public RbacView createRole(final Role role, final Consumer<RbacRoleDefinition> with) {
|
public RbacSpec createRole(final Role role, final Consumer<RbacRoleDefinition> with) {
|
||||||
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
||||||
with.accept(newRoleDef);
|
with.accept(newRoleDef);
|
||||||
previousRoleDef = newRoleDef;
|
previousRoleDef = newRoleDef;
|
||||||
@ -182,7 +181,7 @@ public class RbacView {
|
|||||||
* @return
|
* @return
|
||||||
* the `this` instance itself to allow chained calls.
|
* the `this` instance itself to allow chained calls.
|
||||||
*/
|
*/
|
||||||
public RbacView createSubRole(final Role role) {
|
public RbacSpec createSubRole(final Role role) {
|
||||||
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
||||||
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
|
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
|
||||||
previousRoleDef = newRoleDef;
|
previousRoleDef = newRoleDef;
|
||||||
@ -202,7 +201,7 @@ public class RbacView {
|
|||||||
* @return
|
* @return
|
||||||
* the `this` instance itself to allow chained calls.
|
* the `this` instance itself to allow chained calls.
|
||||||
*/
|
*/
|
||||||
public RbacView createSubRole(final Role role, final Consumer<RbacRoleDefinition> with) {
|
public RbacSpec createSubRole(final Role role, final Consumer<RbacRoleDefinition> with) {
|
||||||
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
||||||
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
|
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
|
||||||
with.accept(newRoleDef);
|
with.accept(newRoleDef);
|
||||||
@ -254,7 +253,7 @@ public class RbacView {
|
|||||||
.orElseGet(() -> new RbacPermissionDefinition(entityAlias, permission, null, true));
|
.orElseGet(() -> new RbacPermissionDefinition(entityAlias, permission, null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public <EC extends BaseEntity> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
|
public <EC extends BaseEntity> RbacSpec declarePlaceholderEntityAliases(final String... aliasNames) {
|
||||||
for (String alias : aliasNames) {
|
for (String alias : aliasNames) {
|
||||||
entityAliases.put(alias, new EntityAlias(alias));
|
entityAliases.put(alias, new EntityAlias(alias));
|
||||||
}
|
}
|
||||||
@ -287,7 +286,7 @@ public class RbacView {
|
|||||||
* @param <EC>
|
* @param <EC>
|
||||||
* a JPA entity class extending RbacObject
|
* a JPA entity class extending RbacObject
|
||||||
*/
|
*/
|
||||||
public <EC extends BaseEntity<?>> RbacView importRootEntityAliasProxy(
|
public <EC extends BaseEntity<?>> RbacSpec importRootEntityAliasProxy(
|
||||||
final String aliasName,
|
final String aliasName,
|
||||||
final Class<? extends BaseEntity<?>> entityClass,
|
final Class<? extends BaseEntity<?>> entityClass,
|
||||||
final ColumnValue forCase,
|
final ColumnValue forCase,
|
||||||
@ -312,7 +311,7 @@ public class RbacView {
|
|||||||
* @param <EC>
|
* @param <EC>
|
||||||
* a JPA entity class extending RbacObject
|
* a JPA entity class extending RbacObject
|
||||||
*/
|
*/
|
||||||
public RbacView importSubEntityAlias(
|
public RbacSpec importSubEntityAlias(
|
||||||
final String aliasName, final Class<? extends BaseEntity<?>> entityClass,
|
final String aliasName, final Class<? extends BaseEntity<?>> entityClass,
|
||||||
final SQL fetchSql, final Column dependsOnColum) {
|
final SQL fetchSql, final Column dependsOnColum) {
|
||||||
importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, true, NOT_NULL);
|
importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, true, NOT_NULL);
|
||||||
@ -349,7 +348,7 @@ public class RbacView {
|
|||||||
* @param <EC>
|
* @param <EC>
|
||||||
* a JPA entity class extending RbacObject
|
* a JPA entity class extending RbacObject
|
||||||
*/
|
*/
|
||||||
public RbacView importEntityAlias(
|
public RbacSpec importEntityAlias(
|
||||||
final String aliasName, final Class<? extends BaseEntity<?>> entityClass, final ColumnValue usingCase,
|
final String aliasName, final Class<? extends BaseEntity<?>> entityClass, final ColumnValue usingCase,
|
||||||
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
|
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
|
||||||
importEntityAliasImpl(aliasName, entityClass, usingCase, fetchSql, dependsOnColum, false, nullable);
|
importEntityAliasImpl(aliasName, entityClass, usingCase, fetchSql, dependsOnColum, false, nullable);
|
||||||
@ -379,12 +378,12 @@ public class RbacView {
|
|||||||
return entityAlias;
|
return entityAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RbacView rbacDefinition(final Class<? extends BaseEntity> entityClass)
|
private static RbacSpec rbacDefinition(final Class<? extends BaseEntity> entityClass)
|
||||||
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
|
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
|
||||||
return (RbacView) entityClass.getMethod("rbac").invoke(null);
|
return (RbacSpec) entityClass.getMethod("rbac").invoke(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RbacView importAsAlias(final String aliasName, final RbacView importedRbacView, final ColumnValue forCase, final boolean asSubEntity) {
|
private RbacSpec importAsAlias(final String aliasName, final RbacSpec importedRbacView, final ColumnValue forCase, final boolean asSubEntity) {
|
||||||
final var mapper = new AliasNameMapper(importedRbacView, aliasName,
|
final var mapper = new AliasNameMapper(importedRbacView, aliasName,
|
||||||
asSubEntity ? entityAliases.keySet() : null);
|
asSubEntity ? entityAliases.keySet() : null);
|
||||||
copyOf(importedRbacView.getEntityAliases().values()).stream()
|
copyOf(importedRbacView.getEntityAliases().values()).stream()
|
||||||
@ -416,7 +415,7 @@ public class RbacView {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacView switchOnColumn(final String discriminatorColumName, final CaseDef... caseDefs) {
|
public RbacSpec switchOnColumn(final String discriminatorColumName, final CaseDef... caseDefs) {
|
||||||
this.discriminatorColumName = discriminatorColumName;
|
this.discriminatorColumName = discriminatorColumName;
|
||||||
allCases.addAll(stream(caseDefs).toList());
|
allCases.addAll(stream(caseDefs).toList());
|
||||||
|
|
||||||
@ -511,7 +510,7 @@ public class RbacView {
|
|||||||
new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + ".sql"));
|
new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + ".sql"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacView limitDiagramTo(final String... aliasNames) {
|
public RbacSpec limitDiagramTo(final String... aliasNames) {
|
||||||
this.limitDiagramToAliasNames = Set.of(aliasNames);
|
this.limitDiagramToAliasNames = Set.of(aliasNames);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -542,15 +541,15 @@ public class RbacView {
|
|||||||
this.superRoleDef = findRbacRole(entityAlias, role);
|
this.superRoleDef = findRbacRole(entityAlias, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacView grantRole(final String entityAlias, final Role role) {
|
public RbacSpec grantRole(final String entityAlias, final Role role) {
|
||||||
findOrCreateGrantDef(findRbacRole(entityAlias, role), superRoleDef).toCreate();
|
findOrCreateGrantDef(findRbacRole(entityAlias, role), superRoleDef).toCreate();
|
||||||
return RbacView.this;
|
return RbacSpec.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacView grantPermission(final Permission perm) {
|
public RbacSpec grantPermission(final Permission perm) {
|
||||||
final var forTable = rootEntityAlias.getRawTableNameWithSchema();
|
final var forTable = rootEntityAlias.getRawTableNameWithSchema();
|
||||||
findOrCreateGrantDef(findRbacPerm(rootEntityAlias, perm, forTable), superRoleDef).toCreate();
|
findOrCreateGrantDef(findRbacPerm(rootEntityAlias, perm, forTable), superRoleDef).toCreate();
|
||||||
return RbacView.this;
|
return RbacSpec.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -698,10 +697,10 @@ public class RbacView {
|
|||||||
this.subRole = role;
|
this.subRole = role;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacView wouldBeGrantedTo(final String entityAlias, final Role role) {
|
public RbacSpec wouldBeGrantedTo(final String entityAlias, final Role role) {
|
||||||
this.superRoleEntity = findEntityAlias(entityAlias);
|
this.superRoleEntity = findEntityAlias(entityAlias);
|
||||||
this.superRole = role;
|
this.superRole = role;
|
||||||
return RbacView.this;
|
return RbacSpec.this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,9 +732,9 @@ public class RbacView {
|
|||||||
* @return
|
* @return
|
||||||
* The RbacView specification to which this permission definition belongs.
|
* The RbacView specification to which this permission definition belongs.
|
||||||
*/
|
*/
|
||||||
public RbacView grantedTo(final String entityAlias, final Role role) {
|
public RbacSpec grantedTo(final String entityAlias, final Role role) {
|
||||||
findOrCreateGrantDef(this, findRbacRole(entityAlias, role)).toCreate();
|
findOrCreateGrantDef(this, findRbacRole(entityAlias, role)).toCreate();
|
||||||
return RbacView.this;
|
return RbacSpec.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1186,12 +1185,12 @@ public class RbacView {
|
|||||||
|
|
||||||
private static class AliasNameMapper {
|
private static class AliasNameMapper {
|
||||||
|
|
||||||
private final RbacView importedRbacView;
|
private final RbacSpec importedRbacView;
|
||||||
private final String outerAliasName;
|
private final String outerAliasName;
|
||||||
|
|
||||||
private final Set<String> outerAliasNames;
|
private final Set<String> outerAliasNames;
|
||||||
|
|
||||||
AliasNameMapper(final RbacView importedRbacView, final String outerAliasName, final Set<String> outerAliasNames) {
|
AliasNameMapper(final RbacSpec importedRbacView, final String outerAliasName, final Set<String> outerAliasNames) {
|
||||||
this.importedRbacView = importedRbacView;
|
this.importedRbacView = importedRbacView;
|
||||||
this.outerAliasName = outerAliasName;
|
this.outerAliasName = outerAliasName;
|
||||||
this.outerAliasNames = (outerAliasNames == null) ? Collections.emptySet() : outerAliasNames;
|
this.outerAliasNames = (outerAliasNames == null) ? Collections.emptySet() : outerAliasNames;
|
||||||
@ -1210,19 +1209,19 @@ public class RbacView {
|
|||||||
|
|
||||||
public static class CaseDef extends ColumnValue {
|
public static class CaseDef extends ColumnValue {
|
||||||
|
|
||||||
final Consumer<RbacView> def;
|
final Consumer<RbacSpec> def;
|
||||||
|
|
||||||
private CaseDef(final String discriminatorColumnValue, final Consumer<RbacView> def) {
|
private CaseDef(final String discriminatorColumnValue, final Consumer<RbacSpec> def) {
|
||||||
super(discriminatorColumnValue);
|
super(discriminatorColumnValue);
|
||||||
this.def = def;
|
this.def = def;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static CaseDef inCaseOf(final String discriminatorColumnValue, final Consumer<RbacView> def) {
|
public static CaseDef inCaseOf(final String discriminatorColumnValue, final Consumer<RbacSpec> def) {
|
||||||
return new CaseDef(discriminatorColumnValue, def);
|
return new CaseDef(discriminatorColumnValue, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CaseDef inOtherCases(final Consumer<RbacView> def) {
|
public static CaseDef inOtherCases(final Consumer<RbacSpec> def) {
|
||||||
return new CaseDef(null, def);
|
return new CaseDef(null, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1281,7 +1280,7 @@ public class RbacView {
|
|||||||
.filter(c -> stream(c.getDeclaredMethods())
|
.filter(c -> stream(c.getDeclaredMethods())
|
||||||
.anyMatch(m -> m.getName().equals("rbac") && isStatic(m.getModifiers()))
|
.anyMatch(m -> m.getName().equals("rbac") && isStatic(m.getModifiers()))
|
||||||
)
|
)
|
||||||
.map(RbacView::castToSubclassOfBaseEntity)
|
.map(RbacSpec::castToSubclassOfBaseEntity)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
return rbacEntityClasses;
|
return rbacEntityClasses;
|
||||||
}
|
}
|
||||||
@ -1296,6 +1295,6 @@ public class RbacView {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
findRbacEntityClasses("net.hostsharing.hsadminng")
|
findRbacEntityClasses("net.hostsharing.hsadminng")
|
||||||
.forEach(RbacView::generateRbacView);
|
.forEach(RbacSpec::generateRbacView);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.generator;
|
package net.hostsharing.hsadminng.rbac.generator;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
@ -12,7 +12,7 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.*;
|
||||||
|
|
||||||
public class RbacViewMermaidFlowchartGenerator {
|
public class RbacViewMermaidFlowchartGenerator {
|
||||||
|
|
||||||
@ -20,14 +20,14 @@ public class RbacViewMermaidFlowchartGenerator {
|
|||||||
public static final String HOSTSHARING_LIGHT_ORANGE = "#feb28c";
|
public static final String HOSTSHARING_LIGHT_ORANGE = "#feb28c";
|
||||||
public static final String HOSTSHARING_DARK_BLUE = "#274d6e";
|
public static final String HOSTSHARING_DARK_BLUE = "#274d6e";
|
||||||
public static final String HOSTSHARING_LIGHT_BLUE = "#99bcdb";
|
public static final String HOSTSHARING_LIGHT_BLUE = "#99bcdb";
|
||||||
private final RbacView rbacDef;
|
private final RbacSpec rbacDef;
|
||||||
|
|
||||||
private final List<RbacView.EntityAlias> usedEntityAliases;
|
private final List<RbacSpec.EntityAlias> usedEntityAliases;
|
||||||
|
|
||||||
private final CaseDef forCase;
|
private final CaseDef forCase;
|
||||||
private final StringWriter flowchart = new StringWriter();
|
private final StringWriter flowchart = new StringWriter();
|
||||||
|
|
||||||
public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef, final CaseDef forCase) {
|
public RbacViewMermaidFlowchartGenerator(final RbacSpec rbacDef, final CaseDef forCase) {
|
||||||
this.rbacDef = rbacDef;
|
this.rbacDef = rbacDef;
|
||||||
this.forCase = forCase;
|
this.forCase = forCase;
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
|||||||
g.getSubRoleDef() != null ? g.getSubRoleDef().getEntityAlias() : null,
|
g.getSubRoleDef() != null ? g.getSubRoleDef().getEntityAlias() : null,
|
||||||
g.getPermDef() != null ? g.getPermDef().getEntityAlias() : null))
|
g.getPermDef() != null ? g.getPermDef().getEntityAlias() : null))
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.sorted(comparing(RbacView.EntityAlias::aliasName))
|
.sorted(comparing(RbacSpec.EntityAlias::aliasName))
|
||||||
.distinct()
|
.distinct()
|
||||||
.filter(rbacDef::renderInDiagram)
|
.filter(rbacDef::renderInDiagram)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
@ -50,7 +50,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
|||||||
renderGrants();
|
renderGrants();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef) {
|
public RbacViewMermaidFlowchartGenerator(final RbacSpec rbacDef) {
|
||||||
this(rbacDef, null);
|
this(rbacDef, null);
|
||||||
}
|
}
|
||||||
private void renderEntitySubgraphs() {
|
private void renderEntitySubgraphs() {
|
||||||
@ -61,7 +61,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
|||||||
.forEach(this::renderEntitySubgraph);
|
.forEach(this::renderEntitySubgraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderEntitySubgraph(final RbacView.EntityAlias entity) {
|
private void renderEntitySubgraph(final RbacSpec.EntityAlias entity) {
|
||||||
if (!rbacDef.renderInDiagram(entity)) {
|
if (!rbacDef.renderInDiagram(entity)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
|||||||
renderGrants(PERM_TO_ROLE, "%% granting permissions to roles");
|
renderGrants(PERM_TO_ROLE, "%% granting permissions to roles");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderGrants(final RbacView.RbacGrantDefinition.GrantType grantType, final String comment) {
|
private void renderGrants(final RbacSpec.RbacGrantDefinition.GrantType grantType, final String comment) {
|
||||||
final var grantsOfRequestedType = rbacDef.getGrantDefs().stream()
|
final var grantsOfRequestedType = rbacDef.getGrantDefs().stream()
|
||||||
.filter(g -> g.grantType() == grantType)
|
.filter(g -> g.grantType() == grantType)
|
||||||
.filter(rbacDef::renderInDiagram)
|
.filter(rbacDef::renderInDiagram)
|
||||||
@ -141,7 +141,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isToBeRenderedForThisCase(final RbacView.RbacGrantDefinition g) {
|
private boolean isToBeRenderedForThisCase(final RbacSpec.RbacGrantDefinition g) {
|
||||||
if ( g.grantType() == ROLE_TO_USER )
|
if ( g.grantType() == ROLE_TO_USER )
|
||||||
return true;
|
return true;
|
||||||
if ( forCase == null && !g.isConditional() )
|
if ( forCase == null && !g.isConditional() )
|
||||||
@ -150,7 +150,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
|||||||
return isToBeRenderedInThisGraph;
|
return isToBeRenderedInThisGraph;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String grantDef(final RbacView.RbacGrantDefinition grant) {
|
private String grantDef(final RbacSpec.RbacGrantDefinition grant) {
|
||||||
final var arrow = (grant.isToCreate() ? " ==>" : " -.->")
|
final var arrow = (grant.isToCreate() ? " ==>" : " -.->")
|
||||||
+ (grant.isAssumed() ? " " : "|XX| ");
|
+ (grant.isAssumed() ? " " : "|XX| ");
|
||||||
final var grantDef = switch (grant.grantType()) {
|
final var grantDef = switch (grant.grantType()) {
|
||||||
@ -164,19 +164,19 @@ public class RbacViewMermaidFlowchartGenerator {
|
|||||||
return grantDef;
|
return grantDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String permDef(final RbacView.RbacPermissionDefinition perm) {
|
private String permDef(final RbacSpec.RbacPermissionDefinition perm) {
|
||||||
return permId(perm) + "{{" + perm.getEntityAlias().aliasName() + perm.getPermission() + "}}";
|
return permId(perm) + "{{" + perm.getEntityAlias().aliasName() + perm.getPermission() + "}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String permId(final RbacView.RbacPermissionDefinition permDef) {
|
private static String permId(final RbacSpec.RbacPermissionDefinition permDef) {
|
||||||
return "perm:" + permDef.getEntityAlias().aliasName() + permDef.getPermission();
|
return "perm:" + permDef.getEntityAlias().aliasName() + permDef.getPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String roleDef(final RbacView.RbacRoleDefinition roleDef) {
|
private String roleDef(final RbacSpec.RbacRoleDefinition roleDef) {
|
||||||
return roleId(roleDef) + "[[" + roleDef.getEntityAlias().aliasName() + roleDef.getRole() + "]]";
|
return roleId(roleDef) + "[[" + roleDef.getEntityAlias().aliasName() + roleDef.getRole() + "]]";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String roleId(final RbacView.RbacRoleDefinition r) {
|
private static String roleId(final RbacSpec.RbacRoleDefinition r) {
|
||||||
return "role:" + r.getEntityAlias().aliasName() + r.getRole();
|
return "role:" + r.getEntityAlias().aliasName() + r.getRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,11 @@ import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
|||||||
|
|
||||||
public class RbacViewPostgresGenerator {
|
public class RbacViewPostgresGenerator {
|
||||||
|
|
||||||
private final RbacView rbacDef;
|
private final RbacSpec rbacDef;
|
||||||
private final String liqibaseTagPrefix;
|
private final String liqibaseTagPrefix;
|
||||||
private final StringWriter plPgSql = new StringWriter();
|
private final StringWriter plPgSql = new StringWriter();
|
||||||
|
|
||||||
public RbacViewPostgresGenerator(final RbacView forRbacDef) {
|
public RbacViewPostgresGenerator(final RbacSpec forRbacDef) {
|
||||||
rbacDef = forRbacDef;
|
rbacDef = forRbacDef;
|
||||||
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableNameWithSchema().replace("_", "-").replace(".", "-");
|
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableNameWithSchema().replace("_", "-").replace(".", "-");
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
@ -31,6 +31,7 @@ public class RbacViewPostgresGenerator {
|
|||||||
new InsertTriggerGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
new InsertTriggerGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
new RbacIdentityViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
new RbacIdentityViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
new RbacRestrictedViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
new RbacRestrictedViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
|
new RbacRbacSystemRebuildGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.generator;
|
package net.hostsharing.hsadminng.rbac.generator;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.RbacPermissionDefinition;
|
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacPermissionDefinition;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -15,22 +15,22 @@ import static java.util.stream.Collectors.joining;
|
|||||||
import static java.util.stream.Collectors.toSet;
|
import static java.util.stream.Collectors.toSet;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.NEW;
|
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.NEW;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.OLD;
|
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.OLD;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||||
|
|
||||||
class RolesGrantsAndPermissionsGenerator {
|
class RolesGrantsAndPermissionsGenerator {
|
||||||
|
|
||||||
private final RbacView rbacDef;
|
private final RbacSpec rbacDef;
|
||||||
private final Set<RbacGrantDefinition> rbacGrants = new HashSet<>();
|
private final Set<RbacGrantDefinition> rbacGrants = new HashSet<>();
|
||||||
private final String liquibaseTagPrefix;
|
private final String liquibaseTagPrefix;
|
||||||
private final String simpleEntityName;
|
private final String simpleEntityName;
|
||||||
private final String simpleEntityVarName;
|
private final String simpleEntityVarName;
|
||||||
private final String qualifiedRawTableName;
|
private final String qualifiedRawTableName;
|
||||||
|
|
||||||
RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
RolesGrantsAndPermissionsGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||||
this.rbacDef = rbacDef;
|
this.rbacDef = rbacDef;
|
||||||
this.rbacGrants.addAll(rbacDef.getGrantDefs().stream()
|
this.rbacGrants.addAll(rbacDef.getGrantDefs().stream()
|
||||||
.filter(RbacGrantDefinition::isToCreate)
|
.filter(RbacGrantDefinition::isToCreate)
|
||||||
@ -95,7 +95,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
private void generateSimplifiedUpdateTriggerFunction(final StringWriter plPgSql) {
|
private void generateSimplifiedUpdateTriggerFunction(final StringWriter plPgSql) {
|
||||||
|
|
||||||
final var updateConditions = updatableEntityAliases()
|
final var updateConditions = updatableEntityAliases()
|
||||||
.map(RbacView.EntityAlias::dependsOnColumName)
|
.map(RbacSpec.EntityAlias::dependsOnColumName)
|
||||||
.distinct()
|
.distinct()
|
||||||
.map(columnName -> "NEW." + columnName + " is distinct from OLD." + columnName)
|
.map(columnName -> "NEW." + columnName + " is distinct from OLD." + columnName)
|
||||||
.collect(joining( "\n or "));
|
.collect(joining( "\n or "));
|
||||||
@ -112,7 +112,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
begin
|
begin
|
||||||
|
|
||||||
if ${updateConditions} then
|
if ${updateConditions} then
|
||||||
delete from rbac.grants g where g.grantedbytriggerof = OLD.uuid;
|
delete from rbac.grant g where g.grantedbytriggerof = OLD.uuid;
|
||||||
call ${rawTableQualifiedName}_build_rbac_system(NEW);
|
call ${rawTableQualifiedName}_build_rbac_system(NEW);
|
||||||
end if;
|
end if;
|
||||||
end; $$;
|
end; $$;
|
||||||
@ -166,7 +166,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
private boolean hasAnyUpdatableAndNullableEntityAliases() {
|
private boolean hasAnyUpdatableAndNullableEntityAliases() {
|
||||||
return updatableEntityAliases()
|
return updatableEntityAliases()
|
||||||
.filter(ea -> ea.nullable() == RbacView.Nullable.NULLABLE)
|
.filter(ea -> ea.nullable() == RbacSpec.Nullable.NULLABLE)
|
||||||
.anyMatch(e -> true);
|
.anyMatch(e -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +210,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
generateGrants(plPgSql, PERM_TO_ROLE);
|
generateGrants(plPgSql, PERM_TO_ROLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<RbacView.EntityAlias> referencedEntityAliases() {
|
private Stream<RbacSpec.EntityAlias> referencedEntityAliases() {
|
||||||
return rbacDef.getEntityAliases().values().stream()
|
return rbacDef.getEntityAliases().values().stream()
|
||||||
.filter(ea -> !rbacDef.isRootEntityAlias(ea))
|
.filter(ea -> !rbacDef.isRootEntityAlias(ea))
|
||||||
.filter(ea -> ea.dependsOnColum() != null)
|
.filter(ea -> ea.dependsOnColum() != null)
|
||||||
@ -218,7 +218,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
.filter(ea -> ea.fetchSql() != null);
|
.filter(ea -> ea.fetchSql() != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<RbacView.EntityAlias> updatableEntityAliases() {
|
private Stream<RbacSpec.EntityAlias> updatableEntityAliases() {
|
||||||
return referencedEntityAliases()
|
return referencedEntityAliases()
|
||||||
.filter(ea -> rbacDef.getUpdatableColumns().contains(ea.dependsOnColum().column));
|
.filter(ea -> rbacDef.getUpdatableColumns().contains(ea.dependsOnColum().column));
|
||||||
}
|
}
|
||||||
@ -234,7 +234,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
});
|
});
|
||||||
|
|
||||||
updatableEntityAliases()
|
updatableEntityAliases()
|
||||||
.map(RbacView.EntityAlias::dependsOnColum)
|
.map(RbacSpec.EntityAlias::dependsOnColum)
|
||||||
.map(c -> c.column)
|
.map(c -> c.column)
|
||||||
.sorted()
|
.sorted()
|
||||||
.distinct()
|
.distinct()
|
||||||
@ -250,18 +250,19 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
private void generateFetchedVars(
|
private void generateFetchedVars(
|
||||||
final StringWriter plPgSql,
|
final StringWriter plPgSql,
|
||||||
final RbacView.EntityAlias ea,
|
final RbacSpec.EntityAlias ea,
|
||||||
final PostgresTriggerReference old) {
|
final PostgresTriggerReference old) {
|
||||||
plPgSql.writeLn(
|
plPgSql.writeLn(
|
||||||
ea.fetchSql().sql + " INTO " + entityRefVar(old, ea) + ";",
|
ea.fetchSql().sql + " INTO " + entityRefVar(old, ea) + ";",
|
||||||
with("columns", ea.aliasName() + ".*"),
|
with("columns", ea.aliasName() + ".*"),
|
||||||
with("ref", old.name()));
|
with("ref", old.name()));
|
||||||
if (ea.nullable() == RbacView.Nullable.NOT_NULL) {
|
if (ea.nullable() == RbacSpec.Nullable.NOT_NULL) {
|
||||||
plPgSql.writeLn(
|
plPgSql.writeLn(
|
||||||
"assert ${entityRefVar}.uuid is not null, format('${entityRefVar} must not be null for ${REF}.${dependsOnColumn} = %s', ${REF}.${dependsOnColumn});",
|
"assert ${entityRefVar}.uuid is not null, format('${entityRefVar} must not be null for ${REF}.${dependsOnColumn} = %s of ${rawTable}', ${REF}.${dependsOnColumn});",
|
||||||
with("entityRefVar", entityRefVar(old, ea)),
|
with("entityRefVar", entityRefVar(old, ea)),
|
||||||
with("dependsOnColumn", ea.dependsOnColumName()),
|
with("dependsOnColumn", ea.dependsOnColumName()),
|
||||||
with("ref", old.name()));
|
with("ref", old.name()),
|
||||||
|
with("rawTable", qualifiedRawTableName));
|
||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -352,11 +353,11 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
.replace("${perm}", permDef.permission.name());
|
.replace("${perm}", permDef.permission.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String refVarName(final PostgresTriggerReference ref, final RbacView.EntityAlias entityAlias) {
|
private String refVarName(final PostgresTriggerReference ref, final RbacSpec.EntityAlias entityAlias) {
|
||||||
return ref.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
return ref.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) {
|
private String roleRef(final PostgresTriggerReference rootRefVar, final RbacSpec.RbacRoleDefinition roleDef) {
|
||||||
if (roleDef == null) {
|
if (roleDef == null) {
|
||||||
System.out.println("null");
|
System.out.println("null");
|
||||||
}
|
}
|
||||||
@ -369,17 +370,17 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
private String entityRefVar(
|
private String entityRefVar(
|
||||||
final PostgresTriggerReference rootRefVar,
|
final PostgresTriggerReference rootRefVar,
|
||||||
final RbacView.EntityAlias entityAlias) {
|
final RbacSpec.EntityAlias entityAlias) {
|
||||||
return rbacDef.isRootEntityAlias(entityAlias)
|
return rbacDef.isRootEntityAlias(entityAlias)
|
||||||
? rootRefVar.name()
|
? rootRefVar.name()
|
||||||
: rootRefVar.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
: rootRefVar.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) {
|
private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||||
|
|
||||||
final var isToCreate = rbacDef.getRoleDefs().stream()
|
final var isToCreate = rbacDef.getRoleDefs().stream()
|
||||||
.filter(roleDef -> rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) && roleDef.getRole() == role)
|
.filter(roleDef -> rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) && roleDef.getRole() == role)
|
||||||
.findFirst().map(RbacView.RbacRoleDefinition::isToCreate).orElse(false);
|
.findFirst().map(RbacSpec.RbacRoleDefinition::isToCreate).orElse(false);
|
||||||
if (!isToCreate) {
|
if (!isToCreate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -403,7 +404,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
plPgSql.writeLn(");");
|
plPgSql.writeLn(");");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateUserGrantsForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
private void generateUserGrantsForRole(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||||
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
|
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
|
||||||
if (!grantsToUsers.isEmpty()) {
|
if (!grantsToUsers.isEmpty()) {
|
||||||
final var arrayElements = grantsToUsers.stream()
|
final var arrayElements = grantsToUsers.stream()
|
||||||
@ -416,13 +417,13 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generatePermissionsForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
private void generatePermissionsForRole(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||||
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
|
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
|
||||||
if (!permissionGrantsForRole.isEmpty()) {
|
if (!permissionGrantsForRole.isEmpty()) {
|
||||||
final var arrayElements = permissionGrantsForRole.stream()
|
final var arrayElements = permissionGrantsForRole.stream()
|
||||||
.map(RbacGrantDefinition::getPermDef)
|
.map(RbacGrantDefinition::getPermDef)
|
||||||
.map(RbacPermissionDefinition::getPermission)
|
.map(RbacPermissionDefinition::getPermission)
|
||||||
.map(RbacView.Permission::name)
|
.map(RbacSpec.Permission::name)
|
||||||
.map(p -> "'" + p + "'")
|
.map(p -> "'" + p + "'")
|
||||||
.sorted()
|
.sorted()
|
||||||
.toList();
|
.toList();
|
||||||
@ -432,7 +433,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||||
final var unconditionalIncomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
|
final var unconditionalIncomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
|
||||||
.filter(g -> !g.isConditional())
|
.filter(g -> !g.isConditional())
|
||||||
.toList();
|
.toList();
|
||||||
@ -446,7 +447,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||||
final var unconditionalOutgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
|
final var unconditionalOutgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
|
||||||
.filter(g -> !g.isConditional())
|
.filter(g -> !g.isConditional())
|
||||||
.toList();
|
.toList();
|
||||||
@ -467,8 +468,8 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Set<RbacGrantDefinition> findPermissionsGrantsForRole(
|
private Set<RbacGrantDefinition> findPermissionsGrantsForRole(
|
||||||
final RbacView.EntityAlias entityAlias,
|
final RbacSpec.EntityAlias entityAlias,
|
||||||
final RbacView.Role role) {
|
final RbacSpec.Role role) {
|
||||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||||
return rbacGrants.stream()
|
return rbacGrants.stream()
|
||||||
.filter(g -> g.grantType() == PERM_TO_ROLE && g.getSuperRoleDef() == roleDef)
|
.filter(g -> g.grantType() == PERM_TO_ROLE && g.getSuperRoleDef() == roleDef)
|
||||||
@ -476,8 +477,8 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Set<RbacGrantDefinition> findGrantsToUserForRole(
|
private Set<RbacGrantDefinition> findGrantsToUserForRole(
|
||||||
final RbacView.EntityAlias entityAlias,
|
final RbacSpec.EntityAlias entityAlias,
|
||||||
final RbacView.Role role) {
|
final RbacSpec.Role role) {
|
||||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||||
return rbacGrants.stream()
|
return rbacGrants.stream()
|
||||||
.filter(g -> g.grantType() == ROLE_TO_USER && g.getSubRoleDef() == roleDef)
|
.filter(g -> g.grantType() == ROLE_TO_USER && g.getSubRoleDef() == roleDef)
|
||||||
@ -485,8 +486,8 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Set<RbacGrantDefinition> findIncomingSuperRolesForRole(
|
private Set<RbacGrantDefinition> findIncomingSuperRolesForRole(
|
||||||
final RbacView.EntityAlias entityAlias,
|
final RbacSpec.EntityAlias entityAlias,
|
||||||
final RbacView.Role role) {
|
final RbacSpec.Role role) {
|
||||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||||
return rbacGrants.stream()
|
return rbacGrants.stream()
|
||||||
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSubRoleDef() == roleDef)
|
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSubRoleDef() == roleDef)
|
||||||
@ -494,8 +495,8 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Set<RbacGrantDefinition> findOutgoingSuperRolesForRole(
|
private Set<RbacGrantDefinition> findOutgoingSuperRolesForRole(
|
||||||
final RbacView.EntityAlias entityAlias,
|
final RbacSpec.EntityAlias entityAlias,
|
||||||
final RbacView.Role role) {
|
final RbacSpec.Role role) {
|
||||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||||
return rbacGrants.stream()
|
return rbacGrants.stream()
|
||||||
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSuperRoleDef() == roleDef)
|
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSuperRoleDef() == roleDef)
|
||||||
@ -579,7 +580,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toPlPgSqlReference(final RbacView.RbacSubjectReference userRef) {
|
private String toPlPgSqlReference(final RbacSpec.RbacSubjectReference userRef) {
|
||||||
return switch (userRef.role) {
|
return switch (userRef.role) {
|
||||||
case CREATOR -> "rbac.currentSubjectUuid()";
|
case CREATOR -> "rbac.currentSubjectUuid()";
|
||||||
default -> throw new IllegalArgumentException("unknown user role: " + userRef);
|
default -> throw new IllegalArgumentException("unknown user role: " + userRef);
|
||||||
@ -588,7 +589,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
private String toPlPgSqlReference(
|
private String toPlPgSqlReference(
|
||||||
final PostgresTriggerReference triggerRef,
|
final PostgresTriggerReference triggerRef,
|
||||||
final RbacView.RbacRoleDefinition roleDef,
|
final RbacSpec.RbacRoleDefinition roleDef,
|
||||||
final boolean assumed) {
|
final boolean assumed) {
|
||||||
final var assumedArg = assumed ? "" : ", rbac.unassumed()";
|
final var assumedArg = assumed ? "" : ", rbac.unassumed()";
|
||||||
return roleDef.descriptorFunctionName() +
|
return roleDef.descriptorFunctionName() +
|
||||||
@ -599,7 +600,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
private static String toTriggerReference(
|
private static String toTriggerReference(
|
||||||
final PostgresTriggerReference triggerRef,
|
final PostgresTriggerReference triggerRef,
|
||||||
final RbacView.EntityAlias entityAlias) {
|
final RbacSpec.EntityAlias entityAlias) {
|
||||||
return triggerRef.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
return triggerRef.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "rbac", name = "grants_ev")
|
@Table(schema = "rbac", name = "grant_ev")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
|
@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.grant;
|
|||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
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.api.RbacGrantsApi;
|
||||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource;
|
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -23,7 +23,7 @@ public class RbacGrantController implements RbacGrantsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StandardMapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RbacGrantRepository rbacGrantRepository;
|
private RbacGrantRepository rbacGrantRepository;
|
||||||
|
@ -8,7 +8,7 @@ import jakarta.persistence.*;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "rbac", name = "grants_rv")
|
@Table(schema = "rbac", name = "grant_rv")
|
||||||
@IdClass(RbacGrantId.class)
|
@IdClass(RbacGrantId.class)
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
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