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.

This commit is contained in:
hongwei 2025-12-09 09:09:37 +01:00
parent 2ae58966c5
commit a8f16a87f2
7 changed files with 152 additions and 206 deletions

View File

@ -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()
}

View File

@ -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)
//
//}

View File

@ -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)))
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
)
}
}

View File

@ -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