introduce-booking-module #41
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.booking.item;
|
|||||||
import io.hypersistence.utils.hibernate.type.json.JsonType;
|
import io.hypersistence.utils.hibernate.type.json.JsonType;
|
||||||
import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
|
import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
|
||||||
import io.hypersistence.utils.hibernate.type.range.Range;
|
import io.hypersistence.utils.hibernate.type.range.Range;
|
||||||
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -10,31 +11,44 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||||
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 org.hibernate.annotations.Type;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
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.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BinaryOperator;
|
|
||||||
|
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static java.util.stream.Collectors.joining;
|
import static net.hostsharing.hsadminng.mapper.PatchableMap.mapToString;
|
||||||
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.rbacdef.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.UPDATE;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.OWNER;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@ -51,18 +65,9 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
|||||||
.withProp(e -> e.getDebitor().toShortString())
|
.withProp(e -> e.getDebitor().toShortString())
|
||||||
.withProp(e -> e.getValidity().asString())
|
.withProp(e -> e.getValidity().asString())
|
||||||
.withProp(HsBookingItemEntity::getCaption)
|
.withProp(HsBookingItemEntity::getCaption)
|
||||||
.withProp(HsBookingItemEntity::getResourcesAsString)
|
.withProp(e -> mapToString(e.getResources()))
|
||||||
.quotedValues(false);
|
.quotedValues(false);
|
||||||
|
|
||||||
private String getResourcesAsString() {
|
|
||||||
return "{ " +
|
|
||||||
(
|
|
||||||
resources.keySet().stream().sorted()
|
|
||||||
.map(k -> k + ": " + resources.get(k)))
|
|
||||||
.collect(joining(", ")
|
|
||||||
) + " }";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
@ -83,6 +88,7 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
|||||||
private String caption;
|
private String caption;
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
|
@Setter(AccessLevel.NONE)
|
||||||
@Type(JsonType.class)
|
@Type(JsonType.class)
|
||||||
@Column(columnDefinition = "resources")
|
@Column(columnDefinition = "resources")
|
||||||
private Map<String, Object> resources = new TreeMap<>();
|
private Map<String, Object> resources = new TreeMap<>();
|
||||||
@ -103,7 +109,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
|||||||
return upperInclusiveFromPostgresDateRange(getValidity());
|
return upperInclusiveFromPostgresDateRange(getValidity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return stringify.apply(this);
|
return stringify.apply(this);
|
||||||
@ -115,10 +120,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
|||||||
":" + caption;
|
":" + caption;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BinaryOperator<Object> thereIsOnlyOneValuePerKey(Object o, Object o1) {
|
|
||||||
return (a, b) -> a;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacView rbac() {
|
||||||
return rbacViewFor("bookingItem", HsBookingItemEntity.class)
|
return rbacViewFor("bookingItem", HsBookingItemEntity.class)
|
||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package net.hostsharing.hsadminng.mapper;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.joining;
|
||||||
|
|
||||||
|
public class PatchableMap {
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static Map<String, Object> patchableMap(final ImmutablePair<String, Object>... entries) {
|
||||||
|
final var map = new HashMap<String, Object>();
|
||||||
|
Arrays.stream(entries).forEach(r -> map.put(r.getKey(), r.getValue()));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static ImmutablePair<String, Object> entry(final String key, final Object value) {
|
||||||
|
return new ImmutablePair<>(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BiConsumer<? extends Object, ? super Map<String, Object>> assignMap = (HsBookingItemEntity i, Map<String, Object> r) -> {
|
||||||
|
i.getResources().clear();
|
||||||
|
i.getResources().putAll(r);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String mapToString(final Map<String, Object> resources) {
|
||||||
|
return "{ "
|
||||||
|
+ (
|
||||||
|
resources.keySet().stream().sorted()
|
||||||
|
.map(k -> k + ": " + resources.get(k)))
|
||||||
|
.collect(joining(", ")
|
||||||
|
)
|
||||||
|
+ " }";
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import io.hypersistence.utils.hibernate.type.range.Range;
|
|||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.ArbitraryBookingResourcesJsonResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.ArbitraryBookingResourcesJsonResource;
|
||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||||
|
import net.hostsharing.hsadminng.mapper.PatchableMap;
|
||||||
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
|
import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.TestInstance;
|
import org.junit.jupiter.api.TestInstance;
|
||||||
@ -63,7 +64,7 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase<
|
|||||||
final var entity = new HsBookingItemEntity();
|
final var entity = new HsBookingItemEntity();
|
||||||
entity.setUuid(INITIAL_BOOKING_ITEM_UUID);
|
entity.setUuid(INITIAL_BOOKING_ITEM_UUID);
|
||||||
entity.setDebitor(TEST_DEBITOR);
|
entity.setDebitor(TEST_DEBITOR);
|
||||||
entity.setResources(objectToMap(INITIAL_RESOURCES));
|
entity.getResources().putAll(objectToMap(INITIAL_RESOURCES));
|
||||||
entity.setCaption(INITIAL_CAPTION);
|
entity.setCaption(INITIAL_CAPTION);
|
||||||
entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM));
|
entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM));
|
||||||
return entity;
|
return entity;
|
||||||
@ -91,7 +92,7 @@ class HsBookingItemEntityPatcherUnitTest extends PatchUnitTestBase<
|
|||||||
"resources",
|
"resources",
|
||||||
HsBookingItemPatchResource::setResources,
|
HsBookingItemPatchResource::setResources,
|
||||||
PATCHED_RESOURCES,
|
PATCHED_RESOURCES,
|
||||||
HsBookingItemEntity::setResources,
|
PatchableMap.assignMap,
|
||||||
objectToMap(PATCHED_RESOURCES))
|
objectToMap(PATCHED_RESOURCES))
|
||||||
.notNullable(),
|
.notNullable(),
|
||||||
new JsonNullableProperty<>(
|
new JsonNullableProperty<>(
|
||||||
|
@ -22,9 +22,9 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static java.util.Map.entry;
|
import static net.hostsharing.hsadminng.mapper.PatchableMap.entry;
|
||||||
|
import static net.hostsharing.hsadminng.mapper.PatchableMap.patchableMap;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
|
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
|
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
|
||||||
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted;
|
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted;
|
||||||
@ -186,18 +186,18 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
|||||||
@Test
|
@Test
|
||||||
public void hostsharingAdmin_canUpdateArbitraryBookingItem() {
|
public void hostsharingAdmin_canUpdateArbitraryBookingItem() {
|
||||||
// given
|
// given
|
||||||
final var givenBookingItem = givenSomeTemporaryBookingItem(1000111);
|
final var givenBookingItemUuid = givenSomeTemporaryBookingItem(1000111).getUuid();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = jpaAttempt.transacted(() -> {
|
final var result = jpaAttempt.transacted(() -> {
|
||||||
context("superuser-alex@hostsharing.net");
|
context("superuser-alex@hostsharing.net");
|
||||||
givenBookingItem.setResources(Map.ofEntries(
|
final var foundBookingItem = em.find(HsBookingItemEntity.class, givenBookingItemUuid);
|
||||||
entry("CPUs", 2),
|
foundBookingItem.getResources().put("CPUs", 2);
|
||||||
entry("SSD-storage", 512),
|
foundBookingItem.getResources().remove("SSD-storage");
|
||||||
entry("HDD-storage", 2048)));
|
foundBookingItem.getResources().put("HSD-storage", 2048);
|
||||||
givenBookingItem.setValidity(Range.closedOpen(
|
foundBookingItem.setValidity(Range.closedOpen(
|
||||||
LocalDate.parse("2019-05-17"), LocalDate.parse("2023-01-01")));
|
LocalDate.parse("2019-05-17"), LocalDate.parse("2023-01-01")));
|
||||||
return toCleanup(bookingItemRepo.save(givenBookingItem));
|
return toCleanup(bookingItemRepo.save(foundBookingItem));
|
||||||
});
|
});
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@ -312,7 +312,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
|||||||
.caption("some temp booking item")
|
.caption("some temp booking item")
|
||||||
.validity(Range.closedOpen(
|
.validity(Range.closedOpen(
|
||||||
LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
|
LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
|
||||||
.resources(Map.ofEntries(
|
.resources(patchableMap(
|
||||||
entry("CPUs", 1),
|
entry("CPUs", 1),
|
||||||
entry("SSD-storage", 256)))
|
entry("SSD-storage", 256)))
|
||||||
.build();
|
.build();
|
||||||
|
Loading…
Reference in New Issue
Block a user