Compare commits
11 Commits
ec53934f30
...
650ae365e5
Author | SHA1 | Date | |
---|---|---|---|
|
650ae365e5 | ||
|
d97827c01e | ||
|
e9dde69c40 | ||
|
7f98bbde64 | ||
|
ea130581a3 | ||
|
f8de575b77 | ||
|
d2f9f0ae8f | ||
|
4c44f42b79 | ||
|
73f147c557 | ||
|
378e1ec584 | ||
994a0e13c0 |
34
README.md
34
README.md
@ -20,6 +20,7 @@ For architecture consider the files in the `doc` and `adr` folder.
|
|||||||
- [Directory and Package Structure](#directory-and-package-structure)
|
- [Directory and Package Structure](#directory-and-package-structure)
|
||||||
- [General Directory Structure](#general-directory-structure)
|
- [General Directory Structure](#general-directory-structure)
|
||||||
- [Source Code Package Structure](#source-code-package-structure)
|
- [Source Code Package Structure](#source-code-package-structure)
|
||||||
|
- [Run Tests from Command Line](#run-tests-from-command-line)
|
||||||
- [Spotless Code Formatting](#spotless-code-formatting)
|
- [Spotless Code Formatting](#spotless-code-formatting)
|
||||||
- [JaCoCo Test Code Coverage Check](#jacoco-test-code-coverage-check)
|
- [JaCoCo Test Code Coverage Check](#jacoco-test-code-coverage-check)
|
||||||
- [PiTest Mutation Testing](#pitest-mutation-testing)
|
- [PiTest Mutation Testing](#pitest-mutation-testing)
|
||||||
@ -39,6 +40,7 @@ For architecture consider the files in the `doc` and `adr` folder.
|
|||||||
- [How to Use a Persistent Database for Integration Tests?](#how-to-use-a-persistent-database-for-integration-tests?)
|
- [How to Use a Persistent Database for Integration Tests?](#how-to-use-a-persistent-database-for-integration-tests?)
|
||||||
- [How to Amend Liquibase SQL Changesets?](#how-to-amend-liquibase-sql-changesets?)
|
- [How to 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?)
|
||||||
- [Further Documentation](#further-documentation)
|
- [Further Documentation](#further-documentation)
|
||||||
<!-- generated TOC end. -->
|
<!-- generated TOC end. -->
|
||||||
|
|
||||||
@ -199,7 +201,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 | sed -e 's/^ //g'
|
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:
|
||||||
@ -233,12 +235,19 @@ sudo apt install graphviz
|
|||||||
|
|
||||||
##### Ubuntu Linux command line
|
##### Ubuntu Linux command line
|
||||||
|
|
||||||
```sh
|
1. Install Pandoc with some extra libraries:
|
||||||
|
```shell
|
||||||
sudo apt-get install pandoc texlive-latex-base texlive-fonts-recommended texlive-extra-utils texlive-latex-extra pandoc-plantuml-filter
|
sudo apt-get install pandoc texlive-latex-base texlive-fonts-recommended texlive-extra-utils texlive-latex-extra pandoc-plantuml-filter
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
2. Install mermaid-filter, e.g. this way:
|
||||||
pandoc --filter pandoc-plantuml rbac.md -o rbac.pdf
|
```shell
|
||||||
|
npm install -g mermaid-filter
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run Pandoc to generate a PDF from a Markdown file with PlantUML and Mermaid diagrams:
|
||||||
|
```shell
|
||||||
|
pandoc --filter mermaid-filter --filter pandoc-plantuml rbac.md -o rbac.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
##### for other IDEs / operating systems
|
##### for other IDEs / operating systems
|
||||||
@ -247,7 +256,7 @@ If you have figured out how it works, please add instructions above this section
|
|||||||
|
|
||||||
#### Render Markdown Embedded Mermaid Diagrams
|
#### Render Markdown Embedded Mermaid Diagrams
|
||||||
|
|
||||||
The source of RBAC role diagrams are much easier to read with Mermaid than with PlantUML or GraphViz, that's the main reason Mermaid ist used too.
|
The source of RBAC role diagrams are much easier to read with Mermaid than with PlantUML or GraphViz, that's also the main reason Mermaid is used.
|
||||||
|
|
||||||
Can you see the following diagram right in your IDE?
|
Can you see the following diagram right in your IDE?
|
||||||
I mean a real graphic diagram, not just some markup code.
|
I mean a real graphic diagram, not just some markup code.
|
||||||
@ -271,8 +280,11 @@ If not, you need to install some tooling.
|
|||||||
|
|
||||||
##### for IntelliJ IDEA (or derived products)
|
##### for IntelliJ IDEA (or derived products)
|
||||||
|
|
||||||
You just need the bundled Markdown plugin enabled and install and activate the Mermaid plugin in its [settings](jetbrains://idea/settings?name=Languages+%26+Frameworks--Markdown).
|
1. Activate the bundled Jebrains Markdown PlantUML Extension via
|
||||||
|
[File | Settings | Languages & Frameworks | Markdown](jetbrains://idea/settings?name=Languages+%26+Frameworks--Markdown)
|
||||||
|
2. Install the Jetbrains Mermaid plugin: https://plugins.jetbrains.com/plugin/20146-mermaid, it also works embedded in Markdown files.
|
||||||
|
|
||||||
|
Now the above diagram should be rendered.
|
||||||
|
|
||||||
##### for other IDEs / command-line / operating systems
|
##### for other IDEs / command-line / operating systems
|
||||||
|
|
||||||
@ -282,13 +294,23 @@ If you have figured out how it works, please add instructions above this section
|
|||||||
|
|
||||||
#### IntelliJ IDEA
|
#### IntelliJ IDEA
|
||||||
|
|
||||||
|
##### Build Settings
|
||||||
|
|
||||||
Go to [Gradle Settings}(jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Build+Tools--Gradle) and select "Build and run using" and "Run tests using" both to "gradle".
|
Go to [Gradle Settings}(jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Build+Tools--Gradle) and select "Build and run using" and "Run tests using" both to "gradle".
|
||||||
Otherwise, settings from `build.gradle`, like compiler arguments, are not applied when compiling through *IntelliJ IDEA*.
|
Otherwise, settings from `build.gradle`, like compiler arguments, are not applied when compiling through *IntelliJ IDEA*.
|
||||||
|
|
||||||
|
##### Annotation Processor
|
||||||
|
|
||||||
Go to [Annotations Processors](jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Compiler--Annotation+Processors) and activate annotation processing.
|
Go to [Annotations Processors](jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Compiler--Annotation+Processors) and activate annotation processing.
|
||||||
Otherwise, *IntelliJ IDEA* can't see *Lombok* generated classes
|
Otherwise, *IntelliJ IDEA* can't see *Lombok* generated classes
|
||||||
and will show false errors (missing identifiers).
|
and will show false errors (missing identifiers).
|
||||||
|
|
||||||
|
|
||||||
|
##### Suggested Plugins
|
||||||
|
|
||||||
|
- [Jetbrains Mermaid Integration](https://plugins.jetbrains.com/plugin/20146-mermaid)
|
||||||
|
- [Vojtěch Krása PlantUML Integration](https://plugins.jetbrains.com/plugin/7017-plantuml-integration)
|
||||||
|
|
||||||
### Other Tools
|
### Other Tools
|
||||||
|
|
||||||
**jq**: a JSON formatter.
|
**jq**: a JSON formatter.
|
||||||
|
25
build.gradle
25
build.gradle
@ -50,8 +50,6 @@ ext {
|
|||||||
set('testcontainersVersion', "1.17.3")
|
set('testcontainersVersion', "1.17.3")
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapper
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
|
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
|
||||||
@ -71,6 +69,17 @@ dependencies {
|
|||||||
implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
|
implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
|
||||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
|
||||||
|
|
||||||
|
// fixes vulnerability CVE-2022-1471
|
||||||
|
// The dependency usually comes from Spring Boot, just in the wrong version.
|
||||||
|
// TODO: Remove this explicit dependency once we are on SpringBoot 3.2.x
|
||||||
|
// as well as the related exclude in settings.gradle
|
||||||
|
// and the dependency suppression in owasp-dependency-check-suppression.xml.
|
||||||
|
implementation('org.yaml:snakeyaml') {
|
||||||
|
version {
|
||||||
|
strictly('2.2')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
testCompileOnly 'org.projectlombok:lombok'
|
testCompileOnly 'org.projectlombok:lombok'
|
||||||
|
|
||||||
@ -173,7 +182,7 @@ openApiGenerate.dependsOn processSpring
|
|||||||
// Spotless Code Formatting
|
// Spotless Code Formatting
|
||||||
spotless {
|
spotless {
|
||||||
java {
|
java {
|
||||||
// removeUnusedImports() TODO: reactivate once it can deal with multi-line-strings
|
removeUnusedImports()
|
||||||
indentWithSpaces(4)
|
indentWithSpaces(4)
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
@ -184,8 +193,14 @@ spotless {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
project.tasks.spotlessJava.dependsOn(tasks.generateLicenseReport, tasks.processResources, tasks.processTestResources)
|
|
||||||
project.tasks.check.dependsOn(spotlessCheck)
|
project.tasks.check.dependsOn(spotlessCheck)
|
||||||
|
// HACK: no idea why spotless uses the output of these tasks, but we get warnings without those
|
||||||
|
project.tasks.spotlessJava.dependsOn(
|
||||||
|
tasks.generateLicenseReport,
|
||||||
|
tasks.pitest,
|
||||||
|
tasks.jacocoTestReport,
|
||||||
|
tasks.processResources,
|
||||||
|
tasks.processTestResources)
|
||||||
|
|
||||||
// OWASP Dependency Security Test
|
// OWASP Dependency Security Test
|
||||||
dependencyCheck {
|
dependencyCheck {
|
||||||
@ -293,7 +308,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*']
|
||||||
|
|
||||||
pitestVersion = '1.9.9'
|
pitestVersion = '1.15.3'
|
||||||
junit5PluginVersion = '1.1.0'
|
junit5PluginVersion = '1.1.0'
|
||||||
|
|
||||||
threads = 4
|
threads = 4
|
||||||
|
@ -49,4 +49,13 @@
|
|||||||
<packageUrl regex="true">^pkg:maven/org\.pitest/pitest\-command\-line@.*$</packageUrl>
|
<packageUrl regex="true">^pkg:maven/org\.pitest/pitest\-command\-line@.*$</packageUrl>
|
||||||
<cpe>cpe:/a:line:line</cpe>
|
<cpe>cpe:/a:line:line</cpe>
|
||||||
</suppress>
|
</suppress>
|
||||||
|
<suppress>
|
||||||
|
<notes><![CDATA[
|
||||||
|
We've explicitly bumped to 2.2, but the dependency checker does not seem to notice that.
|
||||||
|
TODO: Remove this suppression once we are on SpringBoot 3.2,
|
||||||
|
as well as the explicit version bump and the transient dependency exclude.
|
||||||
|
]]></notes>
|
||||||
|
<packageUrl regex="true">^pkg:maven/org\.yaml/snakeyaml@.*$</packageUrl>
|
||||||
|
<cve>CVE-2022-1471</cve>
|
||||||
|
</suppress>
|
||||||
</suppressions>
|
</suppressions>
|
||||||
|
@ -7,4 +7,21 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
components {
|
||||||
|
all {
|
||||||
|
allVariants {
|
||||||
|
withDependencies {
|
||||||
|
removeAll {
|
||||||
|
// TODO: Remove this transient dependency exclude once we are on SpringBoot 3.2.x
|
||||||
|
// as well as the related explicit dependency in build.gradle
|
||||||
|
// and the dependency suppression in owasp-dependency-check-suppression.xml.
|
||||||
|
it.module in [ 'snakeyaml' ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rootProject.name = 'hsadmin-ng'
|
rootProject.name = 'hsadmin-ng'
|
||||||
|
@ -14,7 +14,7 @@ public interface HsOfficeContactRepository extends Repository<HsOfficeContactEnt
|
|||||||
@Query("""
|
@Query("""
|
||||||
SELECT c FROM HsOfficeContactEntity c
|
SELECT c FROM HsOfficeContactEntity c
|
||||||
WHERE :label is null
|
WHERE :label is null
|
||||||
OR c.label like concat(:label, '%')
|
OR c.label like concat(cast(:label as text), '%')
|
||||||
""")
|
""")
|
||||||
List<HsOfficeContactEntity> findContactByOptionalLabelLike(String label);
|
List<HsOfficeContactEntity> findContactByOptionalLabelLike(String label);
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.coopassets;
|
package net.hostsharing.hsadminng.hs.office.coopassets;
|
||||||
|
|
||||||
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
import org.hibernate.annotations.Type;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@ -47,7 +45,6 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable {
|
|||||||
|
|
||||||
@Column(name = "transactiontype")
|
@Column(name = "transactiontype")
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
//@Type(PostgreSQLEnumType.class)
|
|
||||||
private HsOfficeCoopAssetsTransactionType transactionType;
|
private HsOfficeCoopAssetsTransactionType transactionType;
|
||||||
|
|
||||||
@Column(name = "valuedate")
|
@Column(name = "valuedate")
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.coopassets;
|
package net.hostsharing.hsadminng.hs.office.coopassets;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionEntity;
|
|
||||||
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;
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.coopshares;
|
package net.hostsharing.hsadminng.hs.office.coopshares;
|
||||||
|
|
||||||
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||||
import org.hibernate.annotations.Type;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@ -43,7 +41,6 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable {
|
|||||||
|
|
||||||
@Column(name = "transactiontype")
|
@Column(name = "transactiontype")
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
//@Type(PostgreSQLEnumType.class)
|
|
||||||
private HsOfficeCoopSharesTransactionType transactionType;
|
private HsOfficeCoopSharesTransactionType transactionType;
|
||||||
|
|
||||||
@Column(name = "valuedate")
|
@Column(name = "valuedate")
|
||||||
|
@ -23,11 +23,11 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
|
|||||||
JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
|
JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
|
||||||
JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact.uuid
|
JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact.uuid
|
||||||
WHERE :name is null
|
WHERE :name is null
|
||||||
OR partner.details.birthName like concat(:name, '%')
|
OR partner.details.birthName like concat(cast(:name as text), '%')
|
||||||
OR person.tradeName like concat(:name, '%')
|
OR person.tradeName like concat(cast(:name as text), '%')
|
||||||
OR person.familyName like concat(:name, '%')
|
OR person.familyName like concat(cast(:name as text), '%')
|
||||||
OR person.givenName like concat(:name, '%')
|
OR person.givenName like concat(cast(:name as text), '%')
|
||||||
OR contact.label like concat(:name, '%')
|
OR contact.label like concat(cast(:name as text), '%')
|
||||||
""")
|
""")
|
||||||
List<HsOfficeDebitorEntity> findDebitorByOptionalNameLike(String name);
|
List<HsOfficeDebitorEntity> findDebitorByOptionalNameLike(String name);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.membership;
|
package net.hostsharing.hsadminng.hs.office.membership;
|
||||||
|
|
||||||
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
|
|
||||||
import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType;
|
import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType;
|
||||||
import com.vladmihalcea.hibernate.type.range.Range;
|
import com.vladmihalcea.hibernate.type.range.Range;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
@ -61,7 +60,6 @@ public class HsOfficeMembershipEntity implements Stringifyable {
|
|||||||
|
|
||||||
@Column(name = "reasonfortermination")
|
@Column(name = "reasonfortermination")
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
//@Type(PostgreSQLEnumType.class)
|
|
||||||
private HsOfficeReasonForTermination reasonForTermination;
|
private HsOfficeReasonForTermination reasonForTermination;
|
||||||
|
|
||||||
public void setValidFrom(final LocalDate validFrom) {
|
public void setValidFrom(final LocalDate validFrom) {
|
||||||
|
@ -16,11 +16,11 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
|
|||||||
JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact.uuid
|
JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact.uuid
|
||||||
JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
|
JOIN HsOfficePersonEntity person ON person.uuid = partner.person.uuid
|
||||||
WHERE :name is null
|
WHERE :name is null
|
||||||
OR partner.details.birthName like concat(:name, '%')
|
OR partner.details.birthName like concat(cast(:name as text), '%')
|
||||||
OR contact.label like concat(:name, '%')
|
OR contact.label like concat(cast(:name as text), '%')
|
||||||
OR person.tradeName like concat(:name, '%')
|
OR person.tradeName like concat(cast(:name as text), '%')
|
||||||
OR person.givenName like concat(:name, '%')
|
OR person.givenName like concat(cast(:name as text), '%')
|
||||||
OR person.familyName like concat(:name, '%')
|
OR person.familyName like concat(cast(:name as text), '%')
|
||||||
""")
|
""")
|
||||||
List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name);
|
List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name);
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.person;
|
package net.hostsharing.hsadminng.hs.office.person;
|
||||||
|
|
||||||
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import lombok.experimental.FieldNameConstants;
|
import lombok.experimental.FieldNameConstants;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hibernate.annotations.Type;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -37,7 +35,6 @@ public class HsOfficePersonEntity implements Stringifyable {
|
|||||||
|
|
||||||
@Column(name = "persontype")
|
@Column(name = "persontype")
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
//@Type(PostgreSQLEnumType.class)
|
|
||||||
private HsOfficePersonType personType;
|
private HsOfficePersonType personType;
|
||||||
|
|
||||||
@Column(name = "tradename")
|
@Column(name = "tradename")
|
||||||
|
@ -14,9 +14,9 @@ public interface HsOfficePersonRepository extends Repository<HsOfficePersonEntit
|
|||||||
@Query("""
|
@Query("""
|
||||||
SELECT p FROM HsOfficePersonEntity p
|
SELECT p FROM HsOfficePersonEntity p
|
||||||
WHERE :name is null
|
WHERE :name is null
|
||||||
OR p.tradeName like concat(:name, '%')
|
OR p.tradeName like concat(cast(:name as text), '%')
|
||||||
OR p.givenName like concat(:name, '%')
|
OR p.givenName like concat(cast(:name as text), '%')
|
||||||
OR p.familyName like concat(:name, '%')
|
OR p.familyName like concat(cast(:name as text), '%')
|
||||||
""")
|
""")
|
||||||
List<HsOfficePersonEntity> findPersonByOptionalNameLike(String name);
|
List<HsOfficePersonEntity> findPersonByOptionalNameLike(String name);
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.relationship;
|
package net.hostsharing.hsadminng.hs.office.relationship;
|
||||||
|
|
||||||
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import lombok.experimental.FieldNameConstants;
|
import lombok.experimental.FieldNameConstants;
|
||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||||
import org.hibernate.annotations.Type;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -47,7 +45,6 @@ public class HsOfficeRelationshipEntity {
|
|||||||
|
|
||||||
@Column(name = "reltype")
|
@Column(name = "reltype")
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
//@Type(PostgreSQLEnumType.class)
|
|
||||||
private HsOfficeRelationshipType relType;
|
private HsOfficeRelationshipType relType;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -14,7 +14,7 @@ public interface HsOfficeSepaMandateRepository extends Repository<HsOfficeSepaMa
|
|||||||
@Query("""
|
@Query("""
|
||||||
SELECT mandate FROM HsOfficeSepaMandateEntity mandate
|
SELECT mandate FROM HsOfficeSepaMandateEntity mandate
|
||||||
WHERE :iban is null
|
WHERE :iban is null
|
||||||
OR mandate.bankAccount.iban like concat(:iban, '%')
|
OR mandate.bankAccount.iban like concat(cast(:iban as text), '%')
|
||||||
ORDER BY mandate.bankAccount.iban
|
ORDER BY mandate.bankAccount.iban
|
||||||
""")
|
""")
|
||||||
List<HsOfficeSepaMandateEntity> findSepaMandateByOptionalIban(String iban);
|
List<HsOfficeSepaMandateEntity> findSepaMandateByOptionalIban(String iban);
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package net.hostsharing.hsadminng.mapper;
|
package net.hostsharing.hsadminng.mapper;
|
||||||
|
|
||||||
import com.vladmihalcea.hibernate.type.range.Range;
|
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
import org.postgresql.util.PGtokenizer;
|
import org.postgresql.util.PGtokenizer;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
@ -44,7 +42,10 @@ public class PostgresArray {
|
|||||||
tokenizer.remove("\"", "\"");
|
tokenizer.remove("\"", "\"");
|
||||||
final T[] array = newGenericArray(elementClass, tokenizer.getSize()); // Create a new array of the specified type and length
|
final T[] array = newGenericArray(elementClass, tokenizer.getSize()); // Create a new array of the specified type and length
|
||||||
for ( int n = 0; n < tokenizer.getSize(); ++n ) {
|
for ( int n = 0; n < tokenizer.getSize(); ++n ) {
|
||||||
array[n] = itemParser.apply(tokenizer.getToken(n).trim().replace("\\\"", "\""));
|
final String token = tokenizer.getToken(n);
|
||||||
|
if ( !"NULL".equals(token) ) {
|
||||||
|
array[n] = itemParser.apply(token.trim().replace("\\\"", "\""));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ public interface RbacUserRepository extends Repository<RbacUserEntity, UUID> {
|
|||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
select u from RbacUserEntity u
|
select u from RbacUserEntity u
|
||||||
where :userName is null or u.name like concat(:userName, '%')
|
where :userName is null or u.name like concat(cast(:userName as text), '%')
|
||||||
order by u.name
|
order by u.name
|
||||||
""")
|
""")
|
||||||
List<RbacUserEntity> findByOptionalNameLike(String userName);
|
List<RbacUserEntity> findByOptionalNameLike(String userName);
|
||||||
|
@ -11,7 +11,6 @@ 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 java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class TestCustomerController implements TestCustomersApi {
|
public class TestCustomerController implements TestCustomersApi {
|
||||||
|
@ -12,7 +12,7 @@ public interface TestCustomerRepository extends Repository<TestCustomerEntity, U
|
|||||||
|
|
||||||
Optional<TestCustomerEntity> findByUuid(UUID id);
|
Optional<TestCustomerEntity> findByUuid(UUID id);
|
||||||
|
|
||||||
@Query("SELECT c FROM TestCustomerEntity c WHERE :prefix is null or c.prefix like concat(:prefix, '%')")
|
@Query("SELECT c FROM TestCustomerEntity c WHERE :prefix is null or c.prefix like concat(cast(:prefix as text), '%')")
|
||||||
List<TestCustomerEntity> findCustomerByOptionalPrefixLike(String prefix);
|
List<TestCustomerEntity> findCustomerByOptionalPrefixLike(String prefix);
|
||||||
|
|
||||||
TestCustomerEntity save(final TestCustomerEntity entity);
|
TestCustomerEntity save(final TestCustomerEntity entity);
|
||||||
|
@ -8,7 +8,7 @@ import java.util.UUID;
|
|||||||
|
|
||||||
public interface TestPackageRepository extends Repository<TestPackageEntity, UUID> {
|
public interface TestPackageRepository extends Repository<TestPackageEntity, UUID> {
|
||||||
|
|
||||||
@Query("SELECT p FROM TestPackageEntity p WHERE :name is null or p.name like concat(:name, '%')")
|
@Query("SELECT p FROM TestPackageEntity p WHERE :name is null or p.name like concat(cast(:name as text), '%')")
|
||||||
List<TestPackageEntity> findAllByOptionalNameLike(final String name);
|
List<TestPackageEntity> findAllByOptionalNameLike(final String name);
|
||||||
|
|
||||||
TestPackageEntity findByUuid(UUID packageUuid);
|
TestPackageEntity findByUuid(UUID packageUuid);
|
||||||
|
@ -83,7 +83,7 @@ class HsOfficeBankAccountControllerRestTest {
|
|||||||
enum InvalidBicTestCase {
|
enum InvalidBicTestCase {
|
||||||
TOO_SHORT("BEVODEB", "Bic length must be 8 or 11"),
|
TOO_SHORT("BEVODEB", "Bic length must be 8 or 11"),
|
||||||
TOO_LONG("BEVODEBBX", "Bic length must be 8 or 11"),
|
TOO_LONG("BEVODEBBX", "Bic length must be 8 or 11"),
|
||||||
INVALID_CHARACTER("BEV-ODEB", "Bank code must contain only letters.");
|
INVALID_CHARACTER("BEV-ODEB", "Bank code must contain only alphanumeric.");
|
||||||
|
|
||||||
private final String givenBic;
|
private final String givenBic;
|
||||||
private final String expectedErrorMessage;
|
private final String expectedErrorMessage;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.bankaccount;
|
package net.hostsharing.hsadminng.hs.office.bankaccount;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class TestHsOfficeBankAccount {
|
public class TestHsOfficeBankAccount {
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
|
|||||||
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
||||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@Import( { Context.class, JpaAttempt.class })
|
@Import( { Context.class, JpaAttempt.class })
|
||||||
@ -237,10 +236,6 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
|
final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
|
||||||
final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll());
|
final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll());
|
||||||
final var givenContact = givenSomeTemporaryContact("selfregistered-user-drew@hostsharing.org");
|
final var givenContact = givenSomeTemporaryContact("selfregistered-user-drew@hostsharing.org");
|
||||||
assumeThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created")
|
|
||||||
.isEqualTo(initialRoleNames.size() + 3);
|
|
||||||
assumeThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created")
|
|
||||||
.isEqualTo(initialGrantNames.size() + 7);
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = jpaAttempt.transacted(() -> {
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.contact;
|
package net.hostsharing.hsadminng.hs.office.contact;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class TestHsOfficeContact {
|
public class TestHsOfficeContact {
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ import java.util.UUID;
|
|||||||
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
|
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
|
||||||
import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
|
import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
@SpringBootTest(
|
@SpringBootTest(
|
||||||
@ -481,7 +480,7 @@ class HsOfficeDebitorControllerAcceptanceTest {
|
|||||||
void contactAdminUser_canNotDeleteRelatedDebitor() {
|
void contactAdminUser_canNotDeleteRelatedDebitor() {
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
final var givenDebitor = givenSomeTemporaryDebitor();
|
final var givenDebitor = givenSomeTemporaryDebitor();
|
||||||
assumeThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("forth contact");
|
assertThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("forth contact");
|
||||||
|
|
||||||
RestAssured // @formatter:off
|
RestAssured // @formatter:off
|
||||||
.given()
|
.given()
|
||||||
@ -501,7 +500,7 @@ class HsOfficeDebitorControllerAcceptanceTest {
|
|||||||
void normalUser_canNotDeleteUnrelatedDebitor() {
|
void normalUser_canNotDeleteUnrelatedDebitor() {
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
final var givenDebitor = givenSomeTemporaryDebitor();
|
final var givenDebitor = givenSomeTemporaryDebitor();
|
||||||
assumeThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("forth contact");
|
assertThat(givenDebitor.getBillingContact().getLabel()).isEqualTo("forth contact");
|
||||||
|
|
||||||
RestAssured // @formatter:off
|
RestAssured // @formatter:off
|
||||||
.given()
|
.given()
|
||||||
|
@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.hs.office.debitor;
|
|||||||
|
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.TEST_CONTACT;
|
import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.TEST_CONTACT;
|
||||||
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
|
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
|
||||||
|
@ -32,7 +32,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
|
|||||||
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
||||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@Import( { Context.class, JpaAttempt.class })
|
@Import( { Context.class, JpaAttempt.class })
|
||||||
@ -327,7 +326,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
// when
|
// when
|
||||||
final var result = jpaAttempt.transacted(() -> {
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
context("superuser-alex@hostsharing.net", "hs_office_debitor#10003ThirdOHG-thirdcontact.admin");
|
context("superuser-alex@hostsharing.net", "hs_office_debitor#10003ThirdOHG-thirdcontact.admin");
|
||||||
assumeThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent();
|
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent();
|
||||||
|
|
||||||
membershipRepo.deleteByUuid(givenMembership.getUuid());
|
membershipRepo.deleteByUuid(givenMembership.getUuid());
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.membership;
|
|||||||
import com.vladmihalcea.hibernate.type.range.Range;
|
import com.vladmihalcea.hibernate.type.range.Range;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
|
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ import java.util.UUID;
|
|||||||
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
|
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
|
||||||
import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
|
import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
@ -408,7 +407,7 @@ class HsOfficePartnerControllerAcceptanceTest {
|
|||||||
void contactAdminUser_canNotDeleteRelatedPartner() {
|
void contactAdminUser_canNotDeleteRelatedPartner() {
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
final var givenPartner = givenSomeTemporaryPartnerBessler();
|
final var givenPartner = givenSomeTemporaryPartnerBessler();
|
||||||
assumeThat(givenPartner.getContact().getLabel()).isEqualTo("forth contact");
|
assertThat(givenPartner.getContact().getLabel()).isEqualTo("forth contact");
|
||||||
|
|
||||||
RestAssured // @formatter:off
|
RestAssured // @formatter:off
|
||||||
.given()
|
.given()
|
||||||
@ -428,7 +427,7 @@ class HsOfficePartnerControllerAcceptanceTest {
|
|||||||
void normalUser_canNotDeleteUnrelatedPartner() {
|
void normalUser_canNotDeleteUnrelatedPartner() {
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
final var givenPartner = givenSomeTemporaryPartnerBessler();
|
final var givenPartner = givenSomeTemporaryPartnerBessler();
|
||||||
assumeThat(givenPartner.getContact().getLabel()).isEqualTo("forth contact");
|
assertThat(givenPartner.getContact().getLabel()).isEqualTo("forth contact");
|
||||||
|
|
||||||
RestAssured // @formatter:off
|
RestAssured // @formatter:off
|
||||||
.given()
|
.given()
|
||||||
|
@ -29,7 +29,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
|
|||||||
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
||||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@Import( { Context.class, JpaAttempt.class })
|
@Import( { Context.class, JpaAttempt.class })
|
||||||
@ -330,7 +329,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
// when
|
// when
|
||||||
final var result = jpaAttempt.transacted(() -> {
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
context("person-ErbenBesslerMelBessler@example.com");
|
context("person-ErbenBesslerMelBessler@example.com");
|
||||||
assumeThat(partnerRepo.findByUuid(givenPartner.getUuid())).isPresent();
|
assertThat(partnerRepo.findByUuid(givenPartner.getUuid())).isPresent();
|
||||||
|
|
||||||
partnerRepo.deleteByUuid(givenPartner.getUuid());
|
partnerRepo.deleteByUuid(givenPartner.getUuid());
|
||||||
});
|
});
|
||||||
@ -352,10 +351,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll()));
|
final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll()));
|
||||||
final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll()));
|
final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll()));
|
||||||
final var givenPartner = givenSomeTemporaryPartnerBessler("twelfth");
|
final var givenPartner = givenSomeTemporaryPartnerBessler("twelfth");
|
||||||
assumeThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created")
|
|
||||||
.isEqualTo(initialRoleNames.length + 3);
|
|
||||||
assumeThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created")
|
|
||||||
.isEqualTo(initialGrantNames.length + 10);
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = jpaAttempt.transacted(() -> {
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
|
@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.partner;
|
|||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL;
|
import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGAL;
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
|
|||||||
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
||||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@Import( { Context.class, JpaAttempt.class })
|
@Import( { Context.class, JpaAttempt.class })
|
||||||
@ -244,10 +243,6 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
|
final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
|
||||||
final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll());
|
final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll());
|
||||||
final var givenPerson = givenSomeTemporaryPerson("selfregistered-user-drew@hostsharing.org");
|
final var givenPerson = givenSomeTemporaryPerson("selfregistered-user-drew@hostsharing.org");
|
||||||
assumeThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created")
|
|
||||||
.isEqualTo(initialRoleNames.size() + 3);
|
|
||||||
assumeThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created")
|
|
||||||
.isEqualTo(initialGrantNames.size() + 7);
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = jpaAttempt.transacted(() -> {
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.person;
|
package net.hostsharing.hsadminng.hs.office.person;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class TestHsOfficePerson {
|
public class TestHsOfficePerson {
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ import java.util.UUID;
|
|||||||
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
|
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
|
||||||
import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
|
import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.sepamandate;
|
package net.hostsharing.hsadminng.hs.office.sepamandate;
|
||||||
|
|
||||||
import com.vladmihalcea.hibernate.type.range.Range;
|
|
||||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantD
|
|||||||
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
|
||||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@Import({ Context.class, JpaAttempt.class })
|
@Import({ Context.class, JpaAttempt.class })
|
||||||
@ -346,7 +345,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
// when
|
// when
|
||||||
final var result = jpaAttempt.transacted(() -> {
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
context("bankaccount-admin@ThirdOHG.example.com");
|
context("bankaccount-admin@ThirdOHG.example.com");
|
||||||
assumeThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent();
|
assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent();
|
||||||
|
|
||||||
sepaMandateRepo.deleteByUuid(givenSepaMandate.getUuid());
|
sepaMandateRepo.deleteByUuid(givenSepaMandate.getUuid());
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package net.hostsharing.hsadminng.mapper;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@DataJpaTest
|
||||||
|
class PostgresArrayIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
EntityManager em;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateEmptyArray() {
|
||||||
|
em.createNativeQuery("""
|
||||||
|
create or replace function returnEmptyArray()
|
||||||
|
returns text[]
|
||||||
|
stable leakproof
|
||||||
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
emptyArray text[] = '{}';
|
||||||
|
begin
|
||||||
|
return emptyArray;
|
||||||
|
end; $$;
|
||||||
|
""").executeUpdate();
|
||||||
|
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnEmptyArray()", String[].class).getSingleResult();
|
||||||
|
|
||||||
|
final String[] result = PostgresArray.fromPostgresArray(pgArray, String.class, Function.identity());
|
||||||
|
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateStringArray() {
|
||||||
|
em.createNativeQuery("""
|
||||||
|
create or replace function returnStringArray()
|
||||||
|
returns varchar(63)[]
|
||||||
|
stable leakproof
|
||||||
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
text1 text = 'one';
|
||||||
|
text2 text = 'two, three';
|
||||||
|
text3 text = 'four; five';
|
||||||
|
text4 text = 'say "Hello" to me';
|
||||||
|
begin
|
||||||
|
return array[text1, text2, text3, null, text4];
|
||||||
|
end; $$;
|
||||||
|
""").executeUpdate();
|
||||||
|
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnStringArray()", String[].class).getSingleResult();
|
||||||
|
|
||||||
|
final String[] result = PostgresArray.fromPostgresArray(pgArray, String.class, Function.identity());
|
||||||
|
|
||||||
|
assertThat(result).containsExactly("one", "two, three", "four; five", null, "say \"Hello\" to me");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateUUidArray() {
|
||||||
|
em.createNativeQuery("""
|
||||||
|
create or replace function returnUuidArray()
|
||||||
|
returns uuid[]
|
||||||
|
stable leakproof
|
||||||
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
uuid1 UUID = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';
|
||||||
|
uuid2 UUID = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
|
||||||
|
uuid3 UUID = '01234567-89ab-cdef-0123-456789abcdef';
|
||||||
|
begin
|
||||||
|
return ARRAY[uuid1, uuid2, null, uuid3];
|
||||||
|
end; $$;
|
||||||
|
""").executeUpdate();
|
||||||
|
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnUuidArray()", UUID[].class).getSingleResult();
|
||||||
|
|
||||||
|
final UUID[] result = PostgresArray.fromPostgresArray(pgArray, UUID.class, UUID::fromString);
|
||||||
|
|
||||||
|
assertThat(result).containsExactly(
|
||||||
|
UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"),
|
||||||
|
UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"),
|
||||||
|
null,
|
||||||
|
UUID.fromString("01234567-89ab-cdef-0123-456789abcdef"));
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,6 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
@ -343,7 +342,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assumeCreated(final ValidatableResponse response) {
|
private void assumeCreated(final ValidatableResponse response) {
|
||||||
assumeThat(response.extract().response().statusCode()).isEqualTo(201);
|
assertThat(response.extract().response().statusCode()).isEqualTo(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Subject {
|
class Subject {
|
||||||
@ -479,7 +478,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assumeGrantExists(final Subject grantingSubject, final String expectedGrant) {
|
private void assumeGrantExists(final Subject grantingSubject, final String expectedGrant) {
|
||||||
assumeThat(findAllGrantsOf(grantingSubject))
|
assertThat(findAllGrantsOf(grantingSubject))
|
||||||
.extracting(RbacGrantEntity::toDisplay)
|
.extracting(RbacGrantEntity::toDisplay)
|
||||||
.contains(expectedGrant);
|
.contains(expectedGrant);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@Import( { Context.class, JpaAttempt.class })
|
@Import( { Context.class, JpaAttempt.class })
|
||||||
@ -186,9 +185,8 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
context("customer-admin@xxx.example.com", "test_customer#xxx.admin");
|
context("customer-admin@xxx.example.com", "test_customer#xxx.admin");
|
||||||
final var revokeAttempt = attempt(em, () -> {
|
final var revokeAttempt = attempt(em, () ->
|
||||||
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId());
|
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()));
|
||||||
});
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
context("customer-admin@xxx.example.com", "test_customer#xxx.admin");
|
context("customer-admin@xxx.example.com", "test_customer#xxx.admin");
|
||||||
@ -208,9 +206,8 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
context("pac-admin-xxx00@xxx.example.com", "test_package#xxx00.admin");
|
context("pac-admin-xxx00@xxx.example.com", "test_package#xxx00.admin");
|
||||||
final var revokeAttempt = attempt(em, () -> {
|
final var revokeAttempt = attempt(em, () ->
|
||||||
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId());
|
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()));
|
||||||
});
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(revokeAttempt.caughtExceptionsRootCause()).isNull();
|
assertThat(revokeAttempt.caughtExceptionsRootCause()).isNull();
|
||||||
@ -230,9 +227,8 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
context("pac-admin-xxx00@xxx.example.com", "test_package#xxx00.admin");
|
context("pac-admin-xxx00@xxx.example.com", "test_package#xxx00.admin");
|
||||||
final var revokeAttempt = attempt(em, () -> {
|
final var revokeAttempt = attempt(em, () ->
|
||||||
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId());
|
rbacGrantRepository.deleteByRbacGrantId(grant.getRbacGrantId()));
|
||||||
});
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
revokeAttempt.assertExceptionWithRootCauseMessage(
|
revokeAttempt.assertExceptionWithRootCauseMessage(
|
||||||
@ -255,10 +251,10 @@ class RbacGrantRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
rbacGrantRepository.save(grant)
|
rbacGrantRepository.save(grant)
|
||||||
);
|
);
|
||||||
|
|
||||||
assumeThat(grantAttempt.caughtException()).isNull();
|
assertThat(grantAttempt.caughtException()).isNull();
|
||||||
assumeThat(rawRbacGrantRepository.findAll())
|
assertThat(rawRbacGrantRepository.findAll())
|
||||||
.extracting(RawRbacGrantEntity::toDisplay)
|
.extracting(RawRbacGrantEntity::toDisplay)
|
||||||
.contains("{ grant role %s to user %s by role %s and assume }".formatted(
|
.contains("{ grant role %s to user %s by %s and assume }".formatted(
|
||||||
with.grantedRole, with.granteeUserName, with.assumedRole
|
with.grantedRole, with.granteeUserName, with.assumedRole
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -61,11 +61,6 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
assertThat(result.returnedValue()).isNotNull()
|
assertThat(result.returnedValue()).isNotNull()
|
||||||
.extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid);
|
.extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid);
|
||||||
assertThat(rbacUserRepository.findByName(result.returnedValue().getName())).isNotNull();
|
assertThat(rbacUserRepository.findByName(result.returnedValue().getName())).isNotNull();
|
||||||
// jpaAttempt.transacted(() -> {
|
|
||||||
// context(givenUser.getName());
|
|
||||||
// assertThat(em.find(RbacUserEntity.class, givenUser.getUuid()))
|
|
||||||
// .isNotNull().extracting(RbacUserEntity::getName).isEqualTo(givenUser.getName());
|
|
||||||
// }).assertSuccessful();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,9 +82,6 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
// then the user is deleted
|
// then the user is deleted
|
||||||
result.assertSuccessful();
|
result.assertSuccessful();
|
||||||
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
|
assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
|
||||||
// jpaAttempt.transacted(() -> {
|
|
||||||
// assertThat(rbacUserRepository.findByName(givenUser.getName())).isNull();
|
|
||||||
// }).assertSuccessful();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.test.cust;
|
package net.hostsharing.hsadminng.test.cust;
|
||||||
|
|
||||||
import static java.util.UUID.randomUUID;
|
|
||||||
|
|
||||||
public class TestCustomer {
|
public class TestCustomer {
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
@ -85,7 +85,8 @@ class TestPackageControllerAcceptanceTest {
|
|||||||
@Test
|
@Test
|
||||||
void withDescriptionUpdatesDescription() {
|
void withDescriptionUpdatesDescription() {
|
||||||
|
|
||||||
assumeThat(getDescriptionOfPackage("xxx00"))
|
assertThat(getDescriptionOfPackage("xxx00"))
|
||||||
|
.as("precondition failed")
|
||||||
.isEqualTo("Here you can add your own description of package xxx00.");
|
.isEqualTo("Here you can add your own description of package xxx00.");
|
||||||
|
|
||||||
final var randomDescription = RandomStringUtils.randomAlphanumeric(80);
|
final var randomDescription = RandomStringUtils.randomAlphanumeric(80);
|
||||||
@ -117,7 +118,8 @@ class TestPackageControllerAcceptanceTest {
|
|||||||
@Test
|
@Test
|
||||||
void withNullDescriptionUpdatesDescriptionToNull() {
|
void withNullDescriptionUpdatesDescriptionToNull() {
|
||||||
|
|
||||||
assumeThat(getDescriptionOfPackage("xxx01"))
|
assertThat(getDescriptionOfPackage("xxx01"))
|
||||||
|
.as("precondition failed")
|
||||||
.isEqualTo("Here you can add your own description of package xxx01.");
|
.isEqualTo("Here you can add your own description of package xxx01.");
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
@ -146,7 +148,8 @@ class TestPackageControllerAcceptanceTest {
|
|||||||
@Test
|
@Test
|
||||||
void withoutDescriptionDoesNothing() {
|
void withoutDescriptionDoesNothing() {
|
||||||
|
|
||||||
assumeThat(getDescriptionOfPackage("xxx02"))
|
assertThat(getDescriptionOfPackage("xxx02"))
|
||||||
|
.as("precondition failed")
|
||||||
.isEqualTo("Here you can add your own description of package xxx02.");
|
.isEqualTo("Here you can add your own description of package xxx02.");
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
@ -12,7 +12,6 @@ import java.util.Optional;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the 'when' part of a DataJpaTest to improve readability of tests.
|
* Wraps the 'when' part of a DataJpaTest to improve readability of tests.
|
||||||
@ -138,7 +137,7 @@ public class JpaAttempt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public JpaResult<T> assumeSuccessful() {
|
public JpaResult<T> assumeSuccessful() {
|
||||||
assumeThat(exception).as(firstRootCauseMessageLineOf(exception)).isNull();
|
assertThat(exception).as(firstRootCauseMessageLineOf(exception)).isNull();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user