Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
30835d1
Migrate UKOpenBanking v2.0/v3.1 endpoints to native http4s routing
hongwei1 May 28, 2026
8e77487
refactor(uk-ob): comment out migrated Lift endpoint files, slim OBP a…
hongwei1 May 28, 2026
79c380f
refactor(uk-openbanking-v3.1): migrate genuine endpoints (AccountAcce…
hongwei1 May 28, 2026
9fe92a9
refactor(uk-openbanking-v3.1): restore ResourceDoc request/response e…
hongwei1 May 28, 2026
045aa43
fix(resource-docs): add explicit activeResourceDocs cases for ukOpenB…
hongwei1 May 28, 2026
8de61b8
fix(uk-openbanking-v3.1): fix compile errors in Http4sUKOBv310 files
hongwei1 May 28, 2026
7c01820
fix(uk-openbanking-v3.1): fix remaining compile errors after parseBod…
hongwei1 May 28, 2026
fa2506b
fix(uk-ob-v31): resolve compile errors in Http4sUKOBv310Transactions
hongwei1 May 28, 2026
586478e
fix(uk-ob-v31): restore unboxFull after fullBoxOrException in Transac…
hongwei1 May 28, 2026
8583a5d
fix(uk-ob): fix apiVersion override type in OBP aggregator stubs
hongwei1 May 28, 2026
94f0b24
fix(test): update APIUtilHeavyTest to use http4s UK v3.1 AccountAcces…
hongwei1 May 28, 2026
64d5ff1
fix(uk-ob): add missing v2.0 endpoints and fix v3.1 createConsents auth
hongwei1 May 28, 2026
d1a1c1f
Fix createAccountAccessConsents: check auth before body parse to retu…
hongwei1 May 28, 2026
09c4e92
Fix compile errors: add CallContext and RequestOps imports for execut…
hongwei1 May 28, 2026
eeb54a7
Correct stale comments claiming UK endpoints fall through to Lift bridge
hongwei1 May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
case ApiVersion.v1_4_0 => resourceDocs // fully on http4s — no Lift route filter
case ApiVersion.v1_3_0 => resourceDocs // fully on http4s — no Lift route filter
case ApiVersion.`dynamic-entity` => resourceDocs // runtime CRUD now on Http4sDynamicEntity; routes are Nil, skip Lift-route filter
case ApiVersion.ukOpenBankingV20 => resourceDocs // fully on http4s — no Lift route filter
case ApiVersion.ukOpenBankingV31 => resourceDocs // fully on http4s — no Lift route filter
case _ => resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass))
}

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package code.api.UKOpenBanking.v2_0_0

import cats.data.{Kleisli, OptionT}
import cats.effect._
import code.api.util.APIUtil.ResourceDoc
import code.api.util.http4s.ResourceDocMiddleware
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.util.ApiVersion
import org.http4s._

import scala.collection.mutable.ArrayBuffer

/**
* UK Open Banking v2.0 — http4s aggregator (mirror of Berlin Group's Http4sBGv2).
*
* Wraps the migrated account-information routes with ResourceDocMiddleware and
* exposes `wrappedRoutes` for Http4sApp. All 5 v2.0 endpoints — including the two
* account-scoped ones (/accounts/ID/balances, /accounts/ID/transactions) — are
* migrated in Http4sUKOBv200AIS. The Lift ScannedApis aggregator
* (OBP_UKOpenBanking_200) registers `routes = Nil`, so no UK v2.0 path is served
* by Lift — nothing falls through to the Lift bridge.
*/
object Http4sUKOBv200 extends MdcLoggable {

type HttpF[A] = OptionT[IO, A]

val implementedInApiVersion: ApiVersion = ApiVersion.ukOpenBankingV20

val resourceDocs: ArrayBuffer[ResourceDoc] =
Http4sUKOBv200AIS.resourceDocs

val allRoutes: HttpRoutes[IO] = Kleisli[HttpF, Request[IO], Response[IO]] { req =>
Http4sUKOBv200AIS.routes(req)
}

val wrappedRoutes: HttpRoutes[IO] = ResourceDocMiddleware.apply(resourceDocs)(allRoutes)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package code.api.UKOpenBanking.v2_0_0

import cats.data.{Kleisli, OptionT}
import cats.effect.IO
import code.api.APIFailureNewStyle
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.util.APIUtil.{EmptyBody, ResourceDoc, createQueriesByHttpParams, fullBoxOrException, unboxFull}
import code.api.util.ApiTag._
import code.api.util.CustomJsonFormats
import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, UnknownError}
import code.api.util.NewStyle
import code.api.util.http4s.Http4sRequestAttributes.EndpointHelpers
import code.api.util.newstyle.ViewNewStyle
import code.model.BankAccountExtended
import code.util.Helper.MdcLoggable
import code.views.Views
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.{AccountId, BankIdAccountId}
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
import net.liftweb.common.Full
import net.liftweb.http.provider.HTTPParam
import net.liftweb.json.Formats
import org.http4s._
import org.http4s.dsl.io._

import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future

/**
* UK Open Banking v2.0 — account-information endpoints migrated from Lift to http4s.
*
* Faithful migration of all 5 endpoints:
* - getAccountList (/accounts), getAccount (/accounts/ID), getBalances (/balances)
* — list/aggregate endpoints using getBankAccounts + a JSON factory.
* - getAccountBalances (/accounts/ID/balances), getAccountTransactions
* (/accounts/ID/transactions) — account-scoped endpoints; their 3-segment
* patterns are distinct from the 2-segment ones, so http4s route matching
* picks the correct handler.
* No v2.0 endpoint is left on Lift.
*/
object Http4sUKOBv200AIS extends MdcLoggable {
type HttpF[A] = OptionT[IO, A]
implicit val formats: Formats = CustomJsonFormats.formats
val implementedInApiVersion: ScannedApiVersion = ApiVersion.ukOpenBankingV20
val resourceDocs = ArrayBuffer[ResourceDoc]()
val ukV20Prefix = Root / ApiVersion.ukOpenBankingV20.urlPrefix / ApiVersion.ukOpenBankingV20.apiShortVersion

// GET /accounts — list all private accounts of the logged-in user
lazy val getAccountList: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ GET -> `ukV20Prefix` / "accounts" =>
EndpointHelpers.withUser(req) { (u, cc) =>
val callContext = Some(cc)
for {
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u)
(accounts, _) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext)
} yield JSONFactory_UKOpenBanking_200.createAccountsListJSON(accounts)
}
}
resourceDocs += ResourceDoc(
null,
implementedInApiVersion,
nameOf(getAccountList),
"GET",
"/accounts",
"UK Open Banking: Get Account List",
"""Reads a list of bank accounts, with balances where required.""",
EmptyBody,
SwaggerDefinitionsJSON.accountsJsonUKOpenBanking_v200,
List(AuthenticatedUserIsRequired, UnknownError),
List(apiTagUKOpenBanking, apiTagAccount, apiTagPrivateData),
http4sPartialFunction = Some(getAccountList)
)

// GET /accounts/{accountId} — single account
lazy val getAccount: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ GET -> `ukV20Prefix` / "accounts" / accountId =>
EndpointHelpers.withUser(req) { (u, cc) =>
val callContext = Some(cc)
for {
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u).map(_.filter(_.accountId.value == accountId))
(accounts, _) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext)
} yield JSONFactory_UKOpenBanking_200.createAccountJSON(accounts)
}
}
resourceDocs += ResourceDoc(
null,
implementedInApiVersion,
nameOf(getAccount),
"GET",
"/accounts/ACCOUNT_ID",
"UK Open Banking: Get Account",
"""Reads a bank account, with balances where required.""",
EmptyBody,
SwaggerDefinitionsJSON.accountsJsonUKOpenBanking_v200,
List(AuthenticatedUserIsRequired, UnknownError),
List(apiTagUKOpenBanking, apiTagAccount, apiTagPrivateData),
http4sPartialFunction = Some(getAccount)
)

// GET /balances — bulk balances for all private accounts
lazy val getBalances: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ GET -> `ukV20Prefix` / "balances" =>
EndpointHelpers.withUser(req) { (u, cc) =>
val callContext = Some(cc)
for {
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u)
(accounts, _) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext)
} yield JSONFactory_UKOpenBanking_200.createBalancesJSON(accounts)
}
}
resourceDocs += ResourceDoc(
null,
implementedInApiVersion,
nameOf(getBalances),
"GET",
"/balances",
"UK Open Banking: Get Balances",
"""Bulk retrieval of balances for all authorised accounts.""",
EmptyBody,
SwaggerDefinitionsJSON.accountBalancesUKV200,
List(AuthenticatedUserIsRequired, UnknownError),
List(apiTagUKOpenBanking, apiTagAccount, apiTagPrivateData),
http4sPartialFunction = Some(getBalances)
)

// GET /accounts/{accountId}/balances — account-level balances
lazy val getAccountBalances: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ GET -> `ukV20Prefix` / "accounts" / accountIdStr / "balances" =>
EndpointHelpers.withUser(req) { (u, cc) =>
for {
(account, _) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountIdStr), Some(cc))
view <- ViewNewStyle.checkOwnerViewAccessAndReturnOwnerView(u, BankIdAccountId(account.bankId, account.accountId), Some(cc))
moderatedAccount <- Future {
BankAccountExtended(account).moderatedBankAccount(view, BankIdAccountId(account.bankId, account.accountId), Full(u), Some(cc))
} map { x => unboxFull(fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, Some(cc.toLight)))) }
} yield JSONFactory_UKOpenBanking_200.createAccountBalanceJSON(moderatedAccount)
}
}
resourceDocs += ResourceDoc(
null,
implementedInApiVersion,
nameOf(getAccountBalances),
"GET",
"/accounts/ACCOUNT_ID/balances",
"UK Open Banking: Get Account Balances",
"""An AISP may retrieve the account balance information resource for a specific AccountId.""",
EmptyBody,
SwaggerDefinitionsJSON.accountBalancesUKV200,
List(AuthenticatedUserIsRequired, UnknownError),
List(apiTagUKOpenBanking, apiTagAccount, apiTagPrivateData),
http4sPartialFunction = Some(getAccountBalances)
)

// GET /accounts/{accountId}/transactions — account-level transactions
lazy val getAccountTransactions: HttpRoutes[IO] = HttpRoutes.of[IO] {
case req @ GET -> `ukV20Prefix` / "accounts" / accountIdStr / "transactions" =>
EndpointHelpers.withUser(req) { (u, cc) =>
for {
(account, _) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountIdStr), Some(cc))
(bank, _) <- NewStyle.function.getBank(account.bankId, Some(cc))
view <- ViewNewStyle.checkOwnerViewAccessAndReturnOwnerView(u, BankIdAccountId(account.bankId, account.accountId), Some(cc))
params <- Future {
createQueriesByHttpParams(req.headers.headers.toList.map(h => HTTPParam(h.name.toString, List(h.value))))
} map { x => unboxFull(fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, Some(cc.toLight)))) }
(transactions, _) <- Future {
BankAccountExtended(account).getModeratedTransactions(bank, Full(u), view, BankIdAccountId(account.bankId, account.accountId), Some(cc), params)
} map { x => unboxFull(fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, Some(cc.toLight)))) }
} yield JSONFactory_UKOpenBanking_200.createTransactionsJson(transactions, Nil)
}
}
resourceDocs += ResourceDoc(
null,
implementedInApiVersion,
nameOf(getAccountTransactions),
"GET",
"/accounts/ACCOUNT_ID/transactions",
"UK Open Banking: Get Account Transactions",
"""Reads account data from a given account addressed by "account-id".""",
EmptyBody,
SwaggerDefinitionsJSON.transactionsJsonUKV200,
List(AuthenticatedUserIsRequired, UnknownError),
List(apiTagUKOpenBanking, apiTagTransaction, apiTagPrivateData),
http4sPartialFunction = Some(getAccountTransactions)
)

val routes: HttpRoutes[IO] = Kleisli[HttpF, Request[IO], Response[IO]] { req =>
getAccountList(req)
.orElse(getAccount(req))
.orElse(getAccountBalances(req))
.orElse(getAccountTransactions(req))
.orElse(getBalances(req))
}
}
Original file line number Diff line number Diff line change
@@ -1,68 +1,46 @@
/**
Open Bank Project - API
Copyright (C) 2011-2019, TESOBE GmbH.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

Email: contact@tesobe.com
TESOBE GmbH.
Osloer Strasse 16/17
Berlin 13359, Germany

This product includes software developed at
TESOBE (http://www.tesobe.com/)

*/
package code.api.UKOpenBanking.v2_0_0

import code.api.OBPRestHelper
import code.api.util.APIUtil.{OBPEndpoint, getAllowedEndpoints}
import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc}
import code.api.util.ScannedApis
import code.util.Helper.MdcLoggable
import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus, ScannedApiVersion}

import scala.collection.immutable.Nil
import code.api.UKOpenBanking.v2_0_0.APIMethods_UKOpenBanking_200._
import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus}

import scala.collection.mutable.ArrayBuffer

/*
This file defines which endpoints from all the versions are available in v1
* All v2.0 UK Open Banking endpoints have been migrated to Http4sUKOBv200AIS.
* This stub is retained for ScannedApis registration (class-path scanning) and
* so that external callers (APIUtil, SwaggerJSONFactory) that access
* OBP_UKOpenBanking_200.apiVersion / .allResourceDocs continue to compile.
* Routes are served by Http4sUKOBv200.wrappedRoutes in Http4sApp (ahead of the Lift bridge).
*/
object OBP_UKOpenBanking_200 extends OBPRestHelper with MdcLoggable with ScannedApis {

override val apiVersion: ScannedApiVersion = ApiVersion.ukOpenBankingV20
val versionStatus: String = ApiVersionStatus.DRAFT.toString

object OBP_UKOpenBanking_200 extends OBPRestHelper with MdcLoggable with ScannedApis{

override val apiVersion = ApiVersion.ukOpenBankingV20
val versionStatus = ApiVersionStatus.DRAFT.toString

val allEndpoints =
getAccountList ::
getAccountTransactions ::
getAccount ::
getAccountBalances ::
getBalances ::
Nil

override val allResourceDocs = resourceDocs

// Filter the possible endpoints by the disabled / enabled Props settings and add them together
override val routes : List[OBPEndpoint] = getAllowedEndpoints(allEndpoints,resourceDocs)


// Make them available for use!
registerRoutes(routes, allResourceDocs, apiPrefix)

logger.info(s"version $version has been run! There are ${routes.length} routes.")
override val allResourceDocs: ArrayBuffer[ResourceDoc] = Http4sUKOBv200.resourceDocs

override val routes: List[OBPEndpoint] = Nil
}

// ─── Original Lift aggregator (commented out) ────────────────────────────────
//import code.api.UKOpenBanking.v2_0_0.APIMethods_UKOpenBanking_200._
//import scala.collection.immutable.{Nil => immNil}
//import code.api.util.APIUtil.getAllowedEndpoints
//
// val allEndpoints =
// getAccountList ::
// getAccountTransactions ::
// getAccount ::
// getAccountBalances ::
// getBalances ::
// immNil
//
// override val allResourceDocs = resourceDocs
//
// override val routes : List[OBPEndpoint] = getAllowedEndpoints(allEndpoints, resourceDocs)
//
// registerRoutes(routes, allResourceDocs, apiPrefix)
// logger.info(s"version $version has been run! There are ${routes.length} routes.")
Loading
Loading