From 22edd2df203edb0c26f5ae702b02ace35497ff0c Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 17 Dec 2025 18:27:42 +0100 Subject: [PATCH 1/9] refactor/Introduce http4s routes for v7.0.0 and update API resource docs - Add `Http4sEndpoint` type alias and `http4sPartialFunction` in APIUtil for handling http4s routes - Refactor Http4s700 to define routes as standalone functions (e.g., `root`, `getBanks`) within Implementations7_0_0 object - Attach resource documentation to each route for better maintainability - Create a unified `allRoutes` combining v7.0.0 route handlers - Update imports and clean up unused references --- .../main/scala/code/api/util/APIUtil.scala | 6 +- .../scala/code/api/v7_0_0/Http4s700.scala | 132 +++++++++++++----- 2 files changed, 102 insertions(+), 36 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index d6fb5dbb4f..381b0c2839 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -27,6 +27,7 @@ TESOBE (http://www.tesobe.com/) package code.api.util import bootstrap.liftweb.CustomDBVendor +import cats.effect.IO import code.accountholders.AccountHolders import code.api.Constant._ import code.api.OAuthHandshake._ @@ -96,6 +97,7 @@ import net.liftweb.util.Helpers._ import net.liftweb.util._ import org.apache.commons.io.IOUtils import org.apache.commons.lang3.StringUtils +import org.http4s.HttpRoutes import java.io.InputStream import java.net.URLDecoder @@ -1636,7 +1638,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ isFeatured: Boolean = false, specialInstructions: Option[String] = None, var specifiedUrl: Option[String] = None, // A derived value: Contains the called version (added at run time). See the resource doc for resource doc! - createdByBankId: Option[String] = None //we need to filter the resource Doc by BankId + createdByBankId: Option[String] = None, //we need to filter the resource Doc by BankId + http4sPartialFunction: Http4sEndpoint = None // http4s endpoint handler ) { // this code block will be merged to constructor. { @@ -2789,6 +2792,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ type OBPEndpoint = PartialFunction[Req, CallContext => Box[JsonResponse]] type OBPReturnType[T] = Future[(T, Option[CallContext])] + type Http4sEndpoint = Option[HttpRoutes[IO]] def getAllowedEndpoints (endpoints : Iterable[OBPEndpoint], resourceDocs: ArrayBuffer[ResourceDoc]) : List[OBPEndpoint] = { diff --git a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala index 877b91b72d..05d4fb4145 100644 --- a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala +++ b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala @@ -2,18 +2,23 @@ package code.api.v7_0_0 import cats.data.{Kleisli, OptionT} import cats.effect._ -import cats.implicits._ -import code.api.util.{APIUtil, CustomJsonFormats} +import code.api.Constant._ +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ +import code.api.util.APIUtil.{EmptyBody, _} +import code.api.util.ApiTag._ +import code.api.util.ErrorMessages._ +import code.api.util.{CustomJsonFormats, NewStyle} import code.api.v4_0_0.JSONFactory400 -import code.bankconnectors.Connector -import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} -import net.liftweb.json.Formats +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus, ScannedApiVersion} +import net.liftweb.json.{Extraction, Formats} import net.liftweb.json.JsonAST.prettyRender -import net.liftweb.json.Extraction import org.http4s._ import org.http4s.dsl.io._ import org.typelevel.vault.Key +import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future import scala.language.{higherKinds, implicitConversions} @@ -24,12 +29,13 @@ object Http4s700 { implicit val formats: Formats = CustomJsonFormats.formats implicit def convertAnyToJsonString(any: Any): String = prettyRender(Extraction.decompose(any)) - val apiVersion: ScannedApiVersion = ApiVersion.v7_0_0 - val apiVersionString: String = apiVersion.toString + val implementedInApiVersion: ScannedApiVersion = ApiVersion.v7_0_0 + val versionStatus = ApiVersionStatus.STABLE.toString + val resourceDocs = ArrayBuffer[ResourceDoc]() case class CallContext(userId: String, requestId: String) - import cats.effect.unsafe.implicits.global - val callContextKey: Key[CallContext] = Key.newKey[IO, CallContext].unsafeRunSync() + val callContextKey: Key[CallContext] = + Key.newKey[IO, CallContext].unsafeRunSync()(cats.effect.unsafe.IORuntime.global) object CallContextMiddleware { @@ -42,31 +48,87 @@ object Http4s700 { } } - val v700Services: HttpRoutes[IO] = HttpRoutes.of[IO] { - case req @ GET -> Root / "obp" / `apiVersionString` / "root" => - import com.openbankproject.commons.ExecutionContext.Implicits.global - val callContext = req.attributes.lookup(callContextKey).get.asInstanceOf[CallContext] - Ok(IO.fromFuture(IO( - for { - _ <- Future() // Just start async call - } yield { - convertAnyToJsonString( - JSONFactory700.getApiInfoJSON(apiVersion, s"Hello, ${callContext.userId}! Your request ID is ${callContext.requestId}.") - ) - } - ))) - - case req @ GET -> Root / "obp" / `apiVersionString` / "banks" => - import com.openbankproject.commons.ExecutionContext.Implicits.global - Ok(IO.fromFuture(IO( - for { - (banks, callContext) <- code.api.util.NewStyle.function.getBanks(None) - } yield { - convertAnyToJsonString(JSONFactory400.createBanksJson(banks)) - } - ))) + object Implementations7_0_0 { + + // Common prefix: /obp/v7.0.0 + val prefixPath = Root / ApiPathZero.toString / implementedInApiVersion.toString + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(root), + "GET", + "/root", + "Get API Info (root)", + s"""Returns information about: + | + |* API version + |* Hosted by information + |* Git Commit + |${userAuthenticationMessage(false)}""", + EmptyBody, + apiInfoJSON, + List(UnknownError, "no connector set"), + apiTagApi :: Nil, + http4sPartialFunction = Some(root) + ) + + // Route: GET /obp/v7.0.0/root + val root: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `prefixPath` / "root" => + val callContext = req.attributes.lookup(callContextKey).get.asInstanceOf[CallContext] + Ok(IO.fromFuture(IO( + for { + _ <- Future() // Just start async call + } yield { + convertAnyToJsonString( + JSONFactory700.getApiInfoJSON(implementedInApiVersion, s"Hello, ${callContext.userId}! Your request ID is ${callContext.requestId}.") + ) + } + ))) + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(getBanks), + "GET", + "/banks", + "Get Banks", + s"""Get banks on this API instance + |Returns a list of banks supported on this server: + | + |* ID used as parameter in URLs + |* Short and full name of bank + |* Logo URL + |* Website + |${userAuthenticationMessage(false)}""", + EmptyBody, + banksJSON, + List(UnknownError), + apiTagBank :: Nil, + http4sPartialFunction = Some(getBanks) + ) + + // Route: GET /obp/v7.0.0/banks + val getBanks: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `prefixPath` / "banks" => + import com.openbankproject.commons.ExecutionContext.Implicits.global + Ok(IO.fromFuture(IO( + for { + (banks, callContext) <- NewStyle.function.getBanks(None) + } yield { + convertAnyToJsonString(JSONFactory400.createBanksJson(banks)) + } + ))) + } + + // All routes combined + val allRoutes: HttpRoutes[IO] = + Kleisli[HttpF, Request[IO], Response[IO]] { req: Request[IO] => + root(req).orElse(getBanks(req)) + } } - val wrappedRoutesV700Services: HttpRoutes[IO] = CallContextMiddleware.withCallContext(v700Services) + val wrappedRoutesV700Services: HttpRoutes[IO] = CallContextMiddleware.withCallContext(Implementations7_0_0.allRoutes) } - From c20e142a4756e6979c74be701d1255b3cdc258ef Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 17 Dec 2025 19:19:19 +0100 Subject: [PATCH 2/9] feature/Get resource docs endpoint for v7.0.0 - Introduce `getResourceDocsObpV700` to handle resource docs retrieval for API version 7.0.0 - Add `getResourceDocsList` helper function for fetching version-specific resource docs - Update `allRoutes` to include the new endpoint - Modify imports to include necessary utilities and remove unused references --- .../scala/code/api/v7_0_0/Http4s700.scala | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala index 05d4fb4145..40fdbb5b20 100644 --- a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala +++ b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala @@ -3,17 +3,19 @@ package code.api.v7_0_0 import cats.data.{Kleisli, OptionT} import cats.effect._ import code.api.Constant._ +import code.api.ResourceDocs1_4_0.ResourceDocsAPIMethodsUtil import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil.{EmptyBody, _} import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ -import code.api.util.{CustomJsonFormats, NewStyle} +import code.api.util.{ApiVersionUtils, CustomJsonFormats, NewStyle, ScannedApis} +import code.api.v1_4_0.JSONFactory1_4_0 import code.api.v4_0_0.JSONFactory400 import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus, ScannedApiVersion} -import net.liftweb.json.{Extraction, Formats} import net.liftweb.json.JsonAST.prettyRender +import net.liftweb.json.{Extraction, Formats} import org.http4s._ import org.http4s.dsl.io._ import org.typelevel.vault.Key @@ -53,6 +55,14 @@ object Http4s700 { // Common prefix: /obp/v7.0.0 val prefixPath = Root / ApiPathZero.toString / implementedInApiVersion.toString + private def getResourceDocsList(requestedApiVersion: ApiVersion): List[ResourceDoc] = { + requestedApiVersion match { + case version: ScannedApiVersion => + ScannedApis.versionMapScannedApis.get(version).map(_.allResourceDocs.toList).getOrElse(Nil) + case _ => Nil + } + } + resourceDocs += ResourceDoc( null, implementedInApiVersion, @@ -123,10 +133,30 @@ object Http4s700 { ))) } + val getResourceDocsObpV700: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `prefixPath` / "resource-docs" / requestedApiVersionString / "obp" => + import com.openbankproject.commons.ExecutionContext.Implicits.global + val logic = for { + httpParams <- NewStyle.function.extractHttpParamsFromUrl(req.uri.renderString) + tagsParam = httpParams.filter(_.name == "tags").map(_.values).headOption + functionsParam = httpParams.filter(_.name == "functions").map(_.values).headOption + localeParam = httpParams.filter(param => param.name == "locale" || param.name == "language").map(_.values).flatten.headOption + contentParam = httpParams.filter(_.name == "content").map(_.values).flatten.flatMap(ResourceDocsAPIMethodsUtil.stringToContentParam).headOption + apiCollectionIdParam = httpParams.filter(_.name == "api-collection-id").map(_.values).flatten.headOption + tags = tagsParam.map(_.map(ResourceDocTag(_))) + functions = functionsParam.map(_.toList) + requestedApiVersion <- Future(ApiVersionUtils.valueOf(requestedApiVersionString)) + resourceDocs = getResourceDocsList(requestedApiVersion) + filteredDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, tags, functions) + resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam) + } yield convertAnyToJsonString(resourceDocsJson) + Ok(IO.fromFuture(IO(logic))) + } + // All routes combined val allRoutes: HttpRoutes[IO] = Kleisli[HttpF, Request[IO], Response[IO]] { req: Request[IO] => - root(req).orElse(getBanks(req)) + root(req).orElse(getBanks(req)).orElse(getResourceDocsObpV700(req)) } } From bfc6636e8f78ef7bb6ddc112ec3ca398b796f30b Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 17 Dec 2025 22:42:12 +0100 Subject: [PATCH 3/9] refactor(Http4sServer): Reorder service initialization and improve comments --- .../main/scala/bootstrap/http4s/Http4sServer.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala b/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala index 8a8b3366ff..72b0574d27 100644 --- a/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala +++ b/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala @@ -11,17 +11,16 @@ import org.http4s.implicits._ import scala.language.higherKinds object Http4sServer extends IOApp { - val services: Kleisli[({type λ[β$0$] = OptionT[IO, β$0$]})#λ, Request[IO], Response[IO]] = - code.api.v7_0_0.Http4s700.wrappedRoutesV700Services - - val httpApp: Kleisli[IO, Request[IO], Response[IO]] = (services).orNotFound - - //Start OBP relevant objects, and settings + //Start OBP relevant objects and settings; this step MUST be executed first new bootstrap.http4s.Http4sBoot().boot val port = APIUtil.getPropsAsIntValue("http4s.port",8181) val host = APIUtil.getPropsValue("http4s.host","127.0.0.1") + val services: Kleisli[({type λ[β$0$] = OptionT[IO, β$0$]})#λ, Request[IO], Response[IO]] = + code.api.v7_0_0.Http4s700.wrappedRoutesV700Services + + val httpApp: Kleisli[IO, Request[IO], Response[IO]] = (services).orNotFound override def run(args: List[String]): IO[ExitCode] = EmberServerBuilder .default[IO] From d14579a16f7a46a8add30234ec0518bc729a954c Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 17 Dec 2025 23:51:37 +0100 Subject: [PATCH 4/9] feature/Support API version 7.0.0 - Add `v7_0_0` to supported API versions in `ApiVersionUtils` - Update `Http4s700` to return pre-defined resource docs instead of scanning for version 7.0.0 --- obp-api/src/main/scala/code/api/util/ApiVersionUtils.scala | 2 ++ obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ApiVersionUtils.scala b/obp-api/src/main/scala/code/api/util/ApiVersionUtils.scala index 5e93b6f7bd..f7285febb7 100644 --- a/obp-api/src/main/scala/code/api/util/ApiVersionUtils.scala +++ b/obp-api/src/main/scala/code/api/util/ApiVersionUtils.scala @@ -19,6 +19,7 @@ object ApiVersionUtils { v5_0_0 :: v5_1_0 :: v6_0_0 :: + v7_0_0 :: `dynamic-endpoint` :: `dynamic-entity` :: scannedApis @@ -41,6 +42,7 @@ object ApiVersionUtils { case v5_0_0.fullyQualifiedVersion | v5_0_0.apiShortVersion => v5_0_0 case v5_1_0.fullyQualifiedVersion | v5_1_0.apiShortVersion => v5_1_0 case v6_0_0.fullyQualifiedVersion | v6_0_0.apiShortVersion => v6_0_0 + case v7_0_0.fullyQualifiedVersion | v7_0_0.apiShortVersion => v7_0_0 case `dynamic-endpoint`.fullyQualifiedVersion | `dynamic-endpoint`.apiShortVersion => `dynamic-endpoint` case `dynamic-entity`.fullyQualifiedVersion | `dynamic-entity`.apiShortVersion => `dynamic-entity` case version if(scannedApis.map(_.fullyQualifiedVersion).contains(version)) diff --git a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala index 40fdbb5b20..d491e7be35 100644 --- a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala +++ b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala @@ -8,7 +8,7 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil.{EmptyBody, _} import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ -import code.api.util.{ApiVersionUtils, CustomJsonFormats, NewStyle, ScannedApis} +import code.api.util.{ApiVersionUtils, CustomJsonFormats, NewStyle} import code.api.v1_4_0.JSONFactory1_4_0 import code.api.v4_0_0.JSONFactory400 import com.github.dwickern.macros.NameOf.nameOf @@ -58,7 +58,7 @@ object Http4s700 { private def getResourceDocsList(requestedApiVersion: ApiVersion): List[ResourceDoc] = { requestedApiVersion match { case version: ScannedApiVersion => - ScannedApis.versionMapScannedApis.get(version).map(_.allResourceDocs.toList).getOrElse(Nil) + resourceDocs.toList case _ => Nil } } From 99899a6227ed1a9001861694a4e52ff37d5b3bc9 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 18 Dec 2025 09:07:24 +0100 Subject: [PATCH 5/9] refactor/Http4sServer: Update default http4s.port from 8181 to 8086 --- obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala b/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala index 72b0574d27..8207e72686 100644 --- a/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala +++ b/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala @@ -14,7 +14,7 @@ object Http4sServer extends IOApp { //Start OBP relevant objects and settings; this step MUST be executed first new bootstrap.http4s.Http4sBoot().boot - val port = APIUtil.getPropsAsIntValue("http4s.port",8181) + val port = APIUtil.getPropsAsIntValue("http4s.port",8086) val host = APIUtil.getPropsValue("http4s.host","127.0.0.1") val services: Kleisli[({type λ[β$0$] = OptionT[IO, β$0$]})#λ, Request[IO], Response[IO]] = From 07908cc06d906a40c6532e30605e97695bc88311 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 18 Dec 2025 09:18:02 +0100 Subject: [PATCH 6/9] feature/Enhance resource docs handling for v7.0.0 - Update `getResourceDocsList` to include v7.0.0 in `ResourceDocsAPIMethods` - Modify `Http4s700` to utilize centralized `ResourceDocs140` for fetching resource docs - Simplify resource docs filtering logic for v7.0.0 with tailored handling --- .../ResourceDocsAPIMethods.scala | 17 ++++++++++------- .../main/scala/code/api/v7_0_0/Http4s700.scala | 11 ++--------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index 3d5aef287d..850d1d79e5 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -1,8 +1,10 @@ package code.api.ResourceDocs1_4_0 -import code.api.Constant.{GET_DYNAMIC_RESOURCE_DOCS_TTL, GET_STATIC_RESOURCE_DOCS_TTL, PARAM_LOCALE, HostName} +import code.api.Constant.{GET_DYNAMIC_RESOURCE_DOCS_TTL, GET_STATIC_RESOURCE_DOCS_TTL, HostName, PARAM_LOCALE} import code.api.OBPRestHelper import code.api.cache.Caching +import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint +import code.api.dynamic.entity.OBPAPIDynamicEntity import code.api.util.APIUtil._ import code.api.util.ApiRole.{canReadDynamicResourceDocsAtOneBank, canReadResourceDoc} import code.api.util.ApiTag._ @@ -19,12 +21,9 @@ import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0} import code.api.v5_0_0.OBPAPI5_0_0 import code.api.v5_1_0.OBPAPI5_1_0 import code.api.v6_0_0.OBPAPI6_0_0 -import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint -import code.api.dynamic.entity.OBPAPIDynamicEntity import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider import code.util.Helper import code.util.Helper.{MdcLoggable, ObpS, SILENCE_IS_GOLDEN} -import net.liftweb.http.S import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.model.enums.ContentParam import com.openbankproject.commons.model.enums.ContentParam.{ALL, DYNAMIC, STATIC} @@ -32,7 +31,7 @@ import com.openbankproject.commons.model.{BankId, ListResult, User} import com.openbankproject.commons.util.ApiStandards._ import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} import net.liftweb.common.{Box, Empty, Full} -import net.liftweb.http.LiftRules +import net.liftweb.http.{LiftRules, S} import net.liftweb.json import net.liftweb.json.JsonAST.{JField, JString, JValue} import net.liftweb.json._ @@ -117,6 +116,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth logger.debug(s"getResourceDocsList says requestedApiVersion is $requestedApiVersion") val resourceDocs = requestedApiVersion match { + case ApiVersion.v7_0_0 => code.api.v7_0_0.Http4s700.resourceDocs case ApiVersion.v6_0_0 => OBPAPI6_0_0.allResourceDocs case ApiVersion.v5_1_0 => OBPAPI5_1_0.allResourceDocs case ApiVersion.v5_0_0 => OBPAPI5_0_0.allResourceDocs @@ -138,6 +138,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth logger.debug(s"There are ${resourceDocs.length} resource docs available to $requestedApiVersion") val versionRoutes = requestedApiVersion match { + case ApiVersion.v7_0_0 => Nil case ApiVersion.v6_0_0 => OBPAPI6_0_0.routes case ApiVersion.v5_1_0 => OBPAPI5_1_0.routes case ApiVersion.v5_0_0 => OBPAPI5_0_0.routes @@ -164,7 +165,10 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth val versionRoutesClasses = versionRoutes.map { vr => vr.getClass } // Only return the resource docs that have available routes - val activeResourceDocs = resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass)) + val activeResourceDocs = requestedApiVersion match { + case ApiVersion.v7_0_0 => resourceDocs + case _ => resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass)) + } logger.debug(s"There are ${activeResourceDocs.length} resource docs available to $requestedApiVersion") @@ -1223,4 +1227,3 @@ so the caller must specify any required filtering by catalog explicitly. } - diff --git a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala index d491e7be35..1f8388ebdf 100644 --- a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala +++ b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala @@ -3,8 +3,8 @@ package code.api.v7_0_0 import cats.data.{Kleisli, OptionT} import cats.effect._ import code.api.Constant._ -import code.api.ResourceDocs1_4_0.ResourceDocsAPIMethodsUtil import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ +import code.api.ResourceDocs1_4_0.{ResourceDocs140, ResourceDocsAPIMethodsUtil} import code.api.util.APIUtil.{EmptyBody, _} import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ @@ -55,13 +55,6 @@ object Http4s700 { // Common prefix: /obp/v7.0.0 val prefixPath = Root / ApiPathZero.toString / implementedInApiVersion.toString - private def getResourceDocsList(requestedApiVersion: ApiVersion): List[ResourceDoc] = { - requestedApiVersion match { - case version: ScannedApiVersion => - resourceDocs.toList - case _ => Nil - } - } resourceDocs += ResourceDoc( null, @@ -146,7 +139,7 @@ object Http4s700 { tags = tagsParam.map(_.map(ResourceDocTag(_))) functions = functionsParam.map(_.toList) requestedApiVersion <- Future(ApiVersionUtils.valueOf(requestedApiVersionString)) - resourceDocs = getResourceDocsList(requestedApiVersion) + resourceDocs = ResourceDocs140.ImplementationsResourceDocs.getResourceDocsList(requestedApiVersion).getOrElse(Nil) filteredDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, tags, functions) resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam) } yield convertAnyToJsonString(resourceDocsJson) From 42fec68ae9a665ed586f32b1ff619b42c4a0ab5a Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 18 Dec 2025 14:32:29 +0100 Subject: [PATCH 7/9] docfix/Update .gitignore to exclude `.trae` files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d990d9c465..8b845ec5be 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ *.code-workspace .zed .cursor +.trae .classpath .project .cache From e78637d056353e099eee2dde04baa1e3f821cecd Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 19 Dec 2025 09:06:24 +0100 Subject: [PATCH 8/9] test/ApiVersionUtilsTest: Update expected version count to 25 --- obp-api/src/test/scala/code/util/ApiVersionUtilsTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/test/scala/code/util/ApiVersionUtilsTest.scala b/obp-api/src/test/scala/code/util/ApiVersionUtilsTest.scala index 1a14ed8965..05d1bd5104 100644 --- a/obp-api/src/test/scala/code/util/ApiVersionUtilsTest.scala +++ b/obp-api/src/test/scala/code/util/ApiVersionUtilsTest.scala @@ -20,6 +20,6 @@ class ApiVersionUtilsTest extends V400ServerSetup { versions.map(version => ApiVersionUtils.valueOf(version.fullyQualifiedVersion)) //NOTE, when we added the new version, better fix this number manually. and also check the versions - versions.length shouldBe(24) + versions.length shouldBe(25) }} } \ No newline at end of file From 0fc3453a6c9c935570ed2148352f70803d5e81b5 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 19 Dec 2025 11:26:29 +0100 Subject: [PATCH 9/9] feature/WebUI: Add support for configurable external portal URL - Introduce `webui_portal_external_url` property in `sample.props.template` - Update `externalConsumerRegistrationLink` to use `webui_portal_external_url` instead of `webui_api_explorer_url` --- obp-api/src/main/resources/props/sample.props.template | 3 +++ obp-api/src/main/scala/code/snippet/WebUI.scala | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index f9416680e8..b097bc8f99 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -491,6 +491,9 @@ webui_top_text= #webui_footer2_middle_text= +# External Portal URL, change to your instance +webui_portal_external_url = http://localhost:5174 + # API Explorer URL, change to your instance webui_api_explorer_url = https://apiexplorer.openbankproject.com diff --git a/obp-api/src/main/scala/code/snippet/WebUI.scala b/obp-api/src/main/scala/code/snippet/WebUI.scala index 63214fa925..e2f4723180 100644 --- a/obp-api/src/main/scala/code/snippet/WebUI.scala +++ b/obp-api/src/main/scala/code/snippet/WebUI.scala @@ -315,8 +315,8 @@ class WebUI extends MdcLoggable{ // Uses webui_api_explorer_url + /consumers/register as default. // Configure webui_external_consumer_registration_url to override with a custom URL. def externalConsumerRegistrationLink: CssSel = { - val apiExplorerUrl = getWebUiPropsValue("webui_api_explorer_url", "http://localhost:5174") - val defaultConsumerRegisterUrl = s"$apiExplorerUrl/consumers/register" + val portalExternalUrl = getWebUiPropsValue("webui_portal_external_url", "http://localhost:5174") + val defaultConsumerRegisterUrl = s"$portalExternalUrl/consumers/register" val externalUrl = getWebUiPropsValue("webui_external_consumer_registration_url", defaultConsumerRegisterUrl) ".get-api-key-link a [href]" #> scala.xml.Unparsed(externalUrl) & ".get-api-key-link a [target]" #> "_blank" &