From a8f16a87f2e7b82712306e6c6c6e5821c4810653 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 9 Dec 2025 09:09:37 +0100 Subject: [PATCH] Refactor/Http4sServer to use v7.0.0 API and remove deprecated routes. Introduce new JSONFactory for v7.0.0 and update server configuration to use dynamic host and port settings. Clean up unused Middleware and RestRoutes files. --- .../scala/bootstrap/http4s/Http4sServer.scala | 21 ++-- .../scala/bootstrap/http4s/Middleware.scala | 26 ----- .../scala/bootstrap/http4s/RestRoutes.scala | 62 ----------- .../scala/code/api/v1_3_0/Http4s130.scala | 105 ------------------ .../scala/code/api/v7_0_0/Http4s700.scala | 69 ++++++++++++ .../code/api/v7_0_0/JSONFactory7.0.0.scala | 72 ++++++++++++ .../commons/util/ApiVersion.scala | 3 + 7 files changed, 152 insertions(+), 206 deletions(-) delete mode 100644 obp-api/src/main/scala/bootstrap/http4s/Middleware.scala delete mode 100644 obp-api/src/main/scala/bootstrap/http4s/RestRoutes.scala delete mode 100644 obp-api/src/main/scala/code/api/v1_3_0/Http4s130.scala create mode 100644 obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala create mode 100644 obp-api/src/main/scala/code/api/v7_0_0/JSONFactory7.0.0.scala diff --git a/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala b/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala index 812e5c488..0672f3b70 100644 --- a/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala +++ b/obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala @@ -1,6 +1,5 @@ package bootstrap.http4s -import bootstrap.http4s.RestRoutes.{bankServices, helloWorldService} import cats.data.{Kleisli, OptionT} import scala.language.higherKinds @@ -9,33 +8,29 @@ import com.comcast.ip4s._ import org.http4s.ember.server._ import org.http4s.implicits._ import cats.effect._ +import code.api.util.APIUtil import org.http4s._ object Http4sServer extends IOApp { val services: Kleisli[({type λ[β$0$] = OptionT[IO, β$0$]})#λ, Request[IO], Response[IO]] = - bankServices <+> - helloWorldService <+> - code.api.v1_3_0.Http4s130.wrappedRoutesV130Services + code.api.v7_0_0.Http4s700.wrappedRoutesV700Services val httpApp: Kleisli[IO, Request[IO], Response[IO]] = (services).orNotFound //Start OBP relevant objects, and settings new bootstrap.liftweb.Boot().boot + + val port = APIUtil.getPropsAsIntValue("http4s.port",8181) + val host = APIUtil.getPropsValue("http4s.host","127.0.0.1") + override def run(args: List[String]): IO[ExitCode] = EmberServerBuilder .default[IO] - .withHost(ipv4"0.0.0.0") - .withPort(port"8081") + .withHost(Host.fromString(host).get) + .withPort(Port.fromInt(port).get) .withHttpApp(httpApp) .build .use(_ => IO.never) .as(ExitCode.Success) } -//this is testing code -object myApp extends App{ - import cats.effect.unsafe.implicits.global - Http4sServer.run(Nil).unsafeRunSync() -// Http4sServer.run(Nil).unsafeToFuture()//.unsafeRunSync() -} - diff --git a/obp-api/src/main/scala/bootstrap/http4s/Middleware.scala b/obp-api/src/main/scala/bootstrap/http4s/Middleware.scala deleted file mode 100644 index 777cdf317..000000000 --- a/obp-api/src/main/scala/bootstrap/http4s/Middleware.scala +++ /dev/null @@ -1,26 +0,0 @@ -//package bootstrap.http4s -// -//import cats._ -//import cats.effect._ -//import cats.implicits._ -//import cats.data._ -//import code.api.util.CallContext -//import org.http4s._ -//import org.http4s.dsl.io._ -//import org.http4s.server._ -// -//object Middleware { -// -// val authUser: Kleisli[OptionT[IO, *], Request[IO], CallContext] = -// Kleisli(_ => OptionT.liftF(IO(???))) -// -// val middleware: AuthMiddleware[IO, CallContext] = AuthMiddleware(authUser) -// -// val authedRoutes: AuthedRoutes[CallContext, IO] = -// AuthedRoutes.of { -// case GET -> Root / "welcome" as callContext => Ok(s"Welcome, ${callContext}") -// } -// -// val service: HttpRoutes[IO] = middleware(authedRoutes) -// -//} diff --git a/obp-api/src/main/scala/bootstrap/http4s/RestRoutes.scala b/obp-api/src/main/scala/bootstrap/http4s/RestRoutes.scala deleted file mode 100644 index 4262f95a3..000000000 --- a/obp-api/src/main/scala/bootstrap/http4s/RestRoutes.scala +++ /dev/null @@ -1,62 +0,0 @@ -package bootstrap.http4s - -import cats.effect._ -import org.http4s.{HttpRoutes, _} -import org.http4s.dsl.io._ -import cats.implicits._ -import code.api.util.{APIUtil, CustomJsonFormats} -import code.bankconnectors.Connector -import code.model.dataAccess.MappedBank -import com.openbankproject.commons.model.BankCommons -import net.liftweb.json.Formats -import org.http4s.HttpRoutes -import org.http4s.dsl.Http4sDsl - -import scala.language.higherKinds -import cats.effect._ -import code.api.v4_0_0.JSONFactory400 -import org.http4s._ -import org.http4s.dsl.io._ -import net.liftweb.json.JsonAST.{JValue, prettyRender} -import net.liftweb.json.{Extraction, MappingException, compactRender, parse} - -object RestRoutes { - implicit val formats: Formats = CustomJsonFormats.formats - - val helloWorldService: HttpRoutes[IO] = HttpRoutes.of[IO] { - case GET -> Root / "hello" / name => - Ok(s"Hello, $name.") - } - - val bankServices: HttpRoutes[IO] = HttpRoutes.of[IO] { - case GET -> Root / "banks" => - val banks = Connector.connector.vend.getBanksLegacy(None).map(_._1).openOrThrowException("xxxxx") - Ok(prettyRender(Extraction.decompose(banks))) - case GET -> Root / "banks"/ "future" => - import scala.concurrent.Future - import scala.concurrent.ExecutionContext.Implicits.global - Ok(IO.fromFuture(IO( - for { - (banks, callContext) <- code.api.util.NewStyle.function.getBanks(None) - } yield { - prettyRender(Extraction.decompose(JSONFactory400.createBanksJson(banks))) - } - ))) - - val banks = Connector.connector.vend.getBanksLegacy(None).map(_._1).openOrThrowException("xxxxx") - Ok(prettyRender(Extraction.decompose(banks))) - case GET -> Root / "banks" / IntVar(bankId) => - val bank = BankCommons( - bankId = com.openbankproject.commons.model.BankId("bankIdExample.value"), - shortName = "bankShortNameExample.value", - fullName = "bankFullNameExample.value", - logoUrl = "bankLogoUrlExample.value", - websiteUrl = "bankWebsiteUrlExample.value", - bankRoutingScheme = "bankRoutingSchemeExample.value", - bankRoutingAddress = "bankRoutingAddressExample.value", - swiftBic = "bankSwiftBicExample.value", - nationalIdentifier = "bankNationalIdentifierExample.value") - Ok(prettyRender(Extraction.decompose(bank))) - } - -} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/api/v1_3_0/Http4s130.scala b/obp-api/src/main/scala/code/api/v1_3_0/Http4s130.scala deleted file mode 100644 index f1279c0f2..000000000 --- a/obp-api/src/main/scala/code/api/v1_3_0/Http4s130.scala +++ /dev/null @@ -1,105 +0,0 @@ -package code.api.v1_3_0 - -import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ -import code.api.util.APIUtil._ -import code.api.util.ApiTag._ -import code.api.util.ErrorMessages._ -import code.api.util.FutureUtil.EndpointContext -import code.api.util.NewStyle.HttpCode -import code.api.util.{ApiRole, NewStyle} -import code.api.v1_2_1.JSONFactory -import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model.BankId -import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} -import net.liftweb.common.Full -import net.liftweb.http.rest.RestHelper - -import scala.collection.mutable.ArrayBuffer -import scala.concurrent.Future -import cats.effect._ -import org.http4s.{HttpRoutes, _} -import org.http4s.dsl.io._ -import cats.implicits._ -import code.api.util.{APIUtil, CustomJsonFormats} -import code.bankconnectors.Connector -import code.model.dataAccess.MappedBank -import com.openbankproject.commons.model.BankCommons -import net.liftweb.json.Formats -import org.http4s.HttpRoutes -import org.http4s.dsl.Http4sDsl - -import scala.language.{higherKinds, implicitConversions} -import cats.effect._ -import code.api.v4_0_0.JSONFactory400 -import org.http4s._ -import org.http4s.dsl.io._ -import net.liftweb.json.JsonAST.{JValue, prettyRender} -import net.liftweb.json.{Extraction, MappingException, compactRender, parse} -import cats.effect._ -import cats.data.Kleisli -import org.http4s._ -import org.http4s.dsl.io._ -import org.http4s.implicits._ -import org.http4s.ember.server.EmberServerBuilder -import com.comcast.ip4s._ - -import cats.effect.IO -import org.http4s.{HttpRoutes, Request, Response} -import org.http4s.dsl.io._ -import org.typelevel.vault.Key - -object Http4s130 { - - implicit val formats: Formats = CustomJsonFormats.formats - implicit def convertAnyToJsonString(any: Any): String = prettyRender(Extraction.decompose(any)) - - val apiVersion: ScannedApiVersion = ApiVersion.v1_3_0 - - case class CallContext(userId: String, requestId: String) - import cats.effect.unsafe.implicits.global - val callContextKey: Key[CallContext] = Key.newKey[IO, CallContext].unsafeRunSync() - - object CallContextMiddleware { - - - def withCallContext(routes: HttpRoutes[IO]): HttpRoutes[IO] = Kleisli { req: Request[IO] => - val callContext = CallContext(userId = "example-user", requestId = java.util.UUID.randomUUID().toString) - val updatedAttributes = req.attributes.insert(callContextKey, callContext) - val updatedReq = req.withAttributes(updatedAttributes) - routes(updatedReq) - } - } - - - val v130Services: HttpRoutes[IO] = HttpRoutes.of[IO] { - case req @ GET -> Root / apiVersion / "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( - JSONFactory.getApiInfoJSON(OBPAPI1_3_0.version, s"Hello, ${callContext.userId}! Your request ID is ${callContext.requestId}.") - ) - } - ))) - -// case req @ GET -> Root / apiVersion / "cards" => { -// Ok(IO.fromFuture(IO({ -// val callContext = req.attributes.lookup(callContextKey).get.asInstanceOf[CallContext] -// import com.openbankproject.commons.ExecutionContext.Implicits.global -// for { -// (Full(u), callContext) <- authenticatedAccess(None) -// (cards, callContext) <- NewStyle.function.getPhysicalCardsForUser(u, callContext) -// } yield { -// convertAnyToJsonString( -// JSONFactory1_3_0.createPhysicalCardsJSON(cards, u) -// ) -// } -// }))) -// } - } - - val wrappedRoutesV130Services = CallContextMiddleware.withCallContext(v130Services) -} 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 new file mode 100644 index 000000000..10907b1e8 --- /dev/null +++ b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala @@ -0,0 +1,69 @@ +package code.api.v7_0_0 + +import cats.data.Kleisli +import cats.effect._ +import cats.implicits._ +import code.api.util.{APIUtil, CustomJsonFormats} +import code.api.v4_0_0.JSONFactory400 +import code.bankconnectors.Connector +import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} +import net.liftweb.json.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.concurrent.Future +import scala.language.{higherKinds, implicitConversions} + +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 + + case class CallContext(userId: String, requestId: String) + import cats.effect.unsafe.implicits.global + val callContextKey: Key[CallContext] = Key.newKey[IO, CallContext].unsafeRunSync() + + object CallContextMiddleware { + + def withCallContext(routes: HttpRoutes[IO]): HttpRoutes[IO] = Kleisli { req: Request[IO] => + val callContext = CallContext(userId = "example-user", requestId = java.util.UUID.randomUUID().toString) + val updatedAttributes = req.attributes.insert(callContextKey, callContext) + val updatedReq = req.withAttributes(updatedAttributes) + routes(updatedReq) + } + } + + 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)) + } + ))) + } + + val wrappedRoutesV700Services: HttpRoutes[IO] = CallContextMiddleware.withCallContext(v700Services) +} + diff --git a/obp-api/src/main/scala/code/api/v7_0_0/JSONFactory7.0.0.scala b/obp-api/src/main/scala/code/api/v7_0_0/JSONFactory7.0.0.scala new file mode 100644 index 000000000..a675842e6 --- /dev/null +++ b/obp-api/src/main/scala/code/api/v7_0_0/JSONFactory7.0.0.scala @@ -0,0 +1,72 @@ +package code.api.v7_0_0 + +import code.api.Constant +import code.api.util.APIUtil +import code.api.util.ErrorMessages.MandatoryPropertyIsNotSet +import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400} +import code.util.Helper.MdcLoggable +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.util.Props + +object JSONFactory700 extends MdcLoggable { + + // Get git commit from build info + lazy val gitCommit: String = { + val commit = try { + Props.get("git.commit.id", "unknown") + } catch { + case _: Throwable => "unknown" + } + commit + } + + case class APIInfoJsonV700( + version: String, + version_status: String, + git_commit: String, + stage: String, + connector: String, + hostname: String, + local_identity_provider: String, + hosted_by: HostedBy400, + hosted_at: HostedAt400, + energy_source: EnergySource400, + resource_docs_requires_role: Boolean, + message: String + ) + + def getApiInfoJSON(apiVersion: ApiVersion, message: String): APIInfoJsonV700 = { + val organisation = APIUtil.getPropsValue("hosted_by.organisation", "TESOBE") + val email = APIUtil.getPropsValue("hosted_by.email", "contact@tesobe.com") + val phone = APIUtil.getPropsValue("hosted_by.phone", "+49 (0)30 8145 3994") + val organisationWebsite = APIUtil.getPropsValue("organisation_website", "https://www.tesobe.com") + val hostedBy = new HostedBy400(organisation, email, phone, organisationWebsite) + + val organisationHostedAt = APIUtil.getPropsValue("hosted_at.organisation", "") + val organisationWebsiteHostedAt = APIUtil.getPropsValue("hosted_at.organisation_website", "") + val hostedAt = HostedAt400(organisationHostedAt, organisationWebsiteHostedAt) + + val organisationEnergySource = APIUtil.getPropsValue("energy_source.organisation", "") + val organisationWebsiteEnergySource = APIUtil.getPropsValue("energy_source.organisation_website", "") + val energySource = EnergySource400(organisationEnergySource, organisationWebsiteEnergySource) + + val connector = code.api.Constant.CONNECTOR.openOrThrowException(s"$MandatoryPropertyIsNotSet. The missing prop is `connector` ") + val resourceDocsRequiresRole = APIUtil.getPropsAsBoolValue("resource_docs_requires_role", false) + + APIInfoJsonV700( + version = apiVersion.vDottedApiVersion, + version_status = "BLEEDING_EDGE", + git_commit = gitCommit, + connector = connector, + hostname = Constant.HostName, + stage = System.getProperty("run.mode"), + local_identity_provider = Constant.localIdentityProvider, + hosted_by = hostedBy, + hosted_at = hostedAt, + energy_source = energySource, + resource_docs_requires_role = resourceDocsRequiresRole, + message = message + ) + } +} + diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/ApiVersion.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/ApiVersion.scala index 0a5617d18..6173ec700 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/util/ApiVersion.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/ApiVersion.scala @@ -23,6 +23,7 @@ object ApiShortVersions extends Enumeration { val `v5.0.0` = Value("v5.0.0") val `v5.1.0` = Value("v5.1.0") val `v6.0.0` = Value("v6.0.0") + val `v7.0.0` = Value("v7.0.0") val `dynamic-endpoint` = Value("dynamic-endpoint") val `dynamic-entity` = Value("dynamic-entity") } @@ -114,6 +115,7 @@ object ApiVersion { val v5_0_0 = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`v5.0.0`.toString) val v5_1_0 = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`v5.1.0`.toString) val v6_0_0 = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`v6.0.0`.toString) + val v7_0_0 = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`v7.0.0`.toString) val `dynamic-endpoint` = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`dynamic-endpoint`.toString) val `dynamic-entity` = ScannedApiVersion(urlPrefix,ApiStandards.obp.toString,ApiShortVersions.`dynamic-entity`.toString) @@ -131,6 +133,7 @@ object ApiVersion { v5_0_0 :: v5_1_0 :: v6_0_0 :: + v7_0_0 :: `dynamic-endpoint` :: `dynamic-entity`:: Nil