feature/split-up-postalAddress #118

Merged
hsh-michaelhoennig merged 10 commits from feature/split-up-postalAddress into master 2024-11-06 12:24:44 +01:00
11 changed files with 142 additions and 54 deletions
Showing only changes of commit 74c13d94fd - Show all commits

View File

@ -54,8 +54,14 @@ public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContac
@Column(name = "caption")
private String caption;
@Builder.Default
@Setter(AccessLevel.NONE)
@Type(JsonType.class)
@Column(name = "postaladdress")
private String postalAddress; // multiline free-format text
private Map<String, String> postalAddress = new HashMap<>();
@Transient
private PatchableMapWrapper<String> postalAddressWrapper;
@Builder.Default
@Setter(AccessLevel.NONE)
@ -75,6 +81,17 @@ public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContac
@Transient
private PatchableMapWrapper<String> phoneNumbersWrapper;
public PatchableMapWrapper<String> getPostalAddress() {
return PatchableMapWrapper.of(
postalAddressWrapper,
(newWrapper) -> {postalAddressWrapper = newWrapper;},
postalAddress);
}
public void putPostalAddress(Map<String, String> newPostalAddress) {
getPostalAddress().assign(newPostalAddress);
}
public PatchableMapWrapper<String> getEmailAddresses() {
return PatchableMapWrapper.of(
emailAddressesWrapper,

View File

@ -18,7 +18,8 @@ class HsOfficeContactEntityPatcher implements EntityPatcher<HsOfficeContactPatch
@Override
public void apply(final HsOfficeContactPatchResource resource) {
OptionalFromJson.of(resource.getCaption()).ifPresent(entity::setCaption);
OptionalFromJson.of(resource.getPostalAddress()).ifPresent(entity::setPostalAddress);
Optional.ofNullable(resource.getPostalAddress())
.ifPresent(r -> entity.getPostalAddress().patch(KeyValueMap.from(resource.getPostalAddress())));
Optional.ofNullable(resource.getEmailAddresses())
.ifPresent(r -> entity.getEmailAddresses().patch(KeyValueMap.from(resource.getEmailAddresses())));
Optional.ofNullable(resource.getPhoneNumbers())

View File

@ -47,7 +47,7 @@ public interface HsOfficeRelationRbacRepository extends Repository<HsOfficeRelat
OR lower(rel.anchor.givenName) LIKE :personData OR lower(rel.holder.givenName) LIKE :personData )
AND ( :contactData IS NULL
OR lower(rel.contact.caption) LIKE :contactData
OR lower(rel.contact.postalAddress) LIKE :contactData
OR lower(CAST(rel.contact.postalAddress AS String)) LIKE :contactData
OR lower(CAST(rel.contact.emailAddresses AS String)) LIKE :contactData
OR lower(CAST(rel.contact.phoneNumbers AS String)) LIKE :contactData )
""")

View File

@ -12,7 +12,7 @@ components:
caption:
type: string
postalAddress:
type: string
$ref: '#/components/schemas/HsOfficeContactPostalAddress'
emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers:
@ -24,7 +24,7 @@ components:
caption:
type: string
postalAddress:
type: string
$ref: '#/components/schemas/HsOfficeContactPostalAddress'
emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers:
@ -39,21 +39,48 @@ components:
type: string
nullable: true
postalAddress:
type: string
nullable: true
$ref: '#/components/schemas/HsOfficeContactPostalAddress'
emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers:
$ref: '#/components/schemas/HsOfficeContactPhoneNumbers'
HsOfficeContactPostalAddress:
# forces generating a java.lang.Object containing a Map, instead of a class with fixed properties
anyOf:
- type: object
properties:
firma:
type: string
nullable: true
name:
type: string
nullable: true
co:
type: string
nullable: true
street:
type: string
nullable: true
zipcode:
type: string
nullable: true
city:
type: string
nullable: true
country:
type: string
nullable: true
additionalProperties: false
hsh-michaelhoennig marked this conversation as resolved Outdated

true

true
HsOfficeContactEmailAddresses:
# forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses
# forces generating a java.lang.Object containing a Map, instead of a class with fixed properties
anyOf:
- type: object
additionalProperties: true
HsOfficeContactPhoneNumbers:
# forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses
# forces generating a java.lang.Object containing a Map, instead of a class with fixed properties
anyOf:
- type: object
properties:

View File

@ -9,7 +9,7 @@ create table if not exists hs_office.contact
uuid uuid unique references rbac.object (uuid) initially deferred,
version int not null default 0,
caption varchar(128) not null,
postalAddress text,
postalAddress jsonb not null,
emailAddresses jsonb not null,
phoneNumbers jsonb not null
);

View File

@ -11,7 +11,6 @@
create or replace procedure hs_office.contact_create_test_data(contCaption varchar)
language plpgsql as $$
declare
postalAddr varchar;
emailAddr varchar;
begin
emailAddr = 'contact-admin@' || base.cleanIdentifier(contCaption) || '.example.com';
@ -19,14 +18,18 @@ begin
perform rbac.create_subject(emailAddr);
call base.defineContext('creating contact test-data', null, emailAddr);
postalAddr := E'Vorname Nachname\nStraße Hnr\nPLZ Stadt';
raise notice 'creating test contact: %', contCaption;
insert
into hs_office.contact (caption, postaladdress, emailaddresses, phonenumbers)
values (
contCaption,
postalAddr,
( '{ ' ||
-- '"name": "' || contCaption || '",' ||
-- '"street": "Somewhere 1",' ||
-- '"zipcode": "12345",' ||
-- '"city": "Where-Ever",' ||
'"country": "Germany"' ||
'}')::jsonb,
('{ "main": "' || emailAddr || '" }')::jsonb,
('{ "phone_office": "+49 123 1234567" }')::jsonb
);

View File

@ -1111,7 +1111,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
contactRecord.getString("last_name"),
contactRecord.getString("firma")));
contact.putEmailAddresses(Map.of("main", contactRecord.getString("email")));
contact.setPostalAddress(toAddress(contactRecord));
contact.putPostalAddress(toAddress(contactRecord));
contact.putPhoneNumbers(toPhoneNumbers(contactRecord));
contacts.put(contactRecord.getInteger("contact_id"), contact);
@ -1131,36 +1131,21 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
return phoneNumbers;
}
private String toAddress(final Record rec) {
final var result = new StringBuilder();
private Map<String, String> toAddress(final Record rec) {
final var result = new LinkedHashMap<String, String>();
final var name = toName(
rec.getString("salut"),
rec.getString("title"),
rec.getString("first_name"),
rec.getString("last_name"));
if (isNotBlank(name))
result.append(name + "\n");
if (isNotBlank(rec.getString("firma")))
result.append(rec.getString("firma") + "\n");
if (isNotBlank(rec.getString("co")))
result.append("c/o " + rec.getString("co") + "\n");
if (isNotBlank(rec.getString("street")))
result.append(rec.getString("street") + "\n");
final var zipcodeAndCity = toZipcodeAndCity(rec);
if (isNotBlank(zipcodeAndCity))
result.append(zipcodeAndCity + "\n");
return result.toString();
}
result.put("name", name);
private String toZipcodeAndCity(final Record rec) {
final var result = new StringBuilder();
if (isNotBlank(rec.getString("country")))
result.append(rec.getString("country") + " ");
if (isNotBlank(rec.getString("zipcode")))
result.append(rec.getString("zipcode") + " ");
if (isNotBlank(rec.getString("city")))
result.append(rec.getString("city"));
return result.toString();
List.of("firma", "co", "street", "zipcode", "city", "country").forEach(key -> {
if (isNotBlank(rec.getString(key)))
result.put(key, rec.getString(key));
});
return result;
}
private String toCaption(

View File

@ -21,10 +21,13 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
@ -214,7 +217,10 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
"emailAddresses": {
"main": "patched@example.org"
},
"postalAddress": "Patched Address",
"postalAddress": {
"co": "P. Patcher",
"street": "Patchstraße 5"
},
"phoneNumbers": {
"phone_office": "+01 100 123456"
}
@ -229,7 +235,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
.body("uuid", isUuidValid())
.body("caption", is("Temp patched contact"))
.body("emailAddresses", is(Map.of("main", "patched@example.org")))
.body("postalAddress", is("Patched Address"))
.body("postalAddress", hasEntry("name", givenContact.getPostalAddress().get("name"))) // unchanged
.body("postalAddress", hasEntry("co", "P. Patcher")) // patched
.body("postalAddress", hasEntry("street", "Patchstraße 5")) // patched
.body("phoneNumbers", is(Map.of("phone_office", "+01 100 123456")));
// @formatter:on
@ -239,7 +247,11 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
.matches(person -> {
assertThat(person.getCaption()).isEqualTo("Temp patched contact");
assertThat(person.getEmailAddresses()).containsExactlyEntriesOf(Map.of("main", "patched@example.org"));
assertThat(person.getPostalAddress()).isEqualTo("Patched Address");
assertThat(person.getPostalAddress()).containsAllEntriesOf(Map.ofEntries(
entry("name", givenContact.getPostalAddress().get("name")),
entry("co", "P. Patcher"),
entry("street", "Patchstraße 5")
));
assertThat(person.getPhoneNumbers()).containsExactlyEntriesOf(Map.of("phone_office", "+01 100 123456"));
return true;
});
@ -361,8 +373,13 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
final var newContact = HsOfficeContactRbacEntity.builder()
.uuid(UUID.randomUUID())
.caption("Temp from " + Context.getCallerMethodNameFromStackFrame(1) )
.postalAddress(Map.ofEntries(
entry("name", RandomStringUtils.randomAlphabetic(6) + " " + RandomStringUtils.randomAlphabetic(10)),
entry("street", RandomStringUtils.randomAlphabetic(10) + randomInt(1, 99)),
entry("zipcode", "D-" + randomInt(10000, 99999)),
entry("city", RandomStringUtils.randomAlphabetic(10))
))
.emailAddresses(Map.of("main", RandomStringUtils.randomAlphabetic(10) + "@example.org"))
.postalAddress("Postal Address " + RandomStringUtils.randomAlphabetic(10))
.phoneNumbers(Map.of("phone_office", "+01 200 " + RandomStringUtils.randomNumeric(8)))
.build();
@ -378,4 +395,8 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
em.createQuery("DELETE FROM HsOfficeContactRbacEntity c WHERE c.caption LIKE 'Temp %'").executeUpdate();
}).assertSuccessful();
}
private int randomInt(final int min, final int max) {
return ThreadLocalRandom.current().nextInt(min, max);
}
}

View File

@ -19,6 +19,20 @@ class HsOfficeContactPatcherUnitTest extends PatchUnitTestBase<
> {
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID();
private static final Map<String, String> PATCH_POSTAL_ADDRESS = patchMap(
entry("name", "Patty Patch"),
entry("street", "Patchstreet 10"),
entry("zipcode", null),
entry("city", "Hamburg")
);
private static final Map<String, String> PATCHED_POSTAL_ADDRESS = patchMap(
entry("name", "Patty Patch"),
entry("street", "Patchstreet 10"),
entry("zipcode", "20000"),
entry("city", "Hamburg")
);
private static final Map<String, String> PATCH_EMAIL_ADDRESSES = patchMap(
entry("main", "patched@example.com"),
entry("paul", null),
@ -46,6 +60,11 @@ class HsOfficeContactPatcherUnitTest extends PatchUnitTestBase<
final var entity = new HsOfficeContactRbacEntity();
entity.setUuid(INITIAL_CONTACT_UUID);
entity.setCaption("initial caption");
entity.putPostalAddress(Map.ofEntries(
entry("name", "Ina Initial"),
entry("street", "Initialstraße 50"),
entry("zipcode", "20000"),
entry("city", "Hamburg")));
entity.putEmailAddresses(Map.ofEntries(
entry("main", "initial@example.org"),
entry("paul", "paul@example.com"),
@ -54,7 +73,6 @@ class HsOfficeContactPatcherUnitTest extends PatchUnitTestBase<
entry("phone_office", "+49 40 12345-00"),
entry("phone_mobile", "+49 1555 1234567"),
entry("fax", "+49 40 12345-90")));
entity.setPostalAddress("Initialstraße 50\n20000 Hamburg");
return entity;
}
@ -77,24 +95,26 @@ class HsOfficeContactPatcherUnitTest extends PatchUnitTestBase<
"patched caption",
HsOfficeContactRbacEntity::setCaption),
new SimpleProperty<>(
"resources",
"postalAddress",
HsOfficeContactPatchResource::setEmailAddresses,
PATCH_POSTAL_ADDRESS,
HsOfficeContactRbacEntity::putEmailAddresses,
PATCHED_POSTAL_ADDRESS)
.notNullable(),
new SimpleProperty<>(
"emailAddresses",
HsOfficeContactPatchResource::setEmailAddresses,
PATCH_EMAIL_ADDRESSES,
HsOfficeContactRbacEntity::putEmailAddresses,
PATCHED_EMAIL_ADDRESSES)
.notNullable(),
new SimpleProperty<>(
"resources",
"phoneNumbers",
HsOfficeContactPatchResource::setPhoneNumbers,
PATCH_PHONE_NUMBERS,
HsOfficeContactRbacEntity::putPhoneNumbers,
PATCHED_PHONE_NUMBERS)
.notNullable(),
new JsonNullableProperty<>(
"patched given name",
HsOfficeContactPatchResource::setPostalAddress,
"patched given name",
HsOfficeContactRbacEntity::setPostalAddress)
.notNullable()
);
}
}

View File

@ -2,6 +2,8 @@ package net.hostsharing.hsadminng.hs.office.contact;
import java.util.Map;
import static java.util.Map.entry;
public class HsOfficeContactRbacTestEntity {
public static final HsOfficeContactRbacEntity TEST_RBAC_CONTACT = hsOfficeContact("some contact", "some-contact@example.com");
@ -9,7 +11,12 @@ public class HsOfficeContactRbacTestEntity {
static public HsOfficeContactRbacEntity hsOfficeContact(final String caption, final String emailAddr) {
return HsOfficeContactRbacEntity.builder()
.caption(caption)
.postalAddress("address of " + caption)
.postalAddress(Map.ofEntries(
entry("name", "M. Meyer"),
entry("street", "Teststraße 11"),
entry("zipcode", "D-12345"),
entry("city", "Berlin")
))
.emailAddresses(Map.of("main", emailAddr))
.build();
}

View File

@ -2,6 +2,8 @@ package net.hostsharing.hsadminng.hs.office.contact;
import java.util.Map;
import static java.util.Map.entry;
public class HsOfficeContactRealTestEntity {
public static final HsOfficeContactRealEntity TEST_REAL_CONTACT = hsOfficeContact("some contact", "some-contact@example.com");
@ -9,7 +11,12 @@ public class HsOfficeContactRealTestEntity {
static public HsOfficeContactRealEntity hsOfficeContact(final String caption, final String emailAddr) {
return HsOfficeContactRealEntity.builder()
.caption(caption)
.postalAddress("address of " + caption)
.postalAddress(Map.ofEntries(
entry("name", "M. Meyer"),
entry("street", "Teststraße 11"),
entry("zipcode", "D-12345"),
entry("city", "Berlin")
))
.emailAddresses(Map.of("main", emailAddr))
.build();
}