From 2f8549a40ccc2cb7b1a6ca97f96b3d27764b9e10 Mon Sep 17 00:00:00 2001 From: simonredfern Date: Fri, 22 May 2026 05:11:44 +0200 Subject: [PATCH] v7.0.0 corePrivateAccountsAllBanks --- .../scala/code/api/v7_0_0/Http4s700.scala | 59 +++++++++++++++- .../code/api/v7_0_0/JSONFactory7.0.0.scala | 67 ++++++++++++++++++- 2 files changed, 124 insertions(+), 2 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 9c97b5dafb..816d3f6e1a 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 @@ -14,6 +14,7 @@ import code.api.util.http4s.{ErrorResponseConverter, Http4sRequestAttributes, Id import code.api.util.http4s.Http4sRequestAttributes.{EndpointHelpers, RequestOps} import code.api.util.newstyle.ViewNewStyle import code.api.v1_4_0.JSONFactory1_4_0 +import code.api.v2_0_0.AccountsHelper.accountTypeFilterText import code.api.v2_0_0.{BasicViewJson, CreateEntitlementJSON, JSONFactory200} import code.api.v4_0_0.JSONFactory400 import code.api.v6_0_0.{BasicAccountJsonV600, BasicAccountsJsonV600, BankJsonV600, CacheConfigJsonV600, CacheInfoJsonV600, CacheNamespaceInfoJsonV600, CacheNamespaceJsonV600, CacheNamespacesJsonV600, ConnectorInfoJsonV600, ConnectorsJsonV600, DatabasePoolInfoJsonV600, FeaturesJsonV600, InMemoryCacheStatusJsonV600, JSONFactory600, RedisCacheStatusJsonV600, StoredProcedureConnectorHealthJsonV600, UserV600} @@ -33,7 +34,7 @@ import code.metadata.tags.Tags import code.views.Views import code.accountattribute.AccountAttributeX import code.users.{Users => UserVend} -import com.openbankproject.commons.model.{AccountId, BankId, BankIdAccountId, CounterpartyId, CustomerId, ListResult, TransactionRequestType, ViewId} +import com.openbankproject.commons.model.{AccountId, BankId, BankIdAccountId, CoreAccount, CounterpartyId, CustomerId, ListResult, TransactionRequestType, ViewId} import com.openbankproject.commons.model.enums.ChallengeType import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global @@ -344,6 +345,62 @@ object Http4s700 { http4sPartialFunction = Some(getCoreAccountById) ) + // ─── corePrivateAccountsAllBanks (v7) ───────────────────────────────────── + // Same semantics as v3.0.0 /my/accounts but with renamed fields so callers + // can read the (bank_id, account_id, view_id) tuple without remapping. + // v3: { id, ..., views: [ { id, ... } ] } + // v7: { account_id, ..., views: [ { view_id, ... } ] } + + val corePrivateAccountsAllBanks: HttpRoutes[IO] = HttpRoutes.of[IO] { + case req @ GET -> `prefixPath` / "my" / "accounts" => + EndpointHelpers.withUser(req) { (user, cc) => + for { + availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(user) + (coreAccounts, _) <- NewStyle.function.getCoreBankAccountsFuture(availablePrivateAccounts, Some(cc)) + filtered = filterCoreAccountsByType(coreAccounts, req) + } yield JSONFactory700.createCoreAccountsByCoreAccountsJsonV700(filtered, user) + } + } + + resourceDocs += ResourceDoc( + null, + implementedInApiVersion, + nameOf(corePrivateAccountsAllBanks), + "GET", + "/my/accounts", + "Get Accounts at all Banks (private)", + s"""Returns the list of accounts containing private views for the user. + |Each account lists the views available to the user. + | + |This endpoint is the v7.0.0 version of `/obp/v3.0.0/my/accounts` with + |renamed identifier fields: `account_id` (was `id`) and `view_id` (was `id` on each view) + |so the response gives the `(bank_id, account_id, view_id)` tuple directly. + | + |${accountTypeFilterText("/my/accounts")} + | + |${userAuthenticationMessage(true)} + |""", + EmptyBody, + JSONFactory700.coreAccountsJsonV700Example, + List($AuthenticatedUserIsRequired, UnknownError), + List(apiTagAccount, apiTagPSD2AIS, apiTagPrivateData, apiTagPsd2), + None, + http4sPartialFunction = Some(corePrivateAccountsAllBanks) + ) + + private def filterCoreAccountsByType(accounts: List[CoreAccount], req: Request[IO]): List[CoreAccount] = { + val qp = req.uri.query.multiParams + val filters = qp.get("account_type_filter").toList.flatMap(_.flatMap(_.split(","))).filter(_.nonEmpty) + val filtersOperation = qp.get("account_type_filter_operation").flatMap(_.headOption).getOrElse("INCLUDE") + accounts.filter { account => + (filters, filtersOperation) match { + case (f, "INCLUDE") if f.nonEmpty => f.contains(account.accountType) + case (f, "EXCLUDE") if f.nonEmpty => !f.contains(account.accountType) + case _ => true + } + } + } + // Category: withView (user + account + view resolved from BANK_ID + ACCOUNT_ID + VIEW_ID by middleware) val getPrivateAccountByIdFull: HttpRoutes[IO] = HttpRoutes.of[IO] { case req @ GET -> `prefixPath` / "banks" / _ / "accounts" / _ / viewIdStr / "account" => 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 index 8698760f5a..c68c6b9bc8 100644 --- 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 @@ -8,7 +8,8 @@ import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400, PostSimpleCou import code.bankconnectors.Connector import code.customer.CustomerX import code.util.Helper.MdcLoggable -import com.openbankproject.commons.model.{AmountOfMoneyJsonV121, TransactionRequest, TransactionRequestCommonBodyJSON} +import code.views.Views +import com.openbankproject.commons.model.{AccountId, AccountRoutingJsonV121, AmountOfMoneyJsonV121, BankId, BankIdAccountId, CoreAccount, TransactionRequest, TransactionRequestCommonBodyJSON, User} import com.openbankproject.commons.util.ApiVersion import net.liftweb.common.Full @@ -969,4 +970,68 @@ object JSONFactory700 extends MdcLoggable with code.api.util.CustomJsonFormats { } } } + + // ─── Core accounts at all banks (v7 rename: id → account_id / views[].id → view_id) ── + + case class ViewBasicV700( + view_id: String, + short_name: String, + description: String, + is_public: Boolean + ) + + case class CoreAccountJsonV700( + account_id: String, + label: String, + bank_id: String, + account_type: String, + account_routings: List[AccountRoutingJsonV121], + views: List[ViewBasicV700] + ) + + case class CoreAccountsJsonV700(accounts: List[CoreAccountJsonV700]) + + def createCoreAccountsByCoreAccountsJsonV700( + coreAccounts: List[CoreAccount], + user: User + ): CoreAccountsJsonV700 = + CoreAccountsJsonV700(coreAccounts.map { coreAccount => + CoreAccountJsonV700( + account_id = coreAccount.id, + label = coreAccount.label, + bank_id = coreAccount.bankId, + account_type = coreAccount.accountType, + account_routings = coreAccount.accountRoutings.map(r => + AccountRoutingJsonV121(r.scheme, r.address)), + views = Views.views.vend + .privateViewsUserCanAccessForAccount( + user, BankIdAccountId(BankId(coreAccount.bankId), AccountId(coreAccount.id))) + .filter(_.isPrivate) + .map(v => ViewBasicV700( + view_id = v.viewId.value, + short_name = v.name, + description = v.description, + is_public = v.isPublic + )) + ) + }) + + lazy val viewBasicV700Example = ViewBasicV700( + view_id = "owner", + short_name = "Owner", + description = "Owner View", + is_public = false + ) + + lazy val coreAccountJsonV700Example = CoreAccountJsonV700( + account_id = "f026fbd3-d1ea-496b-a853-3cbe65629881", + label = "Account 1", + bank_id = "smnr.bnk.1", + account_type = "330", + account_routings = List(AccountRoutingJsonV121("IBAN", "DE89 3704 0044 0532 0130 00")), + views = List(viewBasicV700Example) + ) + + lazy val coreAccountsJsonV700Example = + CoreAccountsJsonV700(accounts = List(coreAccountJsonV700Example)) }