list hosting-assets with debitor, parent and type query-parameters (#52)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #52
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-05-03 10:28:03 +02:00
parent 1201c16094
commit a93c097f64
9 changed files with 127 additions and 33 deletions

View File

@ -6,6 +6,7 @@ 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.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
import net.hostsharing.hsadminng.mapper.KeyValueMap;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
@ -33,13 +34,15 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsHostingAssetResource>> listAssetsByDebitorUuid(
public ResponseEntity<List<HsHostingAssetResource>> listAssets(
final String currentUser,
final String assumedRoles,
final UUID debitorUuid) {
final UUID debitorUuid,
final UUID parentAssetUuid,
final HsHostingAssetTypeResource type) {
context.define(currentUser, assumedRoles);
final var entities = assetRepo.findAllByDebitorUuid(debitorUuid);
final var entities = assetRepo.findAllByCriteria(debitorUuid, parentAssetUuid, HsHostingAssetType.of(type));
final var resources = mapper.mapList(entities, HsHostingAssetResource.class);
return ResponseEntity.ok(resources);

View File

@ -32,7 +32,6 @@ 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;
@ -65,11 +64,11 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
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::getParentAsset)
.withProp(HsHostingAssetEntity::getBookingItem)
.withProp(HsHostingAssetEntity::getConfig)
.quotedValues(false);
@ -122,8 +121,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
@Override
public String toShortString() {
return ofNullable(bookingItem).map(HsBookingItemEntity::toShortString).orElse("D-???????:?") +
":" + identifier;
return type + ":" + identifier;
}
public static RbacView rbac() {

View File

@ -7,16 +7,22 @@ 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
SELECT asset FROM HsHostingAssetEntity asset
WHERE (:debitorUuid IS NULL OR asset.bookingItem.debitor.uuid = :debitorUuid)
AND (:parentAssetUuid IS NULL OR asset.parentAsset.uuid = :parentAssetUuid)
AND (:type IS NULL OR :type = CAST(asset.type AS String))
""")
List<HsHostingAssetEntity> findAllByDebitorUuid(final UUID debitorUuid);
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));
}
HsHostingAssetEntity save(HsHostingAssetEntity current);

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.hs.hosting.asset;
public enum HsHostingAssetType {
CLOUD_SERVER, // named e.g. vm1234
MANAGED_SERVER, // named e.g. vm1234
@ -25,4 +26,12 @@ public enum HsHostingAssetType {
HsHostingAssetType() {
this(null);
}
public static <T extends Enum<?>> HsHostingAssetType of(final T value) {
return value == null ? null : valueOf(value.name());
}
static String asString(final HsHostingAssetType type) {
return type == null ? null : type.name();
}
}

View File

@ -1,18 +1,29 @@
get:
summary: Returns a list of all hosting assets for a specified debitor.
description: Returns the list of all hosting assets for a debitor which are visible to the current user or any of it's assumed roles.
summary: Returns a filtered list of all hosting assets.
description: Returns the list of all hosting assets which match the given filters and are visible to the current user or any of it's assumed roles.
tags:
- hs-hosting-assets
operationId: listAssetsByDebitorUuid
operationId: listAssets
parameters:
- $ref: 'auth.yaml#/components/parameters/currentUser'
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: debitorUuid
in: query
required: true
required: false
schema:
type: string
format: uuid
- name: parentAssetUuid
in: query
required: false
schema:
type: string
format: uuid
- name: type
in: query
required: false
schema:
$ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetType'
description: The UUID of the debitor, whose hosting assets are to be listed.
responses:
"200":

View File

@ -97,7 +97,7 @@ $$;
create table RbacObject
(
uuid uuid primary key default uuid_generate_v4(),
serialId serial, -- TODO: we might want to remove this once test data deletion works properly
serialId serial, -- TODO.perf: only needed for reverse deletion of temp test data
objectTable varchar(64) not null,
unique (objectTable, uuid)
);

View File

@ -101,6 +101,58 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"""));
// @formatter:on
}
@Test
void globalAdmin_canViewAllAssetsByType() {
// given
context("superuser-alex@hostsharing.net");
RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/hs/hosting/assets?type=" + HsHostingAssetType.MANAGED_SERVER)
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
[
{
"type": "MANAGED_SERVER",
"identifier": "vm1011",
"caption": "some ManagedServer",
"config": {
"CPU": 2,
"SDD": 512,
"extra": 42
}
},
{
"type": "MANAGED_SERVER",
"identifier": "vm1013",
"caption": "some ManagedServer",
"config": {
"CPU": 2,
"SDD": 512,
"extra": 42
}
},
{
"type": "MANAGED_SERVER",
"identifier": "vm1012",
"caption": "some ManagedServer",
"config": {
"CPU": 2,
"SDD": 512,
"extra": 42
}
}
]
"""));
// @formatter:on
}
}
@Nested
@ -274,7 +326,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
context.define("superuser-alex@hostsharing.net");
assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get()
.matches(asset -> {
assertThat(asset.toString()).isEqualTo("HsHostingAssetEntity(D-1000111:some CloudServer, CLOUD_SERVER, vm2001, some test-asset, { CPU: 4, SSD: 4096, something: 1 })");
assertThat(asset.toString()).isEqualTo("HsHostingAssetEntity(CLOUD_SERVER, vm2001, some test-asset, D-1000111:some CloudServer, { CPU: 4, SSD: 4096, something: 1 })");
return true;
});
}

View File

@ -37,13 +37,13 @@ class HsHostingAssetEntityUnitTest {
final var result = givenServer.toString();
assertThat(result).isEqualTo(
"HsHostingAssetEntity(D-1000100:test booking item, MANAGED_WEBSPACE, D-1000100:test booking item:vm1234, xyz00, some managed webspace, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })");
"HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1000100:test booking item, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })");
}
@Test
void toShortStringContainsOnlyMemberNumberAndCaption() {
final var result = givenServer.toShortString();
assertThat(result).isEqualTo("D-1000100:test booking item:xyz00");
assertThat(result).isEqualTo("MANAGED_WEBSPACE:xyz00");
}
}

View File

@ -27,6 +27,7 @@ import java.util.Map;
import static java.util.Map.entry;
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.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.hsadminng.rbac.test.Array.fromFormatted;
@ -77,7 +78,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
.bookingItem(givenManagedServer.getBookingItem())
.parentAsset(givenManagedServer)
.caption("some new managed webspace")
.type(HsHostingAssetType.MANAGED_WEBSPACE)
.type(MANAGED_WEBSPACE)
.identifier("xyz90")
.build();
return toCleanup(assetRepo.save(newAsset));
@ -151,21 +152,19 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
class FindByDebitorUuid {
@Test
public void globalAdmin_withoutAssumedRole_canViewAllAssetsOfArbitraryDebitor() {
public void globalAdmin_withoutAssumedRole_canViewArbitraryAssetsOfAllDebitors() {
// given
context("superuser-alex@hostsharing.net");
final var debitorUuid = debitorRepo.findDebitorByDebitorNumber(1000212).stream()
.findAny().orElseThrow().getUuid();
// when
final var result = assetRepo.findAllByDebitorUuid(debitorUuid);
final var result = assetRepo.findAllByCriteria(null, null, MANAGED_WEBSPACE);
// then
allTheseServersAreReturned(
result,
"HsHostingAssetEntity(D-1000212:some ManagedServer, MANAGED_WEBSPACE, D-1000212:some PrivateCloud:vm1012, bbb01, some Webspace, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000212:some PrivateCloud, MANAGED_SERVER, vm1012, some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000212:some PrivateCloud, CLOUD_SERVER, vm2012, another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })");
"HsHostingAssetEntity(MANAGED_WEBSPACE, bbb01, some Webspace, MANAGED_SERVER:vm1012, D-1000212:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(MANAGED_WEBSPACE, aaa01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(MANAGED_WEBSPACE, ccc01, some Webspace, MANAGED_SERVER:vm1013, D-1000313:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })");
}
@Test
@ -175,15 +174,32 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
final var debitorUuid = debitorRepo.findDebitorByDebitorNumber(1000111).stream().findAny().orElseThrow().getUuid();
// when:
final var result = assetRepo.findAllByDebitorUuid(debitorUuid);
final var result = assetRepo.findAllByCriteria(debitorUuid, null, null);
// then:
exactlyTheseAssetsAreReturned(
result,
"HsHostingAssetEntity(D-1000111:some ManagedServer, MANAGED_WEBSPACE, D-1000111:some PrivateCloud:vm1011, aaa01, some Webspace, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000111:some PrivateCloud, MANAGED_SERVER, vm1011, some ManagedServer, { CPU: 2, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(D-1000111:some PrivateCloud, CLOUD_SERVER, vm2011, another CloudServer, { CPU: 2, HDD: 1024, extra: 42 })");
"HsHostingAssetEntity(MANAGED_WEBSPACE, aaa01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:some PrivateCloud, { CPU: 2, SDD: 512, extra: 42 })",
"HsHostingAssetEntity(CLOUD_SERVER, vm2011, another CloudServer, D-1000111:some PrivateCloud, { CPU: 2, HDD: 1024, extra: 42 })");
}
@Test
public void normalUser_canFilterAssetsRelatedToParentAsset() {
// given
context("superuser-alex@hostsharing.net");
final var parentAssetUuid = assetRepo.findAllByCriteria(null, null, MANAGED_SERVER).stream()
.findAny().orElseThrow().getUuid();
// when
final var result = assetRepo.findAllByCriteria(null, parentAssetUuid, null);
// then
allTheseServersAreReturned(
result,
"HsHostingAssetEntity(MANAGED_WEBSPACE, aaa01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:some ManagedServer, { HDD: 2048, RAM: 1, SDD: 512, extra: 42 })");
}
}
@Nested
@ -356,8 +372,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
HsHostingAssetEntity givenManagedServer(final String debitorName, final HsHostingAssetType type) {
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).stream().findAny().orElseThrow();
return assetRepo.findAllByDebitorUuid(givenDebitor.getUuid()).stream()
.filter(i -> i.getType().equals(type))
return assetRepo.findAllByCriteria(givenDebitor.getUuid(), null, type).stream()
.findAny().orElseThrow();
}