real rbac-entities in booking+hosting (#89)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #89
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-08-21 06:18:36 +02:00
parent 2138b3eed0
commit 1eaeade155
86 changed files with 1950 additions and 1499 deletions

View File

@ -0,0 +1,172 @@
package net.hostsharing.hsadminng.hs.booking.item;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacobject.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
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.OneToMany;
import jakarta.persistence.PostLoad;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static java.util.Collections.emptyMap;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@MappedSuperclass
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@SuperBuilder(builderMethodName = "baseBuilder", toBuilder = true)
public abstract class HsBookingItem implements Stringifyable, BaseEntity<HsBookingItem>, PropertiesProvider {
private static Stringify<HsBookingItem> stringify = stringify(HsBookingItem.class)
.withProp(HsBookingItem::getType)
.withProp(HsBookingItem::getCaption)
.withProp(HsBookingItem::getProject)
.withProp(e -> e.getValidity().asString())
.withProp(HsBookingItem::getResources)
.quotedValues(false);
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "projectuuid")
private HsBookingProjectRealEntity project;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parentitemuuid")
private HsBookingItemRealEntity parentItem;
@NotNull
@Column(name = "type")
@Enumerated(EnumType.STRING)
private HsBookingItemType type;
@Builder.Default
@Type(PostgreSQLRangeType.class)
@Column(name = "validity", columnDefinition = "daterange")
private Range<LocalDate> validity = Range.closedInfinite(LocalDate.now());
@Column(name = "caption")
private String caption;
@Builder.Default
@Setter(AccessLevel.NONE)
@Type(JsonType.class)
@Column(columnDefinition = "resources")
private Map<String, Object> resources = new HashMap<>();
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name = "parentitemuuid", referencedColumnName = "uuid")
private List<HsBookingItemRealEntity> subBookingItems;
@Transient
private PatchableMapWrapper<Object> resourcesWrapper;
@Transient
private boolean isLoaded;
@PostLoad
public void markAsLoaded() {
this.isLoaded = true;
}
public PatchableMapWrapper<Object> getResources() {
return PatchableMapWrapper.of(resourcesWrapper, (newWrapper) -> {resourcesWrapper = newWrapper;}, resources);
}
public void putResources(Map<String, Object> newResources) {
getResources().assign(newResources);
}
public void setValidFrom(final LocalDate validFrom) {
setValidity(toPostgresDateRange(validFrom, getValidTo()));
}
public void setValidTo(final LocalDate validTo) {
setValidity(toPostgresDateRange(getValidFrom(), validTo));
}
public LocalDate getValidFrom() {
return lowerInclusiveFromPostgresDateRange(getValidity());
}
public LocalDate getValidTo() {
return upperInclusiveFromPostgresDateRange(getValidity());
}
@Override
public PatchableMapWrapper<Object> directProps() {
return getResources();
}
@Override
public Object getContextValue(final String propName) {
final var v = resources.get(propName);
if (v != null) {
return v;
}
if (parentItem != null) {
return parentItem.getResources().get(propName);
}
return emptyMap();
}
@Override
public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return ofNullable(getRelatedProject()).map(HsBookingProject::toShortString).orElse("D-???????-?") +
":" + caption;
}
public HsBookingProject getRelatedProject() {
return project != null ? project
: parentItem != null ? parentItem.getRelatedProject()
: null; // can be the case for technical assets like IP-numbers
}
}

View File

@ -33,7 +33,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
private Mapper mapper;
@Autowired
private HsBookingItemRepository bookingItemRepo;
private HsBookingItemRbacRepository bookingItemRepo;
@PersistenceContext
private EntityManager em;
@ -61,9 +61,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
context.define(currentUser, assumedRoles);
final var entityToSave = mapper.map(body, HsBookingItemEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var entityToSave = mapper.map(body, HsBookingItemRbacEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = HsBookingItemEntityValidatorRegistry.validated(bookingItemRepo.save(entityToSave));
final var saved = HsBookingItemEntityValidatorRegistry.validated(em, bookingItemRepo.save(entityToSave));
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
@ -119,19 +119,19 @@ public class HsBookingItemController implements HsBookingItemsApi {
new HsBookingItemEntityPatcher(current).apply(body);
final var saved = bookingItemRepo.save(HsBookingItemEntityValidatorRegistry.validated(current));
final var saved = bookingItemRepo.save(HsBookingItemEntityValidatorRegistry.validated(em, current));
final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
final BiConsumer<HsBookingItemEntity, HsBookingItemResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
final BiConsumer<HsBookingItemRbacEntity, HsBookingItemResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setValidFrom(entity.getValidity().lower());
if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1));
}
};
final BiConsumer<HsBookingItemInsertResource, HsBookingItemEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
final BiConsumer<HsBookingItemInsertResource, HsBookingItemRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo()));
entity.putResources(KeyValueMap.from(resource.getResources()));
};

View File

@ -1,241 +0,0 @@
package net.hostsharing.hsadminng.hs.booking.item;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.rbac.rbacobject.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PostLoad;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import jakarta.validation.constraints.NotNull;
import java.io.IOException;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static java.util.Collections.emptyMap;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
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.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
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.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity
@Builder(toBuilder = true)
@Table(name = "hs_booking_item_rv")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsBookingItemEntity implements Stringifyable, BaseEntity<HsBookingItemEntity>, PropertiesProvider {
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
.withProp(HsBookingItemEntity::getType)
.withProp(HsBookingItemEntity::getCaption)
.withProp(HsBookingItemEntity::getProject)
.withProp(e -> e.getValidity().asString())
.withProp(HsBookingItemEntity::getResources)
.quotedValues(false);
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "projectuuid")
private HsBookingProjectEntity project;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parentitemuuid")
private HsBookingItemEntity parentItem;
@NotNull
@Column(name = "type")
@Enumerated(EnumType.STRING)
private HsBookingItemType type;
@Builder.Default
@Type(PostgreSQLRangeType.class)
@Column(name = "validity", columnDefinition = "daterange")
private Range<LocalDate> validity = Range.closedInfinite(LocalDate.now());
@Column(name = "caption")
private String caption;
@Builder.Default
@Setter(AccessLevel.NONE)
@Type(JsonType.class)
@Column(columnDefinition = "resources")
private Map<String, Object> resources = new HashMap<>();
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name="parentitemuuid", referencedColumnName="uuid")
private List<HsBookingItemEntity> subBookingItems;
@OneToOne(mappedBy="bookingItem")
private HsHostingAssetEntity relatedHostingAsset;
@Transient
private PatchableMapWrapper<Object> resourcesWrapper;
@Transient
private boolean isLoaded;
@PostLoad
public void markAsLoaded() {
this.isLoaded = true;
}
public PatchableMapWrapper<Object> getResources() {
return PatchableMapWrapper.of(resourcesWrapper, (newWrapper) -> {resourcesWrapper = newWrapper; }, resources );
}
public void putResources(Map<String, Object> newResources) {
getResources().assign(newResources);
}
public void setValidFrom(final LocalDate validFrom) {
setValidity(toPostgresDateRange(validFrom, getValidTo()));
}
public void setValidTo(final LocalDate validTo) {
setValidity(toPostgresDateRange(getValidFrom(), validTo));
}
public LocalDate getValidFrom() {
return lowerInclusiveFromPostgresDateRange(getValidity());
}
public LocalDate getValidTo() {
return upperInclusiveFromPostgresDateRange(getValidity());
}
@Override
public PatchableMapWrapper<Object> directProps() {
return getResources();
}
@Override
public Object getContextValue(final String propName) {
final var v = resources.get(propName);
if (v!= null) {
return v;
}
if (parentItem!=null) {
return parentItem.getResources().get(propName);
}
return emptyMap();
}
@Override
public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return ofNullable(relatedProject()).map(HsBookingProjectEntity::toShortString).orElse("D-???????-?") +
":" + caption;
}
private HsBookingProjectEntity relatedProject() {
if (project != null) {
return project;
}
return parentItem == null ? null : parentItem.relatedProject();
}
public HsBookingProjectEntity getRelatedProject() {
return project != null ? project
: parentItem != null ? parentItem.getRelatedProject()
: null; // can be the case for technical assets like IP-numbers
}
public static RbacView rbac() {
return rbacViewFor("bookingItem", HsBookingItemEntity.class)
.withIdentityView(SQL.projection("caption"))
.withRestrictedViewOrderBy(SQL.expression("validity"))
.withUpdatableColumns("version", "caption", "validity", "resources")
.toRole("global", ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
.toRole("global", ADMIN).grantPermission(DELETE)
.importEntityAlias("project", HsBookingProjectEntity.class, usingDefaultCase(),
dependsOnColumn("projectUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("project", ADMIN).grantPermission(INSERT)
.importEntityAlias("parentItem", HsBookingItemEntity.class, usingDefaultCase(),
dependsOnColumn("parentItemUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("parentItem", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.incomingSuperRole("project", AGENT);
with.incomingSuperRole("parentItem", AGENT);
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE);
})
.createSubRole(AGENT)
.createSubRole(TENANT, (with) -> {
with.outgoingSubRole("project", TENANT);
with.outgoingSubRole("parentItem", TENANT);
with.permission(SELECT);
})
.limitDiagramTo("bookingItem", "project", "global");
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("6-hs-booking/630-booking-item/6303-hs-booking-item-rbac");
}
}

View File

@ -10,9 +10,9 @@ import java.util.Optional;
public class HsBookingItemEntityPatcher implements EntityPatcher<HsBookingItemPatchResource> {
private final HsBookingItemEntity entity;
private final HsBookingItem entity;
public HsBookingItemEntityPatcher(final HsBookingItemEntity entity) {
public HsBookingItemEntityPatcher(final HsBookingItem entity) {
this.entity = entity;
}

View File

@ -0,0 +1,83 @@
package net.hostsharing.hsadminng.hs.booking.item;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.io.IOException;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
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.rbacViewFor;
@Entity
@Table(name = "hs_booking_item_rv")
@SuperBuilder(toBuilder = true)
@Getter
@Setter
@NoArgsConstructor
@AttributeOverrides({
@AttributeOverride(name = "uuid", column = @Column(name = "uuid"))
})
public class HsBookingItemRbacEntity extends HsBookingItem {
public static RbacView rbac() {
return rbacViewFor("bookingItem", HsBookingItemRbacEntity.class)
.withIdentityView(SQL.projection("caption"))
.withRestrictedViewOrderBy(SQL.expression("validity"))
.withUpdatableColumns("version", "caption", "validity", "resources")
.toRole("global", ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
.toRole("global", ADMIN).grantPermission(DELETE)
.importEntityAlias("project", HsBookingProject.class, usingDefaultCase(),
dependsOnColumn("projectUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("project", ADMIN).grantPermission(INSERT)
.importEntityAlias("parentItem", HsBookingItemRbacEntity.class, usingDefaultCase(),
dependsOnColumn("parentItemUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("parentItem", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.incomingSuperRole("project", AGENT);
with.incomingSuperRole("parentItem", AGENT);
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE);
})
.createSubRole(AGENT)
.createSubRole(TENANT, (with) -> {
with.outgoingSubRole("project", TENANT);
with.outgoingSubRole("parentItem", TENANT);
with.permission(SELECT);
})
.limitDiagramTo("bookingItem", "project", "global");
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("6-hs-booking/630-booking-item/6303-hs-booking-item-rbac");
}
}

View File

@ -0,0 +1,23 @@
package net.hostsharing.hsadminng.hs.booking.item;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsBookingItemRbacRepository extends HsBookingItemRepository<HsBookingItemRbacEntity>,
Repository<HsBookingItemRbacEntity, UUID> {
Optional<HsBookingItemRbacEntity> findByUuid(final UUID bookingItemUuid);
List<HsBookingItemRbacEntity> findByCaption(String bookingItemCaption);
List<HsBookingItemRbacEntity> findAllByProjectUuid(final UUID projectItemUuid);
HsBookingItemRbacEntity save(HsBookingItemRbacEntity current);
int deleteByUuid(final UUID uuid);
long count();
}

View File

@ -0,0 +1,24 @@
package net.hostsharing.hsadminng.hs.booking.item;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "hs_booking_item")
@SuperBuilder(toBuilder = true)
@Getter
@Setter
@NoArgsConstructor
@AttributeOverrides({
@AttributeOverride(name = "uuid", column = @Column(name = "uuid"))
})public class HsBookingItemRealEntity extends HsBookingItem {
}

View File

@ -0,0 +1,23 @@
package net.hostsharing.hsadminng.hs.booking.item;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsBookingItemRealRepository extends HsBookingItemRepository<HsBookingItemRealEntity>,
Repository<HsBookingItemRealEntity, UUID> {
Optional<HsBookingItemRealEntity> findByUuid(final UUID bookingItemUuid);
List<HsBookingItemRealEntity> findByCaption(String bookingItemCaption);
List<HsBookingItemRealEntity> findAllByProjectUuid(final UUID projectItemUuid);
HsBookingItemRealEntity save(HsBookingItemRealEntity current);
int deleteByUuid(final UUID uuid);
long count();
}

View File

@ -1,20 +1,18 @@
package net.hostsharing.hsadminng.hs.booking.item;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsBookingItemRepository extends Repository<HsBookingItemEntity, UUID> {
public interface HsBookingItemRepository<E extends HsBookingItem> {
Optional<HsBookingItemEntity> findByUuid(final UUID bookingItemUuid);
Optional<E> findByUuid(final UUID bookingItemUuid);
List<HsBookingItemEntity> findByCaption(String bookingItemCaption);
List<E> findByCaption(String bookingItemCaption);
List<HsBookingItemEntity> findAllByProjectUuid(final UUID projectItemUuid);
List<E> findAllByProjectUuid(final UUID projectItemUuid);
HsBookingItemEntity save(HsBookingItemEntity current);
E save(E current);
int deleteByUuid(final UUID uuid);

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import org.apache.commons.lang3.BooleanUtils;
@ -14,14 +14,14 @@ import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingItemEntity> {
public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingItem> {
public HsBookingItemEntityValidator(final ValidatableProperty<?, ?>... properties) {
super(properties);
}
@Override
public List<String> validateEntity(final HsBookingItemEntity bookingItem) {
public List<String> validateEntity(final HsBookingItem bookingItem) {
// TODO.impl: HsBookingItemType could do this similar to HsHostingAssetType
if ( bookingItem.getParentItem() == null && bookingItem.getProject() == null) {
return List.of(bookingItem + ".'parentItem' or .'project' expected to be set, but both are null");
@ -30,21 +30,21 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
}
@Override
public List<String> validateContext(final HsBookingItemEntity bookingItem) {
public List<String> validateContext(final HsBookingItem bookingItem) {
return sequentiallyValidate(
() -> optionallyValidate(bookingItem.getParentItem()),
() -> validateAgainstSubEntities(bookingItem)
);
}
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
private static List<String> optionallyValidate(final HsBookingItem bookingItem) {
return bookingItem != null
? enrich(prefix(bookingItem.toShortString(), ""),
HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem))
: emptyList();
}
protected List<String> validateAgainstSubEntities(final HsBookingItemEntity bookingItem) {
protected List<String> validateAgainstSubEntities(final HsBookingItem bookingItem) {
return enrich(prefix(bookingItem.toShortString(), "resources"),
Stream.concat(
stream(propertyValidators)
@ -58,7 +58,7 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
// TODO.refa: convert into generic shape like multi-options validator
private static String validateMaxTotalValue(
final HsBookingItemEntity bookingItem,
final HsBookingItem bookingItem,
final ValidatableProperty<?, ?> propDef) {
final var propName = propDef.propertyName();
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");

View File

@ -1,10 +1,11 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.errors.MultiValidationException;
import jakarta.persistence.EntityManager;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -18,7 +19,7 @@ import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVAT
public class HsBookingItemEntityValidatorRegistry {
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity>> validators = new HashMap<>();
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItem>> validators = new HashMap<>();
static {
register(PRIVATE_CLOUD, new HsPrivateCloudBookingItemValidator());
register(CLOUD_SERVER, new HsCloudServerBookingItemValidator());
@ -26,14 +27,14 @@ public class HsBookingItemEntityValidatorRegistry {
register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator());
}
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity> validator) {
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItem> validator) {
stream(validator.propertyValidators).forEach( entry -> {
entry.verifyConsistency(Map.entry(type, validator));
});
validators.put(type, validator);
}
public static HsEntityValidator<HsBookingItemEntity> forType(final Enum<HsBookingItemType> type) {
public static HsEntityValidator<HsBookingItem> forType(final Enum<HsBookingItemType> type) {
if ( validators.containsKey(type)) {
return validators.get(type);
}
@ -44,14 +45,16 @@ public class HsBookingItemEntityValidatorRegistry {
return validators.keySet();
}
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
return HsEntityValidator.sequentiallyValidate(
() -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateEntity(bookingItem),
() -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem));
public static List<String> doValidate(final EntityManager em, final HsBookingItem bookingItem) {
return HsEntityValidator.doWithEntityManager(em, () ->
HsEntityValidator.sequentiallyValidate(
() -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateEntity(bookingItem),
() -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem))
);
}
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
MultiValidationException.throwIfNotEmpty(doValidate(entityToSave));
public static <E extends HsBookingItem> E validated(final EntityManager em, final E entityToSave) {
MultiValidationException.throwIfNotEmpty(doValidate(em, entityToSave));
return entityToSave;
}
}

View File

@ -1,13 +1,15 @@
package net.hostsharing.hsadminng.hs.booking.item.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.IntegerProperty;
import org.apache.commons.lang3.function.TriFunction;
import java.util.List;
import java.util.Optional;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_MBOX_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ADDRESS;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_DATABASE;
@ -38,9 +40,9 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
);
}
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> unixUsers() {
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
private static TriFunction<HsBookingItem, IntegerProperty<?>, Integer, List<String>> unixUsers() {
return (final HsBookingItem entity, final IntegerProperty<?> prop, final Integer factor) -> {
final var unixUserCount = fetchRelatedBookingItem(entity)
.map(ha -> ha.getSubHostingAssets().stream()
.filter(subAsset -> subAsset.getType() == UNIX_USER)
.count())
@ -53,9 +55,9 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> databaseUsers() {
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
final var dbUserCount = ofNullable(entity.getRelatedHostingAsset())
private static TriFunction<HsBookingItem, IntegerProperty<?>, Integer, List<String>> databaseUsers() {
return (final HsBookingItem entity, final IntegerProperty<?> prop, final Integer factor) -> {
final var dbUserCount = fetchRelatedBookingItem(entity)
.map(ha -> ha.getSubHostingAssets().stream()
.filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER )
.count())
@ -68,9 +70,9 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> databases() {
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
private static TriFunction<HsBookingItem, IntegerProperty<?>, Integer, List<String>> databases() {
return (final HsBookingItem entity, final IntegerProperty<?> prop, final Integer factor) -> {
final var unixUserCount = fetchRelatedBookingItem(entity)
.map(ha -> ha.getSubHostingAssets().stream()
.filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER )
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
@ -85,9 +87,9 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
};
}
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> eMailAddresses() {
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
private static TriFunction<HsBookingItem, IntegerProperty<?>, Integer, List<String>> eMailAddresses() {
return (final HsBookingItem entity, final IntegerProperty<?> prop, final Integer factor) -> {
final var unixUserCount = fetchRelatedBookingItem(entity)
.map(ha -> ha.getSubHostingAssets().stream()
.filter(bi -> bi.getType() == DOMAIN_MBOX_SETUP)
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
@ -101,4 +103,13 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
return emptyList();
};
}
private static Optional<HsHostingAssetRealEntity> fetchRelatedBookingItem(final HsBookingItem entity) {
// TODO.perf: maybe we need to cache the result at least for a single valiationrun
return HsEntityValidator.localEntityManager.get().createQuery(
"SELECT asset FROM HsHostingAssetRealEntity asset WHERE asset.bookingItem.uuid=:bookingItemUuid",
HsHostingAssetRealEntity.class)
.setParameter("bookingItemUuid", entity.getUuid())
.getResultStream().findFirst(); // there are 0 or 1, never more
}
}

View File

@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.hs.booking.project;
import lombok.*;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
@ -27,18 +28,17 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Builder
@Entity
@Table(name = "hs_booking_project_rv")
@MappedSuperclass
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsBookingProjectEntity implements Stringifyable, BaseEntity<HsBookingProjectEntity> {
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@SuperBuilder(builderMethodName = "baseBuilder", toBuilder = true)
public abstract class HsBookingProject implements Stringifyable, BaseEntity<HsBookingProject> {
private static Stringify<HsBookingProjectEntity> stringify = stringify(HsBookingProjectEntity.class)
.withProp(HsBookingProjectEntity::getDebitor)
.withProp(HsBookingProjectEntity::getCaption)
private static Stringify<HsBookingProject> stringify = stringify(HsBookingProject.class)
.withProp(HsBookingProject::getDebitor)
.withProp(HsBookingProject::getCaption)
.quotedValues(false);
@Id
@ -67,7 +67,7 @@ public class HsBookingProjectEntity implements Stringifyable, BaseEntity<HsBooki
}
public static RbacView rbac() {
return rbacViewFor("project", HsBookingProjectEntity.class)
return rbacViewFor("project", HsBookingProject.class)
.withIdentityView(SQL.query("""
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || cleanIdentifier(bookingProject.caption) as idName
FROM hs_booking_project bookingProject

View File

@ -28,7 +28,7 @@ public class HsBookingProjectController implements HsBookingProjectsApi {
private Mapper mapper;
@Autowired
private HsBookingProjectRepository bookingProjectRepo;
private HsBookingProjectRbacRepository bookingProjectRepo;
@Autowired
private HsBookingDebitorRepository debitorRepo;
@ -56,7 +56,7 @@ public class HsBookingProjectController implements HsBookingProjectsApi {
context.define(currentUser, assumedRoles);
final var entityToSave = mapper.map(body, HsBookingProjectEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var entityToSave = mapper.map(body, HsBookingProjectRbacEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = bookingProjectRepo.save(entityToSave);
@ -118,7 +118,7 @@ public class HsBookingProjectController implements HsBookingProjectsApi {
return ResponseEntity.ok(mapped);
}
final BiConsumer<HsBookingProjectInsertResource, HsBookingProjectEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
final BiConsumer<HsBookingProjectInsertResource, HsBookingProjectRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
if (resource.getDebitorUuid() != null) {
entity.setDebitor(debitorRepo.findByUuid(resource.getDebitorUuid())
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] debitorUuid %s not found".formatted(

View File

@ -8,9 +8,9 @@ import net.hostsharing.hsadminng.mapper.OptionalFromJson;
public class HsBookingProjectEntityPatcher implements EntityPatcher<HsBookingProjectPatchResource> {
private final HsBookingProjectEntity entity;
private final HsBookingProject entity;
public HsBookingProjectEntityPatcher(final HsBookingProjectEntity entity) {
public HsBookingProjectEntityPatcher(final HsBookingProject entity) {
this.entity = entity;
}

View File

@ -0,0 +1,86 @@
package net.hostsharing.hsadminng.hs.booking.project;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.io.IOException;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
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;
@Entity
@Table(name = "hs_booking_project_rv")
@SuperBuilder(toBuilder = true)
@Getter
@Setter
@NoArgsConstructor
public class HsBookingProjectRbacEntity extends HsBookingProject {
public static RbacView rbac() {
return rbacViewFor("project", HsBookingProjectRbacEntity.class)
.withIdentityView(SQL.query("""
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || cleanIdentifier(bookingProject.caption) as idName
FROM hs_booking_project bookingProject
JOIN hs_office_debitor_iv debitorIV ON debitorIV.uuid = bookingProject.debitorUuid
"""))
.withRestrictedViewOrderBy(SQL.expression("caption"))
.withUpdatableColumns("version", "caption")
.importEntityAlias("debitor", HsOfficeDebitorEntity.class, usingDefaultCase(),
dependsOnColumn("debitorUuid"),
directlyFetchedByDependsOnColumn(),
NOT_NULL)
.importEntityAlias("debitorRel", HsOfficeRelationRbacEntity.class, usingCase(DEBITOR),
dependsOnColumn("debitorUuid"),
fetchedBySql("""
SELECT ${columns}
FROM hs_office_relation debitorRel
JOIN hs_office_debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
WHERE debitor.uuid = ${REF}.debitorUuid
"""),
NOT_NULL)
.toRole("debitorRel", ADMIN).grantPermission(INSERT)
.toRole("global", ADMIN).grantPermission(DELETE)
.createRole(OWNER, (with) -> {
with.incomingSuperRole("debitorRel", AGENT).unassumed();
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE);
})
.createSubRole(AGENT)
.createSubRole(TENANT, (with) -> {
with.outgoingSubRole("debitorRel", TENANT);
with.permission(SELECT);
})
.limitDiagramTo("project", "debitorRel", "global");
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("6-hs-booking/620-booking-project/6203-hs-booking-project-rbac");
}
}

View File

@ -0,0 +1,22 @@
package net.hostsharing.hsadminng.hs.booking.project;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsBookingProjectRbacRepository extends HsBookingProjectRepository<HsBookingProjectRbacEntity>,
Repository<HsBookingProjectRbacEntity, UUID> {
Optional<HsBookingProjectRbacEntity> findByUuid(final UUID bookingProjectUuid);
List<HsBookingProjectRbacEntity> findByCaption(final String projectCaption);
List<HsBookingProjectRbacEntity> findAllByDebitorUuid(final UUID bookingProjectUuid);
HsBookingProjectRbacEntity save(HsBookingProjectRbacEntity current);
int deleteByUuid(final UUID uuid);
long count();
}

View File

@ -0,0 +1,19 @@
package net.hostsharing.hsadminng.hs.booking.project;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "hs_booking_project")
@SuperBuilder(toBuilder = true)
@Getter
@Setter
@NoArgsConstructor
public class HsBookingProjectRealEntity extends HsBookingProject {
}

View File

@ -0,0 +1,22 @@
package net.hostsharing.hsadminng.hs.booking.project;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsBookingProjectRealRepository extends HsBookingProjectRepository<HsBookingProjectRealEntity>,
Repository<HsBookingProjectRealEntity, UUID> {
Optional<HsBookingProjectRealEntity> findByUuid(final UUID bookingProjectUuid);
List<HsBookingProjectRealEntity> findByCaption(final String projectCaption);
List<HsBookingProjectRealEntity> findAllByDebitorUuid(final UUID bookingProjectUuid);
HsBookingProjectRealEntity save(HsBookingProjectRealEntity current);
int deleteByUuid(final UUID uuid);
long count();
}

View File

@ -1,19 +1,17 @@
package net.hostsharing.hsadminng.hs.booking.project;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsBookingProjectRepository extends Repository<HsBookingProjectEntity, UUID> {
public interface HsBookingProjectRepository<E extends HsBookingProject> {
Optional<HsBookingProjectEntity> findByUuid(final UUID bookingProjectUuid);
List<HsBookingProjectEntity> findByCaption(final String projectCaption);
Optional<E> findByUuid(final UUID bookingProjectUuid);
List<E> findByCaption(final String projectCaption);
List<HsBookingProjectEntity> findAllByDebitorUuid(final UUID bookingProjectUuid);
List<E> findAllByDebitorUuid(final UUID bookingProjectUuid);
HsBookingProjectEntity save(HsBookingProjectEntity current);
E save(E current);
int deleteByUuid(final UUID uuid);

View File

@ -1,13 +1,40 @@
package net.hostsharing.hsadminng.hs.hosting.asset;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacobject.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
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.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PostLoad;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -16,9 +43,15 @@ import java.util.UUID;
import static java.util.Collections.emptyMap;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
public interface HsHostingAsset extends Stringifyable, BaseEntity<HsHostingAsset>, PropertiesProvider {
@MappedSuperclass
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@SuperBuilder(builderMethodName = "baseBuilder", toBuilder = true)
public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHostingAsset>, PropertiesProvider {
Stringify<HsHostingAsset> stringify = stringify(HsHostingAsset.class)
static Stringify<HsHostingAsset> stringify = stringify(HsHostingAsset.class)
.withProp(HsHostingAsset::getType)
.withProp(HsHostingAsset::getIdentifier)
.withProp(HsHostingAsset::getCaption)
@ -28,29 +61,83 @@ public interface HsHostingAsset extends Stringifyable, BaseEntity<HsHostingAsset
.withProp(HsHostingAsset::getConfig)
.quotedValues(false);
@Id
@GeneratedValue
private UUID uuid;
void setUuid(UUID uuid);
HsHostingAssetType getType();
HsHostingAsset getParentAsset();
void