introduce-booking-project-and-nested-booking-items (#57)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #57 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
parent
23a6f89943
commit
c23baca47a
288
doc/projects-booking-items-and-hosting-entities.md
Normal file
288
doc/projects-booking-items-and-hosting-entities.md
Normal file
@ -0,0 +1,288 @@
|
||||
## HSAdmin-NG
|
||||
### Project/BookingItems/HostingEntities
|
||||
|
||||
__ATTENTION__: The notation uses UML clas diagram elements, but partly with different meanings. See Agenda.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
direction TD
|
||||
|
||||
Partner o-- "0..n" Membership
|
||||
Partner *-- "1..n" Debitor
|
||||
Debitor *-- "1..n" Project
|
||||
|
||||
Project o-- "0..n" PrivateCloudBI
|
||||
Project o-- "0..n" CloudServerBI
|
||||
Project o-- "0..n" ManagedServerBI
|
||||
Project o-- "0..n" ManagedWebspaceBI
|
||||
|
||||
PrivateCloudBI o-- "0..n" ManagedServerBI
|
||||
PrivateCloudBI o-- "0..n" CloudServerBI
|
||||
|
||||
CloudServerBI *-- CloudServerHE
|
||||
|
||||
ManagedServerBI *-- ManagedServerHE
|
||||
ManagedServerBI o-- "0..n" ManagedWebspaceBI
|
||||
ManagedWebspaceBI *-- ManagedWebspaceHE
|
||||
|
||||
ManagedWebspaceHE *-- "1..n" UnixUserHE
|
||||
ManagedWebspaceHE o-- "0..n" DomainDNSSetupHE
|
||||
ManagedWebspaceHE o-- "0..n" DomainHttpSetupHE
|
||||
ManagedWebspaceHE o-- "0..n" DomainEMailSetupHE
|
||||
ManagedWebspaceHE o-- "0..n" EMailAliasHE
|
||||
DomainEMailSetupHE o-- "0..n" EMailAddressHE
|
||||
ManagedWebspaceHE o-- "0..n" MariaDBUserHE
|
||||
MariaDBUserHE o-- "0..n" MariaDBHE
|
||||
ManagedWebspaceHE o-- "0..n" PostgresDBUserHE
|
||||
PostgresDBUserHE o-- "0..n" PostgresDBHE
|
||||
|
||||
DomainHttpSetupHE --|> UnixUserHE : assignedToAsset
|
||||
|
||||
ManagedWebspaceHE --|> ManagedServerHE
|
||||
|
||||
namespace Office {
|
||||
class Partner {
|
||||
}
|
||||
|
||||
class Membership {
|
||||
}
|
||||
|
||||
class Debitor {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
namespace Booking {
|
||||
class Project {
|
||||
+caption
|
||||
+create()
|
||||
}
|
||||
class PrivateCloudBI {
|
||||
+caption
|
||||
~resources = [
|
||||
⠀⠀+CPUs
|
||||
⠀⠀+RAM
|
||||
⠀⠀+SSD
|
||||
⠀⠀+HDD
|
||||
⠀⠀+Traffic
|
||||
]
|
||||
|
||||
+book()
|
||||
}
|
||||
class CloudServerBI {
|
||||
+caption
|
||||
~resources = [
|
||||
⠀⠀+CPUs
|
||||
⠀⠀+RAM
|
||||
⠀⠀+SSD
|
||||
⠀⠀+HDD
|
||||
⠀⠀+Traffic
|
||||
]
|
||||
|
||||
+book()
|
||||
}
|
||||
class ManagedServerBI {
|
||||
+caption
|
||||
~respources = [
|
||||
⠀⠀+CPUs
|
||||
⠀⠀+RAM
|
||||
⠀⠀+SSD
|
||||
⠀⠀+HDD
|
||||
⠀⠀+Traffic
|
||||
]
|
||||
|
||||
+book()
|
||||
}
|
||||
class ManagedWebspaceBI {
|
||||
+caption
|
||||
~resources = [
|
||||
⠀⠀+SSD
|
||||
⠀⠀+HDD
|
||||
⠀⠀+Traffic
|
||||
⠀⠀+MultiOptions
|
||||
⠀⠀+Daemons
|
||||
]
|
||||
|
||||
+book()
|
||||
}
|
||||
}
|
||||
|
||||
style Project stroke:blue,stroke-width:4px
|
||||
style PrivateCloudBI stroke:blue,stroke-width:4px
|
||||
style CloudServerBI stroke:blue,stroke-width:4px
|
||||
style ManagedServerBI stroke:blue,stroke-width:4px
|
||||
style ManagedWebspaceBI stroke:blue,stroke-width:4px
|
||||
|
||||
%% ---------------------------------------------------------
|
||||
|
||||
namespace HostingServers {
|
||||
%% separate (pseudo-) namespace just for better rendering
|
||||
|
||||
class CloudServerHE {
|
||||
-identifier, e.g. "vm1234"
|
||||
-caption := bi.caption?
|
||||
-parentAsset := parentHost
|
||||
-identifier := serverName
|
||||
-create()
|
||||
}
|
||||
class ManagedServerHE {
|
||||
-identifier, e.g. "vm1234"
|
||||
-caption := bi.caption?
|
||||
-parentAsset := parentHost
|
||||
-identifier := serverName
|
||||
~config = [
|
||||
⠀⠀+installed Software
|
||||
]
|
||||
-create()
|
||||
}
|
||||
}
|
||||
|
||||
namespace Hosting {
|
||||
class ManagedWebspaceHE {
|
||||
-parentAsset := parentManagedServer
|
||||
-identifier : webspaceName
|
||||
+caption
|
||||
|
||||
-create()
|
||||
}
|
||||
|
||||
class UnixUserHE {
|
||||
+identifier ["xyz00-..."]
|
||||
+caption
|
||||
~config = [
|
||||
⠀⠀+SSD Soft Quota
|
||||
⠀⠀+SSD Hard Quota
|
||||
⠀⠀+HDD Soft Quota
|
||||
⠀⠀+HDD Hard Quota
|
||||
⠀⠀#shell
|
||||
⠀⠀#password
|
||||
]
|
||||
|
||||
+create()
|
||||
}
|
||||
class DomainDNSSetupHE {
|
||||
+identifier, e.g. "example.com"
|
||||
+caption
|
||||
|
||||
+create()
|
||||
}
|
||||
class DomainHttpSetupHE {
|
||||
+identifier, e.g. "example.com"
|
||||
+caption
|
||||
|
||||
+create()
|
||||
}
|
||||
class DomainEMailSetupHE {
|
||||
+identifier, e.g. "example.com"
|
||||
+caption
|
||||
|
||||
+create()
|
||||
}
|
||||
class EMailAliasHE {
|
||||
+identifier, e.g "xyz00-..."
|
||||
+caption
|
||||
|
||||
~config = [
|
||||
⠀⠀+target[]
|
||||
]
|
||||
|
||||
+create()
|
||||
}
|
||||
class EMailAddressHE {
|
||||
+identifier, e.g. "test@example.org"
|
||||
+caption
|
||||
~config = [
|
||||
⠀⠀+sub-domain
|
||||
⠀⠀+local-part
|
||||
⠀⠀+target
|
||||
]
|
||||
|
||||
+create()
|
||||
}
|
||||
class MariaDBUserHE {
|
||||
+identifier, e.g. "xyz00_mydb"
|
||||
+caption
|
||||
config = [
|
||||
⠀⠀#password
|
||||
]
|
||||
|
||||
+create()
|
||||
}
|
||||
class MariaDBHE {
|
||||
+identifier, e.g. "xyz00_mydb"
|
||||
+caption
|
||||
~config = [
|
||||
⠀⠀+encoding
|
||||
]
|
||||
|
||||
+create()
|
||||
}
|
||||
class PostgresDBUserHE {
|
||||
+identifier, e.g. "xyz00_mydb"
|
||||
+caption
|
||||
~config = [
|
||||
⠀⠀#password
|
||||
]
|
||||
|
||||
+create()
|
||||
}
|
||||
class PostgresDBHE {
|
||||
+identifier, e.g. "xyz00_mydb"
|
||||
+caption
|
||||
|
||||
~config = [
|
||||
⠀⠀+encoding
|
||||
⠀⠀+extensions
|
||||
]
|
||||
+create()
|
||||
}
|
||||
}
|
||||
|
||||
style CloudServerHE stroke:orange,stroke-width:4px
|
||||
style ManagedServerHE stroke:orange,stroke-width:4px
|
||||
style ManagedWebspaceHE stroke:orange,stroke-width:4px
|
||||
style UnixUserHE stroke:blue,stroke-width:4px
|
||||
style DomainDNSSetupHE stroke:blue,stroke-width:4px
|
||||
style DomainHttpSetupHE stroke:blue,stroke-width:4px
|
||||
style DomainEMailSetupHE stroke:blue,stroke-width:4px
|
||||
style EMailAliasHE stroke:blue,stroke-width:4px
|
||||
style EMailAddressHE stroke:blue,stroke-width:4px
|
||||
style MariaDBUserHE stroke:blue,stroke-width:4px
|
||||
style MariaDBHE stroke:blue,stroke-width:4px
|
||||
style PostgresDBUserHE stroke:blue,stroke-width:4px
|
||||
style PostgresDBHE stroke:blue,stroke-width:4px
|
||||
|
||||
%% --------------------------------------
|
||||
|
||||
ParentA o-- ChildA : can contain
|
||||
ParentB *-- ChildB : contains
|
||||
|
||||
namespace Agenda {
|
||||
class ParentA {
|
||||
}
|
||||
class ChildA {
|
||||
}
|
||||
class ParentB {
|
||||
}
|
||||
class ChildB {
|
||||
}
|
||||
class CreatedByClient {
|
||||
}
|
||||
class CreatedAutomatically {
|
||||
}
|
||||
class SomeEntity {
|
||||
~patchable = [
|
||||
%% the following indentations uses two U+2800 to have effect in the rendered diagram
|
||||
⠀⠀+first
|
||||
⠀⠀+second
|
||||
]
|
||||
-readOnly for client accounts
|
||||
+readWrite for client accounts
|
||||
#writeOnly
|
||||
}
|
||||
}
|
||||
|
||||
style CreatedByClient stroke:blue,stroke-width:4px
|
||||
style CreatedAutomatically stroke:orange,stroke-width:4px
|
||||
end
|
||||
```
|
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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() + "'");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -13,5 +13,7 @@ map:
|
||||
- type: string:uuid => java.util.UUID
|
||||
|
||||
paths:
|
||||
/api/hs/booking/projects/{bookingProjectUuid}:
|
||||
null: org.openapitools.jackson.nullable.JsonNullable
|
||||
/api/hs/booking/items/{bookingItemUuid}:
|
||||
null: org.openapitools.jackson.nullable.JsonNullable
|
||||
|
@ -51,7 +51,7 @@ components:
|
||||
HsBookingItemInsert:
|
||||
type: object
|
||||
properties:
|
||||
debitorUuid:
|
||||
projectUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: false
|
||||
@ -74,7 +74,7 @@ components:
|
||||
$ref: '#/components/schemas/BookingResources'
|
||||
required:
|
||||
- caption
|
||||
- debitorUuid
|
||||
- projectUuid
|
||||
- validFrom
|
||||
- resources
|
||||
additionalProperties: false
|
||||
|
@ -1,19 +1,19 @@
|
||||
get:
|
||||
summary: Returns a list of all booking items for a specified debitor.
|
||||
description: Returns the list of all booking items for a specified debitor which are visible to the current user or any of it's assumed roles.
|
||||
summary: Returns a list of all booking items for a specified project.
|
||||
description: Returns the list of all booking items for a specified project which are visible to the current user or any of it's assumed roles.
|
||||
tags:
|
||||
- hs-booking-items
|
||||
operationId: listBookingItemsByDebitorUuid
|
||||
operationId: listBookingItemsByProjectUuid
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentUser'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
- name: debitorUuid
|
||||
- name: projectUuid
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: The UUID of the debitor, whose booking items are to be listed.
|
||||
description: The UUID of the project, whose booking items are to be listed.
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
|
@ -0,0 +1,40 @@
|
||||
|
||||
components:
|
||||
|
||||
schemas:
|
||||
|
||||
HsBookingProject:
|
||||
type: object
|
||||
properties:
|
||||
uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
caption:
|
||||
type: string
|
||||
required:
|
||||
- uuid
|
||||
- caption
|
||||
|
||||
HsBookingProjectPatch:
|
||||
type: object
|
||||
properties:
|
||||
caption:
|
||||
type: string
|
||||
nullable: true
|
||||
|
||||
HsBookingProjectInsert:
|
||||
type: object
|
||||
properties:
|
||||
debitorUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: false
|
||||
caption:
|
||||
type: string
|
||||
minLength: 3
|
||||
maxLength: 80
|
||||
nullable: false
|
||||
required:
|
||||
- debitorUuid
|
||||
- caption
|
||||
additionalProperties: false
|
@ -0,0 +1,83 @@
|
||||
get:
|
||||
tags:
|
||||
- hs-booking-projects
|
||||
description: 'Fetch a single booking project its uuid, if visible for the current subject.'
|
||||
operationId: getBookingProjectByUuid
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentUser'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
- name: bookingProjectUuid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the booking project to fetch.
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: 'hs-booking-project-schemas.yaml#/components/schemas/HsBookingProject'
|
||||
|
||||
"401":
|
||||
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
|
||||
"403":
|
||||
$ref: 'error-responses.yaml#/components/responses/Forbidden'
|
||||
|
||||
patch:
|
||||
tags:
|
||||
- hs-booking-projects
|
||||
description: 'Updates a single booking project identified by its uuid, if permitted for the current subject.'
|
||||
operationId: patchBookingProject
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentUser'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
- name: bookingProjectUuid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: 'hs-booking-project-schemas.yaml#/components/schemas/HsBookingProjectPatch'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: 'hs-booking-project-schemas.yaml#/components/schemas/HsBookingProject'
|
||||
"401":
|
||||
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
|
||||
"403":
|
||||
$ref: 'error-responses.yaml#/components/responses/Forbidden'
|
||||
|
||||
delete:
|
||||
tags:
|
||||
- hs-booking-projects
|
||||
description: 'Delete a single booking project identified by its uuid, if permitted for the current subject.'
|
||||
operationId: deleteBookingIemByUuid
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentUser'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
- name: bookingProjectUuid
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: UUID of the booking project to delete.
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"401":
|
||||
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
|
||||
"403":
|
||||
$ref: 'error-responses.yaml#/components/responses/Forbidden'
|
||||
"404":
|
||||
$ref: 'error-responses.yaml#/components/responses/NotFound'
|
@ -0,0 +1,58 @@
|
||||
get:
|
||||
summary: Returns a list of all booking projects for a specified debitor.
|
||||
description: Returns the list of all booking projects for a specified debitor which are visible to the current user or any of it's assumed roles.
|
||||
tags:
|
||||
- hs-booking-projects
|
||||
operationId: listBookingProjectsByDebitorUuid
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentUser'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
- name: debitorUuid
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: The UUID of the debitor, whose booking projects are to be listed.
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: 'hs-booking-project-schemas.yaml#/components/schemas/HsBookingProject'
|
||||
"401":
|
||||
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
|
||||
"403":
|
||||
$ref: 'error-responses.yaml#/components/responses/Forbidden'
|
||||
|
||||
post:
|
||||
summary: Adds a new project as a container for booking items.
|
||||
tags:
|
||||
- hs-booking-projects
|
||||
operationId: addBookingProject
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentUser'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
requestBody:
|
||||
description: A JSON object describing the new booking project.
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: 'hs-booking-project-schemas.yaml#/components/schemas/HsBookingProjectInsert'
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
$ref: 'hs-booking-project-schemas.yaml#/components/schemas/HsBookingProject'
|
||||
"401":
|
||||
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
|
||||
"403":
|
||||
$ref: 'error-responses.yaml#/components/responses/Forbidden'
|
||||
"409":
|
||||
$ref: 'error-responses.yaml#/components/responses/Conflict'
|
@ -8,6 +8,15 @@ servers:
|
||||
|
||||
paths:
|
||||
|
||||
# Projects
|
||||
|
||||
/api/hs/booking/projects:
|
||||
$ref: "hs-booking-projects.yaml"
|
||||
|
||||
/api/hs/booking/projects/{bookingProjectUuid}:
|
||||
$ref: "hs-booking-projects-with-uuid.yaml"
|
||||
|
||||
|
||||
# Items
|
||||
|
||||
/api/hs/booking/items:
|
||||
|
@ -0,0 +1,22 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
-- ============================================================================
|
||||
--changeset booking-project-MAIN-TABLE:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
create table if not exists hs_booking_project
|
||||
(
|
||||
uuid uuid unique references RbacObject (uuid),
|
||||
version int not null default 0,
|
||||
debitorUuid uuid not null references hs_office_debitor(uuid),
|
||||
caption varchar(80) not null
|
||||
);
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset hs-booking-project-MAIN-TABLE-JOURNAL:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
call create_journal('hs_booking_project');
|
||||
--//
|
@ -0,0 +1,63 @@
|
||||
### rbac project
|
||||
|
||||
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
|
||||
|
||||
```mermaid
|
||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||
flowchart TB
|
||||
|
||||
subgraph debitorRel["`**debitorRel**`"]
|
||||
direction TB
|
||||
style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||
|
||||
subgraph debitorRel:roles[ ]
|
||||
style debitorRel:roles fill:#99bcdb,stroke:white
|
||||
|
||||
role:debitorRel:OWNER[[debitorRel:OWNER]]
|
||||
role:debitorRel:ADMIN[[debitorRel:ADMIN]]
|
||||
role:debitorRel:AGENT[[debitorRel:AGENT]]
|
||||
role:debitorRel:TENANT[[debitorRel:TENANT]]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph project["`**project**`"]
|
||||
direction TB
|
||||
style project fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||
|
||||
subgraph project:roles[ ]
|
||||
style project:roles fill:#dd4901,stroke:white
|
||||
|
||||
role:project:OWNER[[project:OWNER]]
|
||||
role:project:ADMIN[[project:ADMIN]]
|
||||
role:project:AGENT[[project:AGENT]]
|
||||
role:project:TENANT[[project:TENANT]]
|
||||
end
|
||||
|
||||
subgraph project:permissions[ ]
|
||||
style project:permissions fill:#dd4901,stroke:white
|
||||
|
||||
perm:project:INSERT{{project:INSERT}}
|
||||
perm:project:DELETE{{project:DELETE}}
|
||||
perm:project:UPDATE{{project:UPDATE}}
|
||||
perm:project:SELECT{{project:SELECT}}
|
||||
end
|
||||
end
|
||||
|
||||
%% granting roles to roles
|
||||
role:global:ADMIN -.-> role:debitorRel:OWNER
|
||||
role:debitorRel:OWNER -.-> role:debitorRel:ADMIN
|
||||
role:debitorRel:ADMIN -.-> role:debitorRel:AGENT
|
||||
role:debitorRel:AGENT -.-> role:debitorRel:TENANT
|
||||
role:debitorRel:AGENT ==> role:project:OWNER
|
||||
role:project:OWNER ==> role:project:ADMIN
|
||||
role:project:ADMIN ==> role:project:AGENT
|
||||
role:project:AGENT ==> role:project:TENANT
|
||||
role:project:TENANT ==> role:debitorRel:TENANT
|
||||
|
||||
%% granting permissions to roles
|
||||
role:debitorRel:ADMIN ==> perm:project:INSERT
|
||||
role:global:ADMIN ==> perm:project:DELETE
|
||||
role:project:ADMIN ==> perm:project:UPDATE
|
||||
role:project:TENANT ==> perm:project:SELECT
|
||||
|
||||
```
|
@ -3,29 +3,29 @@
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset hs-booking-item-rbac-OBJECT:1 endDelimiter:--//
|
||||
--changeset hs-booking-project-rbac-OBJECT:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
call generateRelatedRbacObject('hs_booking_item');
|
||||
call generateRelatedRbacObject('hs_booking_project');
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset hs-booking-item-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
|
||||
--changeset hs-booking-project-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
call generateRbacRoleDescriptors('hsBookingItem', 'hs_booking_item');
|
||||
call generateRbacRoleDescriptors('hsBookingProject', 'hs_booking_project');
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset hs-booking-item-rbac-insert-trigger:1 endDelimiter:--//
|
||||