introduce-hosting-module (#46)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/46 Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
@ -0,0 +1,124 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetsApi;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
||||
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;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
|
||||
@RestController
|
||||
public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
@Autowired
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private Mapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsHostingAssetRepository assetRepo;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<List<HsHostingAssetResource>> listAssetsByDebitorUuid(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final UUID debitorUuid) {
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var entities = assetRepo.findAllByDebitorUuid(debitorUuid);
|
||||
|
||||
final var resources = mapper.mapList(entities, HsHostingAssetResource.class);
|
||||
return ResponseEntity.ok(resources);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<HsHostingAssetResource> addAsset(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final HsHostingAssetInsertResource body) {
|
||||
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var entityToSave = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var saved = assetRepo.save(entityToSave);
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
.path("/api/hs/hosting/assets/{id}")
|
||||
.buildAndExpand(saved.getUuid())
|
||||
.toUri();
|
||||
final var mapped = mapper.map(saved, HsHostingAssetResource.class);
|
||||
return ResponseEntity.created(uri).body(mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<HsHostingAssetResource> getAssetByUuid(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final UUID serverUuid) {
|
||||
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = assetRepo.findByUuid(serverUuid);
|
||||
return result
|
||||
.map(serverEntity -> ResponseEntity.ok(
|
||||
mapper.map(serverEntity, HsHostingAssetResource.class)))
|
||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<Void> deleteAssetUuid(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final UUID serverUuid) {
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = assetRepo.deleteByUuid(serverUuid);
|
||||
return result == 0
|
||||
? ResponseEntity.notFound().build()
|
||||
: ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<HsHostingAssetResource> patchAsset(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final UUID serverUuid,
|
||||
final HsHostingAssetPatchResource body) {
|
||||
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var current = assetRepo.findByUuid(serverUuid).orElseThrow();
|
||||
|
||||
new HsHostingAssetEntityPatcher(current).apply(body);
|
||||
|
||||
final var saved = assetRepo.save(current);
|
||||
final var mapped = mapper.map(saved, HsHostingAssetResource.class);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final BiConsumer<HsHostingAssetInsertResource, HsHostingAssetEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.putConfig(KeyValueMap.from(resource.getConfig()));
|
||||
};
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import io.hypersistence.utils.hibernate.type.json.JsonType;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
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.RbacObject;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
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.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;
|
||||
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;
|
||||
|
||||
@Builder
|
||||
@Entity
|
||||
@Table(name = "hs_hosting_asset_rv")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject {
|
||||
|
||||
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
||||
.withProp(HsHostingAssetEntity::getBookingItem)
|
||||
.withProp(HsHostingAssetEntity::getType)
|
||||
.withProp(HsHostingAssetEntity::getParentAsset)
|
||||
.withProp(HsHostingAssetEntity::getIdentifier)
|
||||
.withProp(HsHostingAssetEntity::getCaption)
|
||||
.withProp(HsHostingAssetEntity::getConfig)
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "bookingitemuuid")
|
||||
private HsBookingItemEntity bookingItem;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
@JoinColumn(name = "parentassetuuid")
|
||||
private HsHostingAssetEntity parentAsset;
|
||||
|
||||
@Column(name = "type")
|
||||
@Enumerated(EnumType.STRING)
|
||||
private HsHostingAssetType type;
|
||||
|
||||
@Column(name = "identifier")
|
||||
private String identifier; // vm1234, xyz00, example.org, xyz00_abc
|
||||
|
||||
@Column(name = "caption")
|
||||
private String caption;
|
||||
|
||||
@Builder.Default
|
||||
@Setter(AccessLevel.NONE)
|
||||
@Type(JsonType.class)
|
||||
@Column(columnDefinition = "config")
|
||||
private Map<String, Object> config = new HashMap<>();
|
||||
|
||||
@Transient
|
||||
private PatchableMapWrapper configWrapper;
|
||||
|
||||
public PatchableMapWrapper getConfig() {
|
||||
if ( configWrapper == null ) {
|
||||
configWrapper = new PatchableMapWrapper(config);
|
||||
}
|
||||
return configWrapper;
|
||||
}
|
||||
|
||||
public void putConfig(Map<String, Object> entries) {
|
||||
if ( configWrapper == null ) {
|
||||
configWrapper = new PatchableMapWrapper(config);
|
||||
}
|
||||
configWrapper.assign(entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return ofNullable(bookingItem).map(HsBookingItemEntity::toShortString).orElse("D-???????:?") +
|
||||
":" + identifier;
|
||||
}
|
||||
|
||||
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
|
||||
"""))
|
||||
.withRestrictedViewOrderBy(SQL.expression("identifier"))
|
||||
.withUpdatableColumns("version", "caption", "config")
|
||||
|
||||
.importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(),
|
||||
dependsOnColumn("bookingItemUuid"),
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
NOT_NULL)
|
||||
|
||||
.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)
|
||||
// TODO.rbac: implement multiple INSERT-rules, e.g. for Asset.bookingItem + Asset.parentAsset
|
||||
//.toRole("parentServer", AGENT).grantPermission(INSERT)
|
||||
)
|
||||
)
|
||||
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.incomingSuperRole("bookingItem", ADMIN);
|
||||
with.permission(DELETE);
|
||||
})
|
||||
.createSubRole(ADMIN, (with) -> {
|
||||
with.permission(UPDATE);
|
||||
})
|
||||
.createSubRole(TENANT, (with) -> {
|
||||
with.outgoingSubRole("bookingItem", TENANT);
|
||||
with.permission(SELECT);
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
rbac().generateWithBaseFileName("7-hs-hosting/701-hosting-asset/7013-hs-hosting-asset-rbac");
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetPatchResource;
|
||||
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
||||
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class HsHostingAssetEntityPatcher implements EntityPatcher<HsHostingAssetPatchResource> {
|
||||
|
||||
private final HsHostingAssetEntity entity;
|
||||
|
||||
public HsHostingAssetEntityPatcher(final HsHostingAssetEntity entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(final HsHostingAssetPatchResource resource) {
|
||||
OptionalFromJson.of(resource.getCaption())
|
||||
.ifPresent(entity::setCaption);
|
||||
Optional.ofNullable(resource.getConfig())
|
||||
.ifPresent(r -> entity.getConfig().patch(KeyValueMap.from(resource.getConfig())));
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsHostingAssetRepository extends Repository<HsHostingAssetEntity, UUID> {
|
||||
|
||||
List<HsHostingAssetEntity> findAll();
|
||||
Optional<HsHostingAssetEntity> findByUuid(final UUID serverUuid);
|
||||
|
||||
@Query("""
|
||||
SELECT s FROM HsHostingAssetEntity s
|
||||
WHERE s.bookingItem.debitor.uuid = :debitorUuid
|
||||
""")
|
||||
List<HsHostingAssetEntity> findAllByDebitorUuid(final UUID debitorUuid);
|
||||
|
||||
HsHostingAssetEntity save(HsHostingAssetEntity current);
|
||||
|
||||
int deleteByUuid(final UUID uuid);
|
||||
|
||||
long count();
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
public enum HsHostingAssetType {
|
||||
CLOUD_SERVER, // named e.g. vm1234
|
||||
MANAGED_SERVER, // named e.g. vm1234
|
||||
MANAGED_WEBSPACE(MANAGED_SERVER), // named eg. xyz00
|
||||
UNIX_USER(MANAGED_WEBSPACE), // named e.g. xyz00-abc
|
||||
DOMAIN_SETUP(UNIX_USER), // named e.g. example.org
|
||||
|
||||
// TODO.spec: SECURE_MX
|
||||
EMAIL_ALIAS(MANAGED_WEBSPACE), // named e.g. xyz00-abc
|
||||
EMAIL_ADDRESS(DOMAIN_SETUP), // named e.g. sample@example.org
|
||||
PGSQL_USER(MANAGED_WEBSPACE), // named e.g. xyz00_abc
|
||||
PGSQL_DATABASE(MANAGED_WEBSPACE), // named e.g. xyz00_abc, TODO.spec: or PGSQL_USER?
|
||||
MARIADB_USER(MANAGED_WEBSPACE), // named e.g. xyz00_abc
|
||||
MARIADB_DATABASE(MANAGED_WEBSPACE); // named e.g. xyz00_abc, TODO.spec: or MARIADB_USER?
|
||||
|
||||
|
||||
public final HsHostingAssetType parentAssetType;
|
||||
|
||||
HsHostingAssetType(final HsHostingAssetType parentAssetType) {
|
||||
this.parentAssetType = parentAssetType;
|
||||
}
|
||||
|
||||
HsHostingAssetType() {
|
||||
this(null);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user