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

View File

@ -32,7 +32,6 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; 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.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_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.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
@ -65,11 +64,11 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
public class HsHostingAssetEntity implements Stringifyable, RbacObject { public class HsHostingAssetEntity implements Stringifyable, RbacObject {
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class) private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
.withProp(HsHostingAssetEntity::getBookingItem)
.withProp(HsHostingAssetEntity::getType) .withProp(HsHostingAssetEntity::getType)
.withProp(HsHostingAssetEntity::getParentAsset)
.withProp(HsHostingAssetEntity::getIdentifier) .withProp(HsHostingAssetEntity::getIdentifier)
.withProp(HsHostingAssetEntity::getCaption) .withProp(HsHostingAssetEntity::getCaption)
.withProp(HsHostingAssetEntity::getParentAsset)
.withProp(HsHostingAssetEntity::getBookingItem)
.withProp(HsHostingAssetEntity::getConfig) .withProp(HsHostingAssetEntity::getConfig)
.quotedValues(false); .quotedValues(false);
@ -122,8 +121,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
@Override @Override
public String toShortString() { public String toShortString() {
return ofNullable(bookingItem).map(HsBookingItemEntity::toShortString).orElse("D-???????:?") + return type + ":" + identifier;
":" + identifier;
} }
public static RbacView rbac() { public static RbacView rbac() {

View File

@ -7,16 +7,22 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public interface HsHostingAssetRepository extends Repository<HsHostingAssetEntity, UUID> { public interface HsHostingAssetRepository extends Repository<HsHostingAssetEntity, UUID> {
List<HsHostingAssetEntity> findAll(); List<HsHostingAssetEntity> findAll();
Optional<HsHostingAssetEntity> findByUuid(final UUID serverUuid); Optional<HsHostingAssetEntity> findByUuid(final UUID serverUuid);
@Query(""" @Query("""
SELECT s FROM HsHostingAssetEntity s SELECT asset FROM HsHostingAssetEntity asset
WHERE s.bookingItem.debitor.uuid = :debitorUuid 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); HsHostingAssetEntity save(HsHostingAssetEntity current);

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.hs.hosting.asset; package net.hostsharing.hsadminng.hs.hosting.asset;
public enum HsHostingAssetType { public enum HsHostingAssetType {
CLOUD_SERVER, // named e.g. vm1234 CLOUD_SERVER, // named e.g. vm1234
MANAGED_SERVER, // named e.g. vm1234 MANAGED_SERVER, // named e.g. vm1234
@ -25,4 +26,12 @@ public enum HsHostingAssetType {
HsHostingAssetType() { HsHostingAssetType() {
this(null); 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: get:
summary: Returns a list of all hosting assets for a specified debitor. summary: Returns a filtered list of all hosting assets.
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. 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: tags:
- hs-hosting-assets - hs-hosting-assets
operationId: listAssetsByDebitorUuid operationId: listAssets
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentUser' - $ref: 'auth.yaml#/components/parameters/currentUser'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: debitorUuid - name: debitorUuid
in: query in: query
required: true required: false
schema: schema:
type: string type: string
format: uuid 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. description: The UUID of the debitor, whose hosting assets are to be listed.
responses: responses:
"200": "200":

View File

@ -97,7 +97,7 @@ $$;
create table RbacObject create table RbacObject
( (
uuid uuid primary key default uuid_generate_v4(), 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, objectTable varchar(64) not null,
unique (objectTable, uuid) unique (objectTable, uuid)
); );

View File

@ -101,6 +101,58 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
""")); """));
// @formatter:on // @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 @Nested
@ -274,7 +326,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get() assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get()
.matches(asset -> { .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; return true;
}); });
} }

View File

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