From 344511fb5242518d2a351a27da4b41d1f0162360 Mon Sep 17 00:00:00 2001 From: JordenReuter <149687553+JordenReuter@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:33:58 +0100 Subject: [PATCH] feat: added full impl workspace details endpoint (#5) --- pom.xml | 13 +++ .../controllers/WorkspaceRestController.java | 40 ++++++++ .../bff/rs/mappers/WorkspaceMapper.java | 23 +++++ src/main/openapi/openapi-bff.yaml | 70 ++++++++++--- src/main/resources/application.properties | 9 +- .../rs/WorkspaceRestControllerTest.java | 99 +++++++++++++++++++ 6 files changed, 241 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index b121f24..262f22c 100644 --- a/pom.xml +++ b/pom.xml @@ -193,6 +193,19 @@ true + + product-store-svc-external-v1 + generate-resources + + wget + + + https://raw.githubusercontent.com/onecx/onecx-product-store-svc/main/src/main/openapi/onecx-product-store-v1.yaml + target/tmp/openapi + onecx-product-store-svc-v1.yaml + true + + diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/WorkspaceRestController.java b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/WorkspaceRestController.java index f322a63..15c77b6 100644 --- a/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/WorkspaceRestController.java +++ b/src/main/java/org/tkit/onecx/permission/bff/rs/controllers/WorkspaceRestController.java @@ -1,5 +1,8 @@ package org.tkit.onecx.permission.bff.rs.controllers; +import java.util.Arrays; +import java.util.List; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -10,9 +13,14 @@ import org.tkit.quarkus.log.cdi.LogService; import gen.org.tkit.onecx.permission.bff.rs.internal.WorkspaceApiService; +import gen.org.tkit.onecx.permission.bff.rs.internal.model.WorkspaceDetailsDTO; import gen.org.tkit.onecx.permission.client.api.ProductExternalApi; import gen.org.tkit.onecx.permission.client.api.WorkspaceExternalApi; import gen.org.tkit.onecx.permission.client.model.Product; +import gen.org.tkit.onecx.permission.client.model.Workspace; +import gen.org.tkit.onecx.product.store.client.api.ProductsApi; +import gen.org.tkit.onecx.product.store.client.model.ProductItemLoadSearchCriteria; +import gen.org.tkit.onecx.product.store.client.model.ProductsLoadResult; @ApplicationScoped @Transactional(value = Transactional.TxType.NOT_SUPPORTED) @@ -27,6 +35,10 @@ public class WorkspaceRestController implements WorkspaceApiService { @Inject ProductExternalApi productClient; + @RestClient + @Inject + ProductsApi productStoreClient; + @Inject WorkspaceMapper mapper; @@ -43,4 +55,32 @@ public Response getAllWorkspaceNames() { return Response.status(response.getStatus()).entity(response.readEntity(String[].class)).build(); } } + + @Override + public Response getDetailsByWorkspaceName(String workspaceName) { + try (Response response = workspaceClient.getWorkspaceByName(workspaceName)) { + WorkspaceDetailsDTO workspaceDetails; + List productNames; + List workspaceRoles; + ProductsLoadResult productsLoadResult; + var workspaceResponse = response.readEntity(Workspace.class); + workspaceRoles = workspaceResponse.getWorkspaceRoles().stream().toList(); + + //get products of workspace + try (Response wsProductsResponse = productClient.getProducts(workspaceName)) { + //list of product names registered in workspace + productNames = Arrays.stream(wsProductsResponse.readEntity(Product[].class)) + .map(Product::getProductName).toList(); + + //get mfe and ms for each product by name from product-store + ProductItemLoadSearchCriteria mfeAndMsCriteria = new ProductItemLoadSearchCriteria(); + mfeAndMsCriteria.setProductNames(productNames); + try (Response productStoreResponse = productStoreClient.loadProductsByCriteria(mfeAndMsCriteria)) { + productsLoadResult = productStoreResponse.readEntity(ProductsLoadResult.class); + } + workspaceDetails = mapper.map(workspaceRoles, productsLoadResult); + } + return Response.status(Response.Status.OK).entity(workspaceDetails).build(); + } + } } diff --git a/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/WorkspaceMapper.java b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/WorkspaceMapper.java index 1980aa5..e8a15f2 100644 --- a/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/WorkspaceMapper.java +++ b/src/main/java/org/tkit/onecx/permission/bff/rs/mappers/WorkspaceMapper.java @@ -1,14 +1,37 @@ package org.tkit.onecx.permission.bff.rs.mappers; +import java.util.List; + import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; import gen.org.tkit.onecx.permission.bff.rs.internal.model.ProductDTO; +import gen.org.tkit.onecx.permission.bff.rs.internal.model.ProductDetailsDTO; +import gen.org.tkit.onecx.permission.bff.rs.internal.model.WorkspaceDetailsDTO; import gen.org.tkit.onecx.permission.client.model.Product; +import gen.org.tkit.onecx.product.store.client.model.ProductsAbstract; +import gen.org.tkit.onecx.product.store.client.model.ProductsLoadResult; @Mapper(uses = { OffsetDateTimeMapper.class }) public interface WorkspaceMapper { ProductDTO[] map(Product[] products); ProductDTO map(Product product); + + @Mapping(target = "removeMsItem", ignore = true) + @Mapping(target = "removeMfeItem", ignore = true) + @Mapping(target = "productName", source = "name") + @Mapping(target = "ms", source = "microservices") + @Mapping(target = "mfe", source = "microfrontends") + ProductDetailsDTO map(ProductsAbstract productsAbstract); + + List map(List productsAbstracts); + + default WorkspaceDetailsDTO map(List workspaceRoles, ProductsLoadResult productsLoadResult) { + WorkspaceDetailsDTO workspaceDetailsDTO = new WorkspaceDetailsDTO(); + workspaceDetailsDTO.setWorkspaceRoles(workspaceRoles); + workspaceDetailsDTO.setProducts(map(productsLoadResult.getStream())); + return workspaceDetailsDTO; + } } diff --git a/src/main/openapi/openapi-bff.yaml b/src/main/openapi/openapi-bff.yaml index 0d7503f..0083af6 100644 --- a/src/main/openapi/openapi-bff.yaml +++ b/src/main/openapi/openapi-bff.yaml @@ -143,9 +143,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/RolePageResult' + $ref: '#/components/schemas/RolePageResult' 400: description: Bad request content: @@ -174,9 +172,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PermissionPageResult' + $ref: '#/components/schemas/PermissionPageResult' 400: description: Bad request content: @@ -205,9 +201,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/AssignmentPageResult' + $ref: '#/components/schemas/AssignmentPageResult' 400: description: Bad request content: @@ -316,9 +310,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/ApplicationPageResult' + $ref: '#/components/schemas/ApplicationPageResult' 400: description: Bad request content: @@ -367,6 +359,29 @@ paths: type: array items: $ref: '#/components/schemas/Product' + /workspaces/{workspaceName}/details: + get: + x-onecx: + permissions: + permission: + - read + tags: + - workspace + description: get detailed information to all workspace related products and roles + operationId: getDetailsByWorkspaceName + parameters: + - name: workspaceName + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/WorkspaceDetails' components: schemas: CreateRoleRequest: @@ -655,6 +670,37 @@ components: type: string baseUrl: type: string + WorkspaceDetails: + type: object + properties: + workspaceRoles: + type: array + items: + type: string + products: + type: array + items: + $ref: '#/components/schemas/ProductDetails' + ProductDetails: + type: object + properties: + productName: + type: string + mfe: + type: array + items: + $ref: '#/components/schemas/MfeMsAbstract' + ms: + type: array + items: + $ref: '#/components/schemas/MfeMsAbstract' + MfeMsAbstract: + type: object + properties: + appName: + type: string + appId: + type: string OffsetDateTime: format: date-time type: string diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fc97f2b..0978ff1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,11 +12,13 @@ org.eclipse.microprofile.rest.client.propagateHeaders=apm-principal-token # PROD %prod.quarkus.rest-client.onecx_permission_svc.url=http://onecx-permission-svc:8080 %prod.quarkus.rest-client.onecx_workspace_svc.url=http://onecx-workspace-svc:8080 +%prod.quarkus.rest-client.onecx_product_store_svc.url=http://onecx-product-store-svc:8080 %prod.quarkus.oidc-client.client-id=${quarkus.application.name} # DEV %dev.quarkus.rest-client.onecx_workspace_svc.url=${quarkus.mockserver.endpoint} %dev.quarkus.rest-client.onecx_permission_svc.url=${quarkus.mockserver.endpoint} +%dev.quarkus.rest-client.onecx_product_store_svc.url=${quarkus.mockserver.endpoint} %dev.quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url} %dev.quarkus.oidc-client.client-id=${quarkus.oidc.client-id} @@ -37,7 +39,11 @@ quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_v1_yaml.config-key=on quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_v1_yaml.base-package=gen.org.tkit.onecx.permission.client quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_v1_yaml.return-response=true quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; - +# product-store client +quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.config-key=onecx_product_store_svc +quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.base-package=gen.org.tkit.onecx.product.store.client +quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; # INTEGRATION TEST quarkus.test.integration-test-profile=test @@ -52,6 +58,7 @@ quarkus.test.integration-test-profile=test %test.onecx.permissions.product-name=applications %test.quarkus.rest-client.onecx_permission_svc.url=${quarkus.mockserver.endpoint} %test.quarkus.rest-client.onecx_workspace_svc.url=${quarkus.mockserver.endpoint} +%test.quarkus.rest-client.onecx_product_store_svc.url=${quarkus.mockserver.endpoint} %test.tkit.rs.context.token.header-param=apm-principal-token %test.tkit.rs.context.token.enabled=false %test.quarkus.rest-client.onecx_permission_svc.providers=io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter diff --git a/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerTest.java b/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerTest.java index 8b90c61..73fc875 100644 --- a/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerTest.java +++ b/src/test/java/org/tkit/onecx/permission/rs/WorkspaceRestControllerTest.java @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Set; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.core.Response; @@ -17,7 +19,10 @@ import org.tkit.onecx.permission.bff.rs.controllers.WorkspaceRestController; import gen.org.tkit.onecx.permission.bff.rs.internal.model.ProductDTO; +import gen.org.tkit.onecx.permission.bff.rs.internal.model.WorkspaceDetailsDTO; import gen.org.tkit.onecx.permission.client.model.Product; +import gen.org.tkit.onecx.permission.client.model.Workspace; +import gen.org.tkit.onecx.product.store.client.model.*; import io.quarkiverse.mockserver.test.InjectMockServerClient; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; @@ -93,4 +98,98 @@ void getAllProductsByWorkspaceNameTest() { Assertions.assertEquals(2, Arrays.stream(output).toList().size()); } + @Test + void getDetailsByWorkspaceNameTest() { + + String workspaceName = "test-workspace"; + Workspace workspace = new Workspace(); + workspace.name("test-workspace").workspaceRoles(Set.of("role1", "role2")); + + // create mock rest endpoint + mockServerClient + .when(request().withPath("/v1/workspaces/name/" + workspaceName).withMethod(HttpMethod.GET)) + .withId(MOCKID) + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withBody(JsonBody.json(workspace))); + + List productsOfWorkspace = new ArrayList<>(); + Product product1 = new Product(); + product1.productName("product1"); + Product product2 = new Product(); + product2.productName("product2"); + productsOfWorkspace.add(product1); + productsOfWorkspace.add(product2); + + // create mock rest endpoint + mockServerClient + .when(request().withPath("/v1/workspaces/" + workspaceName + "/products").withMethod(HttpMethod.GET)) + .withId("MOCKID2") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withBody(JsonBody.json(productsOfWorkspace))); + + List productNames = List.of("product1", "product2"); + ProductItemLoadSearchCriteria criteria = new ProductItemLoadSearchCriteria(); + criteria.setProductNames(productNames); + + ProductsLoadResult result = new ProductsLoadResult(); + + ProductsAbstract productsAbstract1 = new ProductsAbstract(); + productsAbstract1.setName("product1"); + MicrofrontendAbstract mfe1 = new MicrofrontendAbstract(); + mfe1.appId("mfe1").appName("mfe1"); + MicroserviceAbstract ms1 = new MicroserviceAbstract(); + ms1.appId("ms1").appName("ms1"); + productsAbstract1.setMicrofrontends(List.of(mfe1)); + productsAbstract1.setMicroservices(List.of(ms1)); + + ProductsAbstract productsAbstract2 = new ProductsAbstract(); + productsAbstract2.setName("product2"); + MicrofrontendAbstract mfe2 = new MicrofrontendAbstract(); + mfe2.appId("mfe2").appName("mfe2"); + MicroserviceAbstract ms2 = new MicroserviceAbstract(); + ms2.appId("ms2").appName("ms2"); + productsAbstract2.setMicrofrontends(List.of(mfe2)); + productsAbstract2.setMicroservices(List.of(ms2)); + + result.setStream(List.of(productsAbstract1, productsAbstract2)); + result.setTotalElements(2L); + result.setNumber(0); + result.setSize(2); + result.setTotalPages(1L); + + // create mock rest endpoint + mockServerClient + .when(request().withPath("/v1/products/load").withMethod(HttpMethod.POST) + .withBody(JsonBody.json(criteria))) + .withId("MOCKID3") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withBody(JsonBody.json(result))); + + var output = given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .pathParam("workspaceName", workspaceName) + .get("/{workspaceName}/details") + .then() + .statusCode(Response.Status.OK.getStatusCode()) + .extract().as(WorkspaceDetailsDTO.class); + + Assertions.assertNotNull(output); + Assertions.assertEquals(2, output.getProducts().size()); + Assertions.assertTrue(output.getWorkspaceRoles().contains("role1")); + Assertions.assertTrue(output.getWorkspaceRoles().contains("role2")); + Assertions.assertEquals(1, output.getProducts().get(0).getMfe().size()); + Assertions.assertEquals(1, output.getProducts().get(0).getMs().size()); + Assertions.assertNotNull(output.getProducts().get(0).getMfe().get(0).getAppId()); + + Assertions.assertEquals(1, output.getProducts().get(1).getMfe().size()); + Assertions.assertEquals(1, output.getProducts().get(1).getMs().size()); + Assertions.assertNotNull(output.getProducts().get(1).getMs().get(0).getAppId()); + + mockServerClient.clear(MOCKID); + mockServerClient.clear("MOCKID2"); + mockServerClient.clear("MOCKID3"); + } + }