1
0

introduce-booking-project-and-nested-booking-items (#57)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/57
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-06-03 14:45:28 +02:00
parent 23a6f89943
commit c23baca47a
54 changed files with 2437 additions and 495 deletions

View File

@ -34,13 +34,13 @@ public class HsBookingItemController implements HsBookingItemsApi {
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsBookingItemResource>> listBookingItemsByDebitorUuid(
public ResponseEntity<List<HsBookingItemResource>> listBookingItemsByProjectUuid(
final String currentUser,
final String assumedRoles,
final UUID debitorUuid) {
final UUID projectUuid) {
context.define(currentUser, assumedRoles);
final var entities = bookingItemRepo.findAllByDebitorUuid(debitorUuid);
final var entities = bookingItemRepo.findAllByProjectUuid(projectUuid);
final var resources = mapper.mapList(entities, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources);

View File

@ -9,8 +9,7 @@ import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.validation.Validatable;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
@ -38,14 +37,12 @@ import java.util.Map;
import java.util.UUID;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
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.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.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;
@ -55,7 +52,6 @@ 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.stringify.Stringify.stringify;
@ -69,7 +65,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable<HsBookingItemEntity, HsBookingItemType> {
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
.withProp(HsBookingItemEntity::getDebitor)
.withProp(HsBookingItemEntity::getProject)
.withProp(HsBookingItemEntity::getType)
.withProp(e -> e.getValidity().asString())
.withProp(HsBookingItemEntity::getCaption)
@ -83,9 +79,13 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
@Version
private int version;
@ManyToOne(optional = false)
@JoinColumn(name = "debitoruuid")
private HsOfficeDebitorEntity debitor;
@ManyToOne
@JoinColumn(name = "projectuuid")
private HsBookingProjectEntity project;
@ManyToOne
@JoinColumn(name = "parentitemuuid")
private HsBookingItemEntity parentItem;
@Column(name = "type")
@Enumerated(EnumType.STRING)
@ -139,10 +139,17 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
@Override
public String toShortString() {
return ofNullable(debitor).map(HsOfficeDebitorEntity::toShortString).orElse("D-???????") +
return ofNullable(relatedProject()).map(HsBookingProjectEntity::toShortString).orElse("D-???????-?") +
":" + caption;
}
private HsBookingProjectEntity relatedProject() {
if (project != null) {
return project;
}
return parentItem == null ? null : parentItem.relatedProject();
}
@Override
public String getPropertiesName() {
return "resources";
@ -155,48 +162,42 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
public static RbacView rbac() {
return rbacViewFor("bookingItem", HsBookingItemEntity.class)
.withIdentityView(SQL.query("""
SELECT bookingItem.uuid as uuid, debitorIV.idName || '-' || cleanIdentifier(bookingItem.caption) as idName
FROM hs_booking_item bookingItem
JOIN hs_office_debitor_iv debitorIV ON debitorIV.uuid = bookingItem.debitorUuid
"""))
.withIdentityView(SQL.projection("caption"))
.withRestrictedViewOrderBy(SQL.expression("validity"))
.withUpdatableColumns("version", "caption", "validity", "resources")
.importEntityAlias("debitor", HsOfficeDebitorEntity.class, usingDefaultCase(),
dependsOnColumn("debitorUuid"),
directlyFetchedByDependsOnColumn(),
NOT_NULL)
.importEntityAlias("debitorRel", HsOfficeRelationEntity.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(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("debitorRel", AGENT);
with.incomingSuperRole("project", AGENT);
with.incomingSuperRole("parentItem", AGENT);
})
.createSubRole(ADMIN, (with) -> {
with.incomingSuperRole("debitorRel", AGENT);
with.permission(UPDATE);
})
.createSubRole(AGENT)
.createSubRole(TENANT, (with) -> {
with.outgoingSubRole("debitorRel", TENANT);
with.outgoingSubRole("project", TENANT);
with.outgoingSubRole("parentItem", TENANT);
with.permission(SELECT);
})
.limitDiagramTo("bookingItem", "debitorRel", "global");
.limitDiagramTo("bookingItem", "project", "global");
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("6-hs-booking/601-booking-item/6013-hs-booking-item-rbac");
rbac().generateWithBaseFileName("6-hs-booking/620-booking-item/6203-hs-booking-item-rbac");
}
}

View File

@ -11,7 +11,7 @@ public interface HsBookingItemRepository extends Repository<HsBookingItemEntity,
List<HsBookingItemEntity> findAll();
Optional<HsBookingItemEntity> findByUuid(final UUID bookingItemUuid);
List<HsBookingItemEntity> findAllByDebitorUuid(final UUID bookingItemUuid);
List<HsBookingItemEntity> findAllByProjectUuid(final UUID projectItemUuid);
HsBookingItemEntity save(HsBookingItemEntity current);

View File

@ -0,0 +1,114 @@
package net.hostsharing.hsadminng.hs.booking.project;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingProjectsApi;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectInsertResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectResource;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import java.util.List;
import java.util.UUID;
@RestController
public class HsBookingProjectController implements HsBookingProjectsApi {
@Autowired
private Context context;
@Autowired
private Mapper mapper;
@Autowired
private HsBookingProjectRepository bookingProjectRepo;
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsBookingProjectResource>> listBookingProjectsByDebitorUuid(
final String currentUser,
final String assumedRoles,
final UUID debitorUuid) {
context.define(currentUser, assumedRoles);
final var entities = bookingProjectRepo.findAllByDebitorUuid(debitorUuid);
final var resources = mapper.mapList(entities, HsBookingProjectResource.class);
return ResponseEntity.ok(resources);
}
@Override
@Transactional
public ResponseEntity<HsBookingProjectResource> addBookingProject(
final String currentUser,
final String assumedRoles,
final HsBookingProjectInsertResource body) {
context.define(currentUser, assumedRoles);
final var entityToSave = mapper.map(body, HsBookingProjectEntity.class);
final var saved = bookingProjectRepo.save(entityToSave);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/hs/booking/projects/{id}")
.buildAndExpand(saved.getUuid())
.toUri();
final var mapped = mapper.map(saved, HsBookingProjectResource.class);
return ResponseEntity.created(uri).body(mapped);
}
@Override
@Transactional(readOnly = true)
public ResponseEntity<HsBookingProjectResource> getBookingProjectByUuid(
final String currentUser,
final String assumedRoles,
final UUID bookingProjectUuid) {
context.define(currentUser, assumedRoles);
final var result = bookingProjectRepo.findByUuid(bookingProjectUuid);
return result
.map(bookingProjectEntity -> ResponseEntity.ok(
mapper.map(bookingProjectEntity, HsBookingProjectResource.class)))
.orElseGet(() -> ResponseEntity.notFound().build());
}
@Override
@Transactional
public ResponseEntity<Void> deleteBookingIemByUuid(
final String currentUser,
final String assumedRoles,
final UUID bookingProjectUuid) {
context.define(currentUser, assumedRoles);
final var result = bookingProjectRepo.deleteByUuid(bookingProjectUuid);
return result == 0
? ResponseEntity.notFound().build()
: ResponseEntity.noContent().build();
}
@Override
@Transactional
public ResponseEntity<HsBookingProjectResource> patchBookingProject(
final String currentUser,
final String assumedRoles,
final UUID bookingProjectUuid,
final HsBookingProjectPatchResource body) {
context.define(currentUser, assumedRoles);
final var current = bookingProjectRepo.findByUuid(bookingProjectUuid).orElseThrow();
new HsBookingProjectEntityPatcher(current).apply(body);
final var saved = bookingProjectRepo.save(current);
final var mapped = mapper.map(saved, HsBookingProjectResource.class);
return ResponseEntity.ok(mapped);
}
}

View File

@ -0,0 +1,113 @@
package net.hostsharing.hsadminng.hs.booking.project;
import lombok.*;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
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.Stringifyable;
import jakarta.persistence.*;
import java.io.IOException;
import java.util.UUID;
import static java.util.Optional.ofNullable;
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.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
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.stringify.Stringify.stringify;
@Builder
@Entity
@Table(name = "hs_booking_project_rv")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsBookingProjectEntity implements Stringifyable, RbacObject {
private static Stringify<HsBookingProjectEntity> stringify = stringify(HsBookingProjectEntity.class)
.withProp(HsBookingProjectEntity::getDebitor)
.withProp(HsBookingProjectEntity::getCaption)
.quotedValues(false);
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@ManyToOne(optional = false)
@JoinColumn(name = "debitoruuid")
private HsOfficeDebitorEntity debitor;
@Column(name = "caption")
private String caption;
@Override
public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return ofNullable(debitor).map(HsOfficeDebitorEntity::toShortString).orElse("D-???????") +
":" + caption;
}
public static RbacView rbac() {
return rbacViewFor("project", HsBookingProjectEntity.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", HsOfficeRelationEntity.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);
})
.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/610-booking-project/6103-hs-booking-project-rbac");
}
}

View File

@ -0,0 +1,22 @@
package net.hostsharing.hsadminng.hs.booking.project;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
public class HsBookingProjectEntityPatcher implements EntityPatcher<HsBookingProjectPatchResource> {
private final HsBookingProjectEntity entity;
public HsBookingProjectEntityPatcher(final HsBookingProjectEntity entity) {
this.entity = entity;
}
@Override
public void apply(final HsBookingProjectPatchResource resource) {
OptionalFromJson.of(resource.getCaption())
.ifPresent(entity::setCaption);
}
}

View File

@ -0,0 +1,21 @@
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> {
List<HsBookingProjectEntity> findAll();
Optional<HsBookingProjectEntity> findByUuid(final UUID bookingProjectUuid);
List<HsBookingProjectEntity> findAllByDebitorUuid(final UUID bookingProjectUuid);
HsBookingProjectEntity save(HsBookingProjectEntity current);
int deleteByUuid(final UUID uuid);
long count();
}

View File

@ -78,14 +78,14 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
public ResponseEntity<HsHostingAssetResource> getAssetByUuid(
final String currentUser,
final String assumedRoles,
final UUID serverUuid) {
final UUID assetUuid) {
context.define(currentUser, assumedRoles);
final var result = assetRepo.findByUuid(serverUuid);
final var result = assetRepo.findByUuid(assetUuid);
return result
.map(serverEntity -> ResponseEntity.ok(
mapper.map(serverEntity, HsHostingAssetResource.class)))
.map(assetEntity -> ResponseEntity.ok(
mapper.map(assetEntity, HsHostingAssetResource.class)))
.orElseGet(() -> ResponseEntity.notFound().build());
}
@ -94,10 +94,10 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
public ResponseEntity<Void> deleteAssetUuid(
final String currentUser,
final String assumedRoles,
final UUID serverUuid) {
final UUID assetUuid) {
context.define(currentUser, assumedRoles);
final var result = assetRepo.deleteByUuid(serverUuid);
final var result = assetRepo.deleteByUuid(assetUuid);
return result == 0
? ResponseEntity.notFound().build()
: ResponseEntity.noContent().build();
@ -108,12 +108,12 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
public ResponseEntity<HsHostingAssetResource> patchAsset(
final String currentUser,
final String assumedRoles,
final UUID serverUuid,
final UUID assetUuid,
final HsHostingAssetPatchResource body) {
context.define(currentUser, assumedRoles);
final var current = assetRepo.findByUuid(serverUuid).orElseThrow();
final var current = assetRepo.findByUuid(assetUuid).orElseThrow();
new HsHostingAssetEntityPatcher(current).apply(body);

View File

@ -33,14 +33,11 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inCaseOf;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inOtherCases;
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.GLOBAL;
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;
@ -79,11 +76,11 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
@Version
private int version;
@ManyToOne(optional = false)
@ManyToOne
@JoinColumn(name = "bookingitemuuid")
private HsBookingItemEntity bookingItem;
@ManyToOne(optional = true)
@ManyToOne
@JoinColumn(name = "parentassetuuid")
private HsHostingAssetEntity parentAsset;
@ -136,47 +133,39 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
public static RbacView rbac() {
return rbacViewFor("asset", HsHostingAssetEntity.class)
.withIdentityView(SQL.query("""
SELECT asset.uuid as uuid, bookingItemIV.idName || '-' || cleanIdentifier(asset.identifier) as idName
FROM hs_hosting_asset asset
JOIN hs_booking_item_iv bookingItemIV ON bookingItemIV.uuid = asset.bookingItemUuid
"""))
.withIdentityView(SQL.projection("identifier"))
.withRestrictedViewOrderBy(SQL.expression("identifier"))
.withUpdatableColumns("version", "caption", "config")
.toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
.importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(),
dependsOnColumn("bookingItemUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("bookingItem", AGENT).grantPermission(INSERT)
.switchOnColumn("type",
inCaseOf(CLOUD_SERVER.name(),
then -> then.toRole("bookingItem", AGENT).grantPermission(INSERT)),
inCaseOf(MANAGED_SERVER.name(),
then -> then.toRole("bookingItem", AGENT).grantPermission(INSERT)),
inCaseOf(MANAGED_WEBSPACE.name(), then ->
then.importEntityAlias("parentServer", HsHostingAssetEntity.class, usingCase(MANAGED_SERVER),
dependsOnColumn("parentAssetUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("parentServer", ADMIN).grantPermission(INSERT)
.toRole("bookingItem", AGENT).grantPermission(INSERT)
),
inOtherCases(then -> {})
)
.importEntityAlias("parentAsset", HsHostingAssetEntity.class, usingCase(MANAGED_SERVER),
dependsOnColumn("parentAssetUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("parentAsset", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.incomingSuperRole("bookingItem", ADMIN);
with.incomingSuperRole("parentAsset", ADMIN);
with.permission(DELETE);
})
.createSubRole(ADMIN, (with) -> {
with.incomingSuperRole("bookingItem", AGENT);
with.incomingSuperRole("parentAsset", AGENT);
with.permission(UPDATE);
})
.createSubRole(AGENT)
.createSubRole(TENANT, (with) -> {
with.outgoingSubRole("bookingItem", TENANT);
with.outgoingSubRole("parentAsset", TENANT);
with.permission(SELECT);
})
.limitDiagramTo("asset", "bookingItem", "bookingItem.debitorRel", "parentServer", "global");
}

View File

@ -15,13 +15,13 @@ public interface HsHostingAssetRepository extends Repository<HsHostingAssetEntit
@Query("""
SELECT asset FROM HsHostingAssetEntity asset
WHERE (:debitorUuid IS NULL OR asset.bookingItem.debitor.uuid = :debitorUuid)
WHERE (:projectUuid IS NULL OR asset.bookingItem.project.uuid = :projectUuid)
AND (:parentAssetUuid IS NULL OR asset.parentAsset.uuid = :parentAssetUuid)
AND (:type IS NULL OR :type = CAST(asset.type AS String))
""")
List<HsHostingAssetEntity> findAllByCriteriaImpl(UUID debitorUuid, UUID parentAssetUuid, String type);
default List<HsHostingAssetEntity> findAllByCriteria(final UUID debitorUuid, final UUID parentAssetUuid, final HsHostingAssetType type) {
return findAllByCriteriaImpl(debitorUuid, parentAssetUuid, HsHostingAssetType.asString(type));
List<HsHostingAssetEntity> findAllByCriteriaImpl(UUID projectUuid, UUID parentAssetUuid, String type);
default List<HsHostingAssetEntity> findAllByCriteria(final UUID projectUuid, final UUID parentAssetUuid, final HsHostingAssetType type) {
return findAllByCriteriaImpl(projectUuid, parentAssetUuid, HsHostingAssetType.asString(type));
}
HsHostingAssetEntity save(HsHostingAssetEntity current);

View File

@ -26,7 +26,7 @@ class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator<HsHosting
}
private static void validateIdentifierPattern(final List<String> result, final HsHostingAssetEntity assetEntity) {
final var expectedIdentifierPattern = "^" + assetEntity.getParentAsset().getBookingItem().getDebitor().getDefaultPrefix() + "[0-9][0-9]$";
final var expectedIdentifierPattern = "^" + assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix() + "[0-9][0-9]$";
if ( !assetEntity.getIdentifier().matches(expectedIdentifierPattern)) {
result.add("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'");
}

View File

@ -150,7 +150,7 @@ public class InsertTriggerGenerator {
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into ${rawSubTable} not allowed regardless of current subject, no insert permissions grated at all';
raise exception '[403] insert into ${rawSubTable} values(%) not allowed regardless of current subject, no insert permissions granted at all', NEW;
end; $$;
create trigger ${rawSubTable}_insert_permission_check_tg
@ -254,8 +254,8 @@ public class InsertTriggerGenerator {
private void generateInsertPermissionsChecksFooter(final StringWriter plPgSql) {
plPgSql.writeLn();
plPgSql.writeLn("""
raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
raise exception '[403] insert into ${rawSubTable} values(%) not allowed for current subjects % (%)',
NEW, currentSubjects(), currentSubjectsUuids();
end; $$;
create trigger ${rawSubTable}_insert_permission_check_tg