From 8aa1535b2785e3184fb89ae151284b5120806615 Mon Sep 17 00:00:00 2001 From: JordenReuter <149687553+JordenReuter@users.noreply.github.com> Date: Tue, 16 Apr 2024 10:40:21 +0200 Subject: [PATCH] Feature/p002271 7079 images base url display name adjustments (#11) * feat: improved local image support, route base urls and displayName * feat: update dependencies and added docs --- docs/antora.yml | 3 + docs/modules/onecx-shell-bff/nav.adoc | 1 + docs/modules/onecx-shell-bff/pages/index.adoc | 10 + .../pages/onecx-shell-bff-attributes.adoc | 5 + .../pages/onecx-shell-bff-docs.adoc | 80 ++++++++ .../pages/onecx-shell-bff-extensions.adoc | 133 +++++++++++++ .../pages/onecx-shell-bff.adoc | 46 +++++ pom.xml | 6 +- .../tkit/onecx/shell/bff/rs/ShellConfig.java | 17 ++ .../controllers/PermissionRestController.java | 2 +- .../WorkspaceConfigRestController.java | 33 +++- .../bff/rs/mappers/PermissionMapper.java | 2 +- .../bff/rs/mappers/WorkspaceConfigMapper.java | 5 +- src/main/openapi/openapi-bff.yaml | 28 +++ .../rs/WorkspaceConfigRestControllerTest.java | 179 +++++++++++++++++- 15 files changed, 538 insertions(+), 12 deletions(-) create mode 100644 docs/antora.yml create mode 100644 docs/modules/onecx-shell-bff/nav.adoc create mode 100644 docs/modules/onecx-shell-bff/pages/index.adoc create mode 100644 docs/modules/onecx-shell-bff/pages/onecx-shell-bff-attributes.adoc create mode 100644 docs/modules/onecx-shell-bff/pages/onecx-shell-bff-docs.adoc create mode 100644 docs/modules/onecx-shell-bff/pages/onecx-shell-bff-extensions.adoc create mode 100644 docs/modules/onecx-shell-bff/pages/onecx-shell-bff.adoc diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 0000000..c966d4f --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,3 @@ +name: onecx-shell +title: Shell bff +version: latest \ No newline at end of file diff --git a/docs/modules/onecx-shell-bff/nav.adoc b/docs/modules/onecx-shell-bff/nav.adoc new file mode 100644 index 0000000..5eecaf9 --- /dev/null +++ b/docs/modules/onecx-shell-bff/nav.adoc @@ -0,0 +1 @@ +* xref:onecx-shell-bff:index.adoc[Shell Bff] \ No newline at end of file diff --git a/docs/modules/onecx-shell-bff/pages/index.adoc b/docs/modules/onecx-shell-bff/pages/index.adoc new file mode 100644 index 0000000..15ca5ad --- /dev/null +++ b/docs/modules/onecx-shell-bff/pages/index.adoc @@ -0,0 +1,10 @@ +include::onecx-shell-bff-attributes.adoc[opts=optional] + +== onecx-shell-bff + +include::docs.adoc[opts=optional] + +=== Configuration +include::onecx-shell-bff.adoc[opts=optional] + +include::onecx-shell-bff-docs.adoc[opts=optional] diff --git a/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-attributes.adoc b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-attributes.adoc new file mode 100644 index 0000000..26c1d08 --- /dev/null +++ b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-attributes.adoc @@ -0,0 +1,5 @@ + +:docker-registry: https://github.com/onecx/onecx-shell-bff/pkgs/container/onecx-shell-bff +:helm-registry: https://github.com/onecx/onecx-shell-bff/pkgs/container/charts%2Fonecx-shell-bff +:properties-file: src/main/resources/application.properties +:helm-file: src/main/helm/values.yaml \ No newline at end of file diff --git a/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-docs.adoc b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-docs.adoc new file mode 100644 index 0000000..cd46a1d --- /dev/null +++ b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-docs.adoc @@ -0,0 +1,80 @@ + +include::onecx-shell-bff-attributes.adoc[opts=optional] + +=== Default properties + +.{properties-file} +[%collapsible%open] +==== +[source,properties,subs=attributes+] +---- +quarkus.http.auth.permission.health.paths=/q/* +quarkus.http.auth.permission.health.policy=permit +quarkus.http.auth.permission.default.paths=/* +quarkus.http.auth.permission.default.policy=authenticated +onecx.permissions.application-id=${quarkus.application.name} +onecx.shell.permissions.cache-enabled=true +onecx.shell.permissions.key-separator=# +org.eclipse.microprofile.rest.client.propagateHeaders=apm-principal-token +%prod.quarkus.rest-client.onecx_workspace_svc.url=http://onecx-workspace-svc:8080 +%prod.quarkus.rest-client.onecx_theme_svc.url=http://onecx-theme-svc:8080 +%prod.quarkus.rest-client.onecx_product_store_svc.url=http://onecx-product-store-svc:8080 +%prod.quarkus.rest-client.onecx_permission_svc.url=http://onecx-permission-svc:8080 +%prod.quarkus.rest-client.onecx_user_profile_svc.url=http://onecx-user-profile-svc:8080 +onecx.component.mock.keys[0]=portalmenu +%prod.quarkus.oidc-client.client-id=${quarkus.application.name} +quarkus.openapi-generator.codegen.input-base-dir=target/tmp/openapi +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.config-key=onecx_workspace_svc +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.base-package=gen.org.tkit.onecx.workspace.client +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +quarkus.openapi-generator.codegen.spec.onecx_workspace_svc_external_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection; +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.config-key=onecx_theme_svc +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.base-package=gen.org.tkit.onecx.theme.client +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +quarkus.openapi-generator.codegen.spec.onecx_theme_svc_external_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection; +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; +quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.model-name-suffix=PSV1 +quarkus.openapi-generator.codegen.spec.onecx_product_store_svc_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection; +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.config-key=onecx_permission_svc +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.base-package=gen.org.tkit.onecx.permission.client +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +quarkus.openapi-generator.codegen.spec.onecx_permission_svc_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection; +quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.config-key=onecx_user_profile_svc +quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.base-package=gen.org.tkit.onecx.user.profile.client +quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.return-response=true +quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.additional-api-type-annotations=@org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +quarkus.openapi-generator.codegen.spec.onecx_user_profile_svc_v1_yaml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection; +---- +==== + +=== Extensions + +include::onecx-shell-bff-extensions.adoc[opts=optional] + +=== Container + +{docker-registry}[Docker registry] + + +=== Helm + +{helm-registry}[Helm registry] + +Default values + +.{helm-file} +[source,yaml] +---- +app: + name: bff + image: + repository: "onecx/onecx-shell-bff" + +---- + diff --git a/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-extensions.adoc b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-extensions.adoc new file mode 100644 index 0000000..f726a3e --- /dev/null +++ b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff-extensions.adoc @@ -0,0 +1,133 @@ + +include::onecx-shell-bff-attributes.adoc[opts=optional] + +[.extension.table.searchable, cols="50,.^15,.^15,.^20"] +|=== +h| Extensions +h| Documentation +h| Configuration +h| Version + +| quarkus-rest + +| https://quarkus.io/guides/rest[Link] +| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-rest.adoc[Link] +| 3.9.3 + +| quarkus-smallrye-openapi + +| https://quarkus.io/guides/openapi-swaggerui[Link] +| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-smallrye-openapi.adoc[Link] +| 3.9.3 + +| quarkus-rest-jackson + +| https://quarkus.io/guides/rest-json[Link] +| +| 3.9.3 + +| quarkus-smallrye-health + +| https://quarkus.io/guides/smallrye-health[Link] +| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-smallrye-health.adoc[Link] +| 3.9.3 + +| quarkus-openapi-generator + +| https://docs.quarkiverse.io/quarkus-openapi-generator/dev/index.html[Link] +| https://github.com/quarkiverse/quarkus-openapi-generator/blob/2.4.1/docs/modules/ROOT/pages/includes/quarkus-openapi-generator.adoc[Link] +| 2.4.1 + +| quarkus-rest-client-reactive-jackson + +| https://quarkus.io/guides/rest-client[Link] +| +| 3.9.3 + +| tkit-quarkus-log-cdi + +| https://1000kit.github.io/tkit-quarkus/current/tkit-quarkus/tkit-quarkus-log-cdi.html[Link] +| https://github.com/1000kit/tkit-quarkus/blob/2.21.0/docs/modules/tkit-quarkus/pages/includes/tkit-quarkus-log-cdi.adoc[Link] +| 2.21.0 + +| tkit-quarkus-log-rs + +| https://1000kit.github.io/tkit-quarkus/current/tkit-quarkus/tkit-quarkus-log-rs.html[Link] +| https://github.com/1000kit/tkit-quarkus/blob/2.21.0/docs/modules/tkit-quarkus/pages/includes/tkit-quarkus-log-rs.adoc[Link] +| 2.21.0 + +| tkit-quarkus-log-json + +| https://1000kit.github.io/tkit-quarkus/current/tkit-quarkus/tkit-quarkus-log-json.html[Link] +| https://github.com/1000kit/tkit-quarkus/blob/2.21.0/docs/modules/tkit-quarkus/pages/includes/tkit-quarkus-log-json.adoc[Link] +| 2.21.0 + +| tkit-quarkus-rest + +| https://1000kit.github.io/tkit-quarkus/current/tkit-quarkus/tkit-quarkus-rest.html[Link] +| https://github.com/1000kit/tkit-quarkus/blob/2.21.0/docs/modules/tkit-quarkus/pages/includes/tkit-quarkus-rest.adoc[Link] +| 2.21.0 + +| tkit-quarkus-rest-context + +| https://1000kit.github.io/tkit-quarkus/current/tkit-quarkus/tkit-quarkus-rest-context.html[Link] +| https://github.com/1000kit/tkit-quarkus/blob/2.21.0/docs/modules/tkit-quarkus/pages/includes/tkit-quarkus-rest-context.adoc[Link] +| 2.21.0 + +| tkit-quarkus-jpa + +| https://1000kit.github.io/tkit-quarkus/current/tkit-quarkus/tkit-quarkus-jpa.html[Link] +| https://github.com/1000kit/tkit-quarkus/blob/2.21.0/docs/modules/tkit-quarkus/pages/includes/tkit-quarkus-jpa.adoc[Link] +| 2.21.0 + +| tkit-quarkus-security + +| https://1000kit.github.io/tkit-quarkus/current/tkit-quarkus/tkit-quarkus-security.html[Link] +| https://github.com/1000kit/tkit-quarkus/blob/2.21.0/docs/modules/tkit-quarkus/pages/includes/tkit-quarkus-security.adoc[Link] +| 2.21.0 + +| quarkus-hibernate-validator + +| https://quarkus.io/guides/validation[Link] +| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-hibernate-validator.adoc[Link] +| 3.9.3 + +| onecx-permissions + +| https://onecx.github.io/docs/onecx-quarkus/current/onecx-quarkus/onecx-permissions.html[Link] +| https://github.com/onecx/onecx-quarkus/blob/0.16.0/docs/modules/onecx-quarkus/pages/includes/onecx-permissions.adoc[Link] +| 0.16.0 + +| quarkus-oidc + +| https://quarkus.io/guides/security-oidc-bearer-token-authentication-tutorial[Link] +| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-oidc.adoc[Link] +| 3.9.3 + +| quarkus-oidc-client-reactive-filter + +| https://quarkus.io/guides/security-openid-connect-client-reference[Link] +| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-oidc-client-reactive-filter.adoc[Link] +| 3.9.3 + +| onecx-core + +| https://onecx.github.io/docs/onecx-quarkus/current/onecx-quarkus/onecx-core.html[Link] +| +| 0.16.0 + +| quarkus-arc + +| https://quarkus.io/guides/cdi-reference[Link] +| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-arc.adoc[Link] +| 3.9.3 + +| quarkus-container-image-docker + +| https://quarkus.io/guides/container-image[Link] +| https://github.com/quarkusio/quarkusio.github.io/blob/develop/_generated-doc/latest/config/quarkus-container-image-docker.adoc[Link] +| 3.9.3 + + + +|=== \ No newline at end of file diff --git a/docs/modules/onecx-shell-bff/pages/onecx-shell-bff.adoc b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff.adoc new file mode 100644 index 0000000..0f4d2b2 --- /dev/null +++ b/docs/modules/onecx-shell-bff/pages/onecx-shell-bff.adoc @@ -0,0 +1,46 @@ + +:summaryTableId: onecx-shell-bff +[.configuration-legend] +icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime +[.configuration-reference.searchable, cols="80,.^10,.^10"] +|=== + +h|[[onecx-shell-bff_configuration]]link:#onecx-shell-bff_configuration[Configuration property] + +h|Type +h|Default + +a| [[onecx-shell-bff_onecx-shell-permissions-cache-enabled]]`link:#onecx-shell-bff_onecx-shell-permissions-cache-enabled[onecx.shell.permissions.cache-enabled]` + + +[.description] +-- +Enable or disable caching + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++ONECX_SHELL_PERMISSIONS_CACHE_ENABLED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++ONECX_SHELL_PERMISSIONS_CACHE_ENABLED+++` +endif::add-copy-button-to-env-var[] +--|boolean +|`true` + + +a| [[onecx-shell-bff_onecx-shell-permissions-key-separator]]`link:#onecx-shell-bff_onecx-shell-permissions-key-separator[onecx.shell.permissions.key-separator]` + + +[.description] +-- +select default key separator + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++ONECX_SHELL_PERMISSIONS_KEY_SEPARATOR+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++ONECX_SHELL_PERMISSIONS_KEY_SEPARATOR+++` +endif::add-copy-button-to-env-var[] +--|string +|`#` + +|=== \ No newline at end of file diff --git a/pom.xml b/pom.xml index 96ca725..c4e3b66 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.tkit.onecx onecx-quarkus3-parent - 0.37.0 + 0.46.0 onecx-shell-bff @@ -15,7 +15,7 @@ io.quarkus - quarkus-resteasy-reactive + quarkus-rest io.quarkus @@ -23,7 +23,7 @@ io.quarkus - quarkus-resteasy-reactive-jackson + quarkus-rest-jackson io.quarkus diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/ShellConfig.java b/src/main/java/org/tkit/onecx/shell/bff/rs/ShellConfig.java index c9353fa..f9a170c 100644 --- a/src/main/java/org/tkit/onecx/shell/bff/rs/ShellConfig.java +++ b/src/main/java/org/tkit/onecx/shell/bff/rs/ShellConfig.java @@ -1,20 +1,37 @@ package org.tkit.onecx.shell.bff.rs; +import io.quarkus.runtime.annotations.ConfigDocFilename; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; +/** + * Shell bff configuration + */ +@ConfigDocFilename("onecx-shell-bff.adoc") +@ConfigRoot(phase = ConfigPhase.RUN_TIME) @ConfigMapping(prefix = "onecx.shell") public interface ShellConfig { + /** + * permission configurations + */ @WithName("permissions") PermissionConfig permissions(); interface PermissionConfig { + /** + * Enable or disable caching + */ @WithDefault("true") @WithName("cache-enabled") boolean cachingEnabled(); + /** + * select default key separator + */ @WithDefault("#") @WithName("key-separator") String keySeparator(); diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/PermissionRestController.java b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/PermissionRestController.java index 77b043c..b80a8ed 100644 --- a/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/PermissionRestController.java +++ b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/PermissionRestController.java @@ -17,7 +17,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import org.jboss.resteasy.reactive.RestResponse; import org.jboss.resteasy.reactive.server.ServerExceptionMapper; -import org.tkit.onecx.quarkus.permission.client.PermissionResponse; +import org.tkit.onecx.quarkus.permission.service.PermissionResponse; import org.tkit.onecx.shell.bff.rs.ShellConfig; import org.tkit.onecx.shell.bff.rs.mappers.ExceptionMapper; import org.tkit.onecx.shell.bff.rs.mappers.PermissionMapper; diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/WorkspaceConfigRestController.java b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/WorkspaceConfigRestController.java index a0aac6e..6b94349 100644 --- a/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/WorkspaceConfigRestController.java +++ b/src/main/java/org/tkit/onecx/shell/bff/rs/controllers/WorkspaceConfigRestController.java @@ -8,8 +8,10 @@ import jakarta.transaction.Transactional; import jakarta.validation.ConstraintViolationException; import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.jboss.resteasy.reactive.RestResponse; @@ -55,6 +57,9 @@ public class WorkspaceConfigRestController implements WorkspaceConfigApiService @Inject RemoteComponentMockConfig mockConfig; + @Context + UriInfo uriInfo; + @Override public Response getWorkspaceConfig(GetWorkspaceConfigRequestDTO getWorkspaceConfigRequestDTO) { GetWorkspaceConfigResponseDTO responseDTO = new GetWorkspaceConfigResponseDTO(); @@ -76,7 +81,8 @@ public Response getWorkspaceConfig(GetWorkspaceConfigRequestDTO getWorkspaceConf try (Response psResponse = productStoreClient.getProductByName(p.getProductName())) { var product = psResponse.readEntity(ProductPSV1.class); product.getMicrofrontends() - .forEach(mfe -> routes.add(mapper.mapRoute(mfe, product, p.getMicrofrontends()))); + .forEach(mfe -> routes.add(mapper.mapRoute(mfe, product, p.getMicrofrontends(), + workspaceResponse.getBaseUrl()))); } catch (WebApplicationException ex) { //skip } @@ -86,6 +92,12 @@ public Response getWorkspaceConfig(GetWorkspaceConfigRequestDTO getWorkspaceConf //get theme info try (Response themeResponse = themeClient.getThemeByName(workspaceResponse.getTheme())) { var themeInfo = themeResponse.readEntity(Theme.class); + if (themeInfo.getFaviconUrl() == null) { + themeInfo.setFaviconUrl(uriInfo.getPath() + "/themes/" + themeInfo.getName() + "/favicon"); + } + if (themeInfo.getLogoUrl() == null) { + themeInfo.setLogoUrl(uriInfo.getPath() + "/themes/" + themeInfo.getName() + "/logo"); + } responseDTO.setTheme(mapper.mapTheme(themeInfo)); } //call remoteComponent Mocks => should be removed after implementation @@ -118,6 +130,25 @@ public Response getThemeFaviconByName(String name) { } } + @Override + public Response getThemeLogoByName(String name) { + Response.ResponseBuilder responseBuilder; + try (Response response = themeClient.getThemeLogoByName(name)) { + var contentType = response.getHeaderString(HttpHeaders.CONTENT_TYPE); + var contentLength = response.getHeaderString(HttpHeaders.CONTENT_LENGTH); + var body = response.readEntity(byte[].class); + if (contentType != null && body.length != 0) { + responseBuilder = Response.status(response.getStatus()) + .header(HttpHeaders.CONTENT_TYPE, contentType) + .header(HttpHeaders.CONTENT_LENGTH, contentLength) + .entity(body); + } else { + responseBuilder = Response.status(Response.Status.BAD_REQUEST); + } + return responseBuilder.build(); + } + } + /** * SHOULD BE REMOVED AFTER IMPLEMENTATION * diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/PermissionMapper.java b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/PermissionMapper.java index f07dda3..54fbff1 100644 --- a/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/PermissionMapper.java +++ b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/PermissionMapper.java @@ -2,7 +2,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.tkit.onecx.quarkus.permission.client.PermissionResponse; +import org.tkit.onecx.quarkus.permission.service.PermissionResponse; import gen.org.tkit.onecx.shell.bff.rs.internal.model.GetPermissionsResponseDTO; diff --git a/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/WorkspaceConfigMapper.java b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/WorkspaceConfigMapper.java index afc3e54..c114eba 100644 --- a/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/WorkspaceConfigMapper.java +++ b/src/main/java/org/tkit/onecx/shell/bff/rs/mappers/WorkspaceConfigMapper.java @@ -31,15 +31,16 @@ public interface WorkspaceConfigMapper { ProductItemSearchCriteriaPSV1 map(WorkspaceAbstract workspaceInfo); default RouteDTO mapRoute(MicrofrontendPSV1 mfe, ProductPSV1 product, - List wsMfes) { + List wsMfes, String workspaceUrl) { RouteDTO route = new RouteDTO(); route.setRemoteEntryUrl(mfe.getRemoteEntry()); route.setExposedModule(mfe.getExposedModule()); route.setProductName(product.getName()); + route.setDisplayName(product.getDisplayName()); route.setAppId(mfe.getAppId()); route.setTechnology(TechnologiesDTO.ANGULAR); var selectedMfe = wsMfes.stream().filter(microfrontend -> microfrontend.getMfeId().equals(mfe.getAppId())).findFirst(); - selectedMfe.ifPresent(microfrontend -> route.setBaseUrl(microfrontend.getBasePath())); + selectedMfe.ifPresent(microfrontend -> route.setBaseUrl(workspaceUrl + microfrontend.getBasePath())); route.setUrl(mfe.getRemoteBaseUrl()); route.setPathMatch(PathMatchDTO.PREFIX); if (mfe.getRemoteName() != null) { diff --git a/src/main/openapi/openapi-bff.yaml b/src/main/openapi/openapi-bff.yaml index a891056..4bcc388 100644 --- a/src/main/openapi/openapi-bff.yaml +++ b/src/main/openapi/openapi-bff.yaml @@ -60,6 +60,31 @@ paths: 404: description: Not found + /workspaceConfig/themes/{name}/logo: + get: + tags: + - "WorkspaceConfig" + description: Load logo by theme name + operationId: getThemeLogoByName + parameters: + - name: name + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + content: + image/*: + schema: + minimum: 1 + maximum: 110000 + type: string + format: binary + 404: + description: Not found + /userProfile: get: tags: @@ -203,6 +228,7 @@ components: - 'appId' - 'productName' - 'pathMatch' + - 'displayName' properties: url: type: string @@ -222,6 +248,8 @@ components: $ref: '#/components/schemas/PathMatch' remoteName: type: string + displayName: + type: string Technologies: type: string diff --git a/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java b/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java index d9374cf..5039681 100644 --- a/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java +++ b/src/test/java/org/tkit/onecx/shell/bff/rs/WorkspaceConfigRestControllerTest.java @@ -61,6 +61,92 @@ void getWorkspaceConfigByBaseUrlTest() { new Microfrontend().basePath("/app1").mfeId("app1"))); loadResponse.setProducts(List.of(product1)); + // create mock rest endpoint for load workspace by name + mockServerClient.when(request().withPath("/v1/workspaces/w1/load").withMethod(HttpMethod.GET)) + .withId("mockWSLoad") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(loadResponse))); + + ProductPSV1 productResponse = new ProductPSV1(); + productResponse.basePath("/product1").name("product1").microfrontends(List.of( + new MicrofrontendPSV1().exposedModule("App1Module") + .appName("app1") + .remoteBaseUrl("/remoteBaseUrl") + .remoteEntry("/remoteEntry.js") + .technology("ANGULAR"))); + // create mock rest endpoint for get product by name from product-store + mockServerClient.when(request().withPath("/v1/products/product1").withMethod(HttpMethod.GET)) + .withId("mockPS") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(productResponse))); + + Theme themeResponse = new Theme(); + themeResponse.name("theme1").cssFile("cssfile").properties(new Object()).logoUrl("someLogoUrl") + .faviconUrl("someFavIconUrl"); + // create mock rest endpoint for get theme by name from theme-svc + mockServerClient.when(request().withPath("/v1/themes/theme1").withMethod(HttpMethod.GET)) + .withId("mockTheme") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(themeResponse))); + + var input = new GetWorkspaceConfigRequestDTO(); + input.setUrl("/w1Url"); + + var output = given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .body(input) + .post() + .then() + .statusCode(Response.Status.OK.getStatusCode()) + .contentType(APPLICATION_JSON) + .extract().as(GetWorkspaceConfigResponseDTO.class); + + Assertions.assertNotNull(output); + Assertions.assertEquals("w1", output.getWorkspace().getName()); + Assertions.assertEquals("theme1", output.getTheme().getName()); + Assertions.assertEquals(1, output.getRoutes().size()); + + //CHECK FOR MOCKED REMOTE COMPONENTS + //SHOULD BE REMOVED AFTER IMPLEMENTATION + Assertions.assertEquals("PortalMenu", output.getRemoteComponents().get(0).getName()); + Assertions.assertEquals("appId", output.getRemoteComponents().get(0).getAppId()); + Assertions.assertEquals("menu", output.getShellRemoteComponents().get(0).getSlotName()); + + mockServerClient.clear("mockWS"); + mockServerClient.clear("mockPS"); + mockServerClient.clear("mockTheme"); + mockServerClient.clear("mockWSLoad"); + } + + @Test + void getWorkspaceConfigByBaseUrlTest_emptyImageUrl_should_create_urls_test() { + GetWorkspaceByUrlRequest byUrlRequest = new GetWorkspaceByUrlRequest(); + byUrlRequest.setUrl("/w1Url"); + Workspace workspace = new Workspace(); + workspace.name("w1").theme("theme1"); + + // create mock rest endpoint for workspace search + mockServerClient.when(request().withPath("/v1/workspaces/byUrl").withMethod(HttpMethod.POST) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(byUrlRequest))) + .withId("mockWS") + .respond(httpRequest -> response().withStatusCode(Response.Status.OK.getStatusCode()) + .withContentType(MediaType.APPLICATION_JSON) + .withBody(JsonBody.json(workspace))); + + WorkspaceLoad loadResponse = new WorkspaceLoad(); + loadResponse.setName("w1"); + Product product1 = new Product(); + product1.baseUrl("/product1").productName("product1").microfrontends(List.of( + new Microfrontend().basePath("/app1").mfeId("app1"))); + loadResponse.setProducts(List.of(product1)); + // create mock rest endpoint for load workspace by name mockServerClient.when(request().withPath("/v1/workspaces/w1/load").withMethod(HttpMethod.GET)) .withId("mockWSLoad") @@ -110,6 +196,8 @@ void getWorkspaceConfigByBaseUrlTest() { Assertions.assertEquals("w1", output.getWorkspace().getName()); Assertions.assertEquals("theme1", output.getTheme().getName()); Assertions.assertEquals(1, output.getRoutes().size()); + Assertions.assertNotNull(output.getTheme().getFaviconUrl()); + Assertions.assertNotNull(output.getTheme().getLogoUrl()); //CHECK FOR MOCKED REMOTE COMPONENTS //SHOULD BE REMOVED AFTER IMPLEMENTATION @@ -372,7 +460,39 @@ void getThemeFaviconTest() { } @Test - void getThemeFavicon_shouldReturnBadRequest_whenBodyEmpty() { + void getThemeLogoTest() { + + byte[] bytesRes = new byte[] { (byte) 0xe0, 0x4f, (byte) 0xd0, + 0x20, (byte) 0xea, 0x3a, 0x69, 0x10, (byte) 0xa2, (byte) 0xd8, 0x08, 0x00, 0x2b, + 0x30, 0x30, (byte) 0x9d }; + + // create mock rest endpoint for get theme by name from theme-svc + mockServerClient.when(request().withPath("/v1/themes/theme1/logo").withMethod(HttpMethod.GET)) + .withId("mockLogo") + .respond(httpRequest -> response().withStatusCode(OK.getStatusCode()) + .withHeaders( + new Header(HttpHeaders.CONTENT_TYPE, "image/png")) + .withBody(bytesRes)); + + var output = given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .pathParam("name", "theme1") + .get("/themes/{name}/logo") + .then() + .statusCode(OK.getStatusCode()) + .header(HttpHeaders.CONTENT_TYPE, "image/png") + .extract().body().asByteArray(); + + Assertions.assertNotNull(output); + mockServerClient.clear("mockLogo"); + + } + + @Test + void getThemeFaviconAndLogo_shouldReturnBadRequest_whenBodyEmpty() { byte[] bytesRes = null; @@ -384,6 +504,14 @@ void getThemeFavicon_shouldReturnBadRequest_whenBodyEmpty() { new Header(HttpHeaders.CONTENT_TYPE, "image/png")) .withBody(bytesRes)); + mockServerClient.when(request().withPath("/v1/themes/theme1/logo").withMethod(HttpMethod.GET)) + .withId("mockLogo") + .withPriority(100) + .respond(httpRequest -> response().withStatusCode(OK.getStatusCode()) + .withHeaders( + new Header(HttpHeaders.CONTENT_TYPE, "image/png")) + .withBody(bytesRes)); + given() .when() .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) @@ -393,12 +521,23 @@ void getThemeFavicon_shouldReturnBadRequest_whenBodyEmpty() { .get("/themes/{name}/favicon") .then() .statusCode(BAD_REQUEST.getStatusCode()); - mockServerClient.clear("mockFavicon"); + given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .pathParam("name", "theme1") + .get("/themes/{name}/logo") + .then() + .statusCode(BAD_REQUEST.getStatusCode()); + + mockServerClient.clear("mockFavicon"); + mockServerClient.clear("mockLogo"); } @Test - void getThemeFavicon_shouldReturnBadRequest_whenContentTypeEmpty() { + void getThemeFaviconAndLogo_shouldReturnBadRequest_whenContentTypeEmpty() { byte[] bytesRes = new byte[] { (byte) 0xe0, 0x4f, (byte) 0xd0, 0x20, (byte) 0xea, 0x3a, 0x69, 0x10, (byte) 0xa2, (byte) 0xd8, 0x08, 0x00, 0x2b, @@ -409,6 +548,11 @@ void getThemeFavicon_shouldReturnBadRequest_whenContentTypeEmpty() { .withPriority(100) .respond(httpRequest -> response().withStatusCode(OK.getStatusCode()) .withBody(bytesRes)); + mockServerClient.when(request().withPath("/v1/themes/theme1/logo").withMethod(HttpMethod.GET)) + .withId("mockLogo") + .withPriority(100) + .respond(httpRequest -> response().withStatusCode(OK.getStatusCode()) + .withBody(bytesRes)); given() .when() @@ -420,7 +564,18 @@ void getThemeFavicon_shouldReturnBadRequest_whenContentTypeEmpty() { .then() .statusCode(BAD_REQUEST.getStatusCode()); + given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .pathParam("name", "theme1") + .get("/themes/{name}/logo") + .then() + .statusCode(BAD_REQUEST.getStatusCode()); + mockServerClient.clear("mockFavicon"); + mockServerClient.clear("mockLogo"); } @@ -432,6 +587,11 @@ void getImage_shouldReturnBadRequest_whenAllEmpty() { .withPriority(100) .respond(httpRequest -> response().withStatusCode(OK.getStatusCode())); + mockServerClient.when(request().withPath("/v1/themes/theme1/logo").withMethod(HttpMethod.GET)) + .withId("mockLogo") + .withPriority(100) + .respond(httpRequest -> response().withStatusCode(OK.getStatusCode())); + given() .when() .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) @@ -441,7 +601,18 @@ void getImage_shouldReturnBadRequest_whenAllEmpty() { .get("/themes/{name}/favicon") .then() .statusCode(BAD_REQUEST.getStatusCode()); - mockServerClient.clear("mockFavicon"); + given() + .when() + .auth().oauth2(keycloakClient.getAccessToken(ADMIN)) + .header(APM_HEADER_PARAM, ADMIN) + .contentType(APPLICATION_JSON) + .pathParam("name", "theme1") + .get("/themes/{name}/logo") + .then() + .statusCode(BAD_REQUEST.getStatusCode()); + + mockServerClient.clear("mockFavicon"); + mockServerClient.clear("mockLogo"); } }