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");
+ }
+
}