@@ -2,15 +2,26 @@ package code.api.UKOpenBanking.v4_0_1
22
33import cats .data .{Kleisli , OptionT }
44import cats .effect .IO
5- import code .api .util .APIUtil .{EmptyBody , ResourceDoc }
5+ import code .api .APIFailureNewStyle
6+ import code .api .Constant
7+ import code .api .UKOpenBanking .v3_1_0 .JSONFactory_UKOpenBanking_310 .ConsentPostBodyUKV310
8+ import code .api .util .APIUtil .{EmptyBody , ResourceDoc , HTTPParam , connectorEmptyResponse , createQueriesByHttpParams , defaultBankId , fullBoxOrException , passesPsd2Aisp , unboxFull , unboxFullOrFail , DateWithDayFormat }
69import code .api .util .ApiTag
10+ import code .api .util .CallContext
711import code .api .util .CustomJsonFormats
8- import code .api .util .ErrorMessages .{AuthenticatedUserIsRequired , UnknownError }
9- import code .api .util .http4s .Http4sRequestAttributes .EndpointHelpers
12+ import code .api .util .ErrorMessages .{AuthenticatedUserIsRequired , ConsentNotFound , ConsentViewNotFund , UnknownError }
13+ import code .api .util .http4s .Http4sRequestAttributes .{EndpointHelpers , RequestOps }
14+ import code .api .util .newstyle .ViewNewStyle
15+ import code .api .util .{APIUtil , ConsentJWT , JwtUtil , NewStyle }
16+ import code .consent .Consents
17+ import code .model .{BankAccountExtended , UserExtended }
1018import code .util .Helper .MdcLoggable
19+ import code .views .Views
1120import com .github .dwickern .macros .NameOf .nameOf
21+ import com .openbankproject .commons .model .{AccountId , BankId , BankIdAccountId , TransactionAttribute , View , ViewId }
1222import com .openbankproject .commons .util .{ApiVersion , ScannedApiVersion }
1323import com .openbankproject .commons .util .JsonAliases
24+ import net .liftweb .common .Full
1425import org .json4s .{Formats , JObject }
1526import org .http4s ._
1627import org .http4s .dsl .io ._
@@ -78,7 +89,46 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
7889}"""
7990 lazy val createAccountAccessConsents : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
8091 case req @ POST -> `ukV401Prefix` / " aisp" / " account-access-consents" =>
81- EndpointHelpers .executeFutureCreated(req)(Future .successful(parseBody(EX_createAccountAccessConsents )))
92+ // Check auth FIRST (before body parsing) to mirror Lift's wrappedWithAuthCheck behaviour:
93+ // unauthenticated -> 401, invalid body -> 400.
94+ EndpointHelpers .executeFutureCreated(req) {
95+ implicit val cc : CallContext = req.callContext
96+ for {
97+ u <- cc.user.toOption match {
98+ case Some (user) => Future .successful(user)
99+ case None => Future .failed(new RuntimeException (AuthenticatedUserIsRequired ))
100+ }
101+ consentJson <- Future .fromTry(scala.util.Try (
102+ JsonAliases .parse(cc.httpBody.getOrElse(" {}" )).extract[ConsentPostBodyUKV310 ]
103+ ))
104+ consumerId = cc.consumer.map(_.consumerId.get)
105+ _ <- passesPsd2Aisp(Some (cc))
106+ createdConsent <- Future (Consents .consentProvider.vend.saveUKConsent(
107+ Some (u),
108+ bankId = None ,
109+ accountIds = None ,
110+ consumerId = consumerId,
111+ permissions = consentJson.Data .Permissions ,
112+ expirationDateTime = DateWithDayFormat .parse(consentJson.Data .ExpirationDateTime ),
113+ transactionFromDateTime = DateWithDayFormat .parse(consentJson.Data .TransactionFromDateTime ),
114+ transactionToDateTime = DateWithDayFormat .parse(consentJson.Data .TransactionToDateTime ),
115+ apiStandard = Some (" UKOpenBanking" ),
116+ apiVersion = Some (" 4.0.1" )
117+ )) map { i => connectorEmptyResponse(i, Some (cc)) }
118+ } yield {
119+ JSONFactory_UKOpenBanking_401 .createConsentResponseJSON(
120+ consentId = createdConsent.consentId,
121+ creationDateTime = createdConsent.creationDateTime.toString,
122+ status = createdConsent.status,
123+ statusUpdateDateTime = createdConsent.statusUpdateDateTime.toString,
124+ permissions = consentJson.Data .Permissions ,
125+ expirationDateTime = consentJson.Data .ExpirationDateTime ,
126+ transactionFromDateTime = consentJson.Data .TransactionFromDateTime ,
127+ transactionToDateTime = consentJson.Data .TransactionToDateTime ,
128+ selfPath = " /aisp/account-access-consents"
129+ )
130+ }
131+ }
82132 }
83133 resourceDocs += ResourceDoc (
84134 implementedInApiVersion,
@@ -130,7 +180,28 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
130180}"""
131181 lazy val getAccountAccessConsentsConsentId : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
132182 case req @ GET -> `ukV401Prefix` / " aisp" / " account-access-consents" / consentId =>
133- EndpointHelpers .withUser(req) { (u, cc) => Future .successful(parseBody(EX_getAccountAccessConsentsConsentId )) }
183+ EndpointHelpers .withUser(req) { (_, cc) =>
184+ for {
185+ consent <- Future (Consents .consentProvider.vend.getConsentByConsentId(consentId)) map {
186+ unboxFullOrFail(_, Some (cc), s " $ConsentNotFound ( $consentId) " )
187+ }
188+ consentViews <- Future (JwtUtil .getSignedPayloadAsJson(consent.jsonWebToken).map(
189+ JsonAliases .parse(_).extract[ConsentJWT ].views.map(_.view_id)
190+ )) map { unboxFullOrFail(_, Some (cc), s " $ConsentViewNotFund ( $consentId) " ) }
191+ } yield {
192+ JSONFactory_UKOpenBanking_401 .createConsentResponseJSON(
193+ consentId = consent.consentId,
194+ creationDateTime = consent.creationDateTime.toString,
195+ status = consent.status,
196+ statusUpdateDateTime = consent.statusUpdateDateTime.toString,
197+ permissions = consentViews,
198+ expirationDateTime = consent.expirationDateTime.toString,
199+ transactionFromDateTime = consent.transactionFromDateTime.toString,
200+ transactionToDateTime = consent.transactionToDateTime.toString,
201+ selfPath = s " /aisp/account-access-consents/ $consentId"
202+ )
203+ }
204+ }
134205 }
135206 resourceDocs += ResourceDoc (
136207 implementedInApiVersion,
@@ -149,7 +220,17 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
149220 private val EX_deleteAccountAccessConsentsConsentId : String = """ {}"""
150221 lazy val deleteAccountAccessConsentsConsentId : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
151222 case req @ DELETE -> `ukV401Prefix` / " aisp" / " account-access-consents" / consentId =>
152- EndpointHelpers .executeDelete(req) { cc => Future .successful(()) }
223+ EndpointHelpers .withUserDelete(req) { (_, cc) =>
224+ for {
225+ _ <- passesPsd2Aisp(Some (cc))
226+ _ <- Future (Consents .consentProvider.vend.getConsentByConsentId(consentId)) map {
227+ unboxFullOrFail(_, Some (cc), ConsentNotFound )
228+ }
229+ _ <- Future (Consents .consentProvider.vend.revoke(consentId)) map {
230+ i => connectorEmptyResponse(i, Some (cc))
231+ }
232+ } yield ()
233+ }
153234 }
154235 resourceDocs += ResourceDoc (
155236 implementedInApiVersion,
@@ -239,7 +320,27 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
239320}"""
240321 lazy val getAccounts : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
241322 case req @ GET -> `ukV401Prefix` / " aisp" / " accounts" =>
242- EndpointHelpers .withUser(req) { (u, cc) => Future .successful(parseBody(EX_getAccounts )) }
323+ EndpointHelpers .withUser(req) { (u, cc) =>
324+ val detailViewId = ViewId (Constant .SYSTEM_READ_ACCOUNTS_DETAIL_VIEW_ID )
325+ val basicViewId = ViewId (Constant .SYSTEM_READ_ACCOUNTS_BASIC_VIEW_ID )
326+ for {
327+ _ <- NewStyle .function.checkUKConsent(u, Some (cc))
328+ _ <- passesPsd2Aisp(Some (cc))
329+ availablePrivateAccounts <- Views .views.vend.getPrivateBankAccountsFuture(u)
330+ (accounts, _) <- NewStyle .function.getBankAccounts(availablePrivateAccounts, Some (cc))
331+ (moderatedAttributes, _) <- NewStyle .function.getModeratedAccountAttributesByAccounts(
332+ accounts.map(a => BankIdAccountId (a.bankId, a.accountId)),
333+ basicViewId,
334+ Some (cc))
335+ } yield {
336+ val accountsWithView = accounts.flatMap { account =>
337+ APIUtil .checkViewAccessAndReturnView(detailViewId, BankIdAccountId (account.bankId, account.accountId), Full (u), Some (cc)).or(
338+ APIUtil .checkViewAccessAndReturnView(basicViewId, BankIdAccountId (account.bankId, account.accountId), Full (u), Some (cc))
339+ ).toOption.map(view => (account, view))
340+ }
341+ JSONFactory_UKOpenBanking_401 .createAccountsListJSON(accountsWithView, moderatedAttributes)
342+ }
343+ }
243344 }
244345 resourceDocs += ResourceDoc (
245346 implementedInApiVersion,
@@ -328,8 +429,29 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
328429 }
329430}"""
330431 lazy val getAccountsAccountId : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
331- case req @ GET -> `ukV401Prefix` / " aisp" / " accounts" / accountId =>
332- EndpointHelpers .withUser(req) { (u, cc) => Future .successful(parseBody(EX_getAccountsAccountId )) }
432+ case req @ GET -> `ukV401Prefix` / " aisp" / " accounts" / accountIdStr =>
433+ EndpointHelpers .withUser(req) { (u, cc) =>
434+ val accountId = AccountId (accountIdStr)
435+ val detailViewId = ViewId (Constant .SYSTEM_READ_ACCOUNTS_DETAIL_VIEW_ID )
436+ val basicViewId = ViewId (Constant .SYSTEM_READ_ACCOUNTS_BASIC_VIEW_ID )
437+ for {
438+ availablePrivateAccounts <- Views .views.vend.getPrivateBankAccountsFuture(u) map {
439+ _.filter(_.accountId.value == accountId.value)
440+ }
441+ (accounts, _) <- NewStyle .function.getBankAccounts(availablePrivateAccounts, Some (cc))
442+ (moderatedAttributes, _) <- NewStyle .function.getModeratedAccountAttributesByAccounts(
443+ accounts.map(a => BankIdAccountId (a.bankId, a.accountId)),
444+ basicViewId,
445+ Some (cc))
446+ } yield {
447+ val accountsWithView = accounts.flatMap { account =>
448+ APIUtil .checkViewAccessAndReturnView(detailViewId, BankIdAccountId (account.bankId, account.accountId), Full (u), Some (cc)).or(
449+ APIUtil .checkViewAccessAndReturnView(basicViewId, BankIdAccountId (account.bankId, account.accountId), Full (u), Some (cc))
450+ ).toOption.map(view => (account, view))
451+ }
452+ JSONFactory_UKOpenBanking_401 .createAccountsListJSON(accountsWithView, moderatedAttributes)
453+ }
454+ }
333455 }
334456 resourceDocs += ResourceDoc (
335457 implementedInApiVersion,
@@ -394,8 +516,18 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
394516 }
395517}"""
396518 lazy val getAccountsAccountIdBalances : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
397- case req @ GET -> `ukV401Prefix` / " aisp" / " accounts" / accountId / " balances" =>
398- EndpointHelpers .withUser(req) { (u, cc) => Future .successful(parseBody(EX_getAccountsAccountIdBalances )) }
519+ case req @ GET -> `ukV401Prefix` / " aisp" / " accounts" / accountIdStr / " balances" =>
520+ EndpointHelpers .withUser(req) { (u, cc) =>
521+ val accountId = AccountId (accountIdStr)
522+ val viewId = ViewId (Constant .SYSTEM_READ_BALANCES_VIEW_ID )
523+ for {
524+ _ <- NewStyle .function.checkUKConsent(u, Some (cc))
525+ _ <- passesPsd2Aisp(Some (cc))
526+ (account, _) <- NewStyle .function.getBankAccountByAccountId(accountId, Some (cc))
527+ view <- ViewNewStyle .checkViewAccessAndReturnView(viewId, BankIdAccountId (account.bankId, accountId), Full (u), Some (cc))
528+ moderatedAccount <- NewStyle .function.moderatedBankAccountCore(account, view, Full (u), Some (cc))
529+ } yield JSONFactory_UKOpenBanking_401 .createAccountBalanceJSON(moderatedAccount)
530+ }
399531 }
400532 resourceDocs += ResourceDoc (
401533 implementedInApiVersion,
@@ -2080,8 +2212,32 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
20802212 }
20812213}"""
20822214 lazy val getAccountsAccountIdTransactions : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
2083- case req @ GET -> `ukV401Prefix` / " aisp" / " accounts" / accountId / " transactions" =>
2084- EndpointHelpers .withUser(req) { (u, cc) => Future .successful(parseBody(EX_getAccountsAccountIdTransactions )) }
2215+ case req @ GET -> `ukV401Prefix` / " aisp" / " accounts" / accountIdStr / " transactions" =>
2216+ EndpointHelpers .withUser(req) { (u, cc) =>
2217+ val accountId = AccountId (accountIdStr)
2218+ val detailViewId = ViewId (Constant .SYSTEM_READ_TRANSACTIONS_DETAIL_VIEW_ID )
2219+ val basicViewId = ViewId (Constant .SYSTEM_READ_TRANSACTIONS_BASIC_VIEW_ID )
2220+ for {
2221+ _ <- NewStyle .function.checkUKConsent(u, Some (cc))
2222+ _ <- passesPsd2Aisp(Some (cc))
2223+ (account, _) <- NewStyle .function.getBankAccountByAccountId(accountId, Some (cc))
2224+ (bank, _) <- NewStyle .function.getBank(account.bankId, Some (cc))
2225+ view <- ViewNewStyle .checkViewsAccessAndReturnView(detailViewId, basicViewId, BankIdAccountId (account.bankId, accountId), Full (u), Some (cc))
2226+ params <- Future {
2227+ createQueriesByHttpParams(req.headers.headers.toList.map(h => HTTPParam (h.name.toString, List (h.value))))
2228+ } map { x =>
2229+ unboxFull(fullBoxOrException(x ~> APIFailureNewStyle (UnknownError , 400 , Some (cc.toLight))))
2230+ }
2231+ (transactions, _) <- BankAccountExtended (account).getModeratedTransactionsFuture(bank, Full (u), view, Some (cc), params) map { x =>
2232+ unboxFull(fullBoxOrException(x ~> APIFailureNewStyle (UnknownError , 400 , Some (cc.toLight))))
2233+ }
2234+ (moderatedAttributes : List [TransactionAttribute ], _) <- NewStyle .function.getModeratedAttributesByTransactions(
2235+ account.bankId,
2236+ transactions.map(_.id),
2237+ view.viewId,
2238+ Some (cc))
2239+ } yield JSONFactory_UKOpenBanking_401 .createTransactionsJsonNew(account.bankId, transactions, moderatedAttributes, view)
2240+ }
20852241 }
20862242 resourceDocs += ResourceDoc (
20872243 implementedInApiVersion,
@@ -2147,7 +2303,12 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
21472303}"""
21482304 lazy val getBalances : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
21492305 case req @ GET -> `ukV401Prefix` / " aisp" / " balances" =>
2150- EndpointHelpers .withUser(req) { (u, cc) => Future .successful(parseBody(EX_getBalances )) }
2306+ EndpointHelpers .withUser(req) { (u, cc) =>
2307+ for {
2308+ availablePrivateAccounts <- Views .views.vend.getPrivateBankAccountsFuture(u)
2309+ (accounts, _) <- NewStyle .function.getBankAccounts(availablePrivateAccounts, Some (cc))
2310+ } yield JSONFactory_UKOpenBanking_401 .createBalancesJSON(accounts)
2311+ }
21512312 }
21522313 resourceDocs += ResourceDoc (
21532314 implementedInApiVersion,
@@ -3283,7 +3444,22 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
32833444}"""
32843445 lazy val getTransactions : HttpRoutes [IO ] = HttpRoutes .of[IO ] {
32853446 case req @ GET -> `ukV401Prefix` / " aisp" / " transactions" =>
3286- EndpointHelpers .withUser(req) { (u, cc) => Future .successful(parseBody(EX_getTransactions )) }
3447+ EndpointHelpers .withUser(req) { (u, cc) =>
3448+ for {
3449+ (bank, _) <- NewStyle .function.getBank(BankId (defaultBankId), Some (cc))
3450+ availablePrivateAccounts <- Views .views.vend.getPrivateBankAccountsFuture(u)
3451+ (accounts, _) <- NewStyle .function.getBankAccounts(availablePrivateAccounts, Some (cc))
3452+ allTxns <- Future {
3453+ accounts.flatMap { bankAccount =>
3454+ (for {
3455+ view <- UserExtended (u).checkOwnerViewAccessAndReturnOwnerView(BankIdAccountId (bankAccount.bankId, bankAccount.accountId), Some (cc))
3456+ params = createQueriesByHttpParams(req.headers.headers.toList.map(h => HTTPParam (h.name.toString, List (h.value)))).getOrElse(Nil )
3457+ (transactions, _) <- BankAccountExtended (bankAccount).getModeratedTransactions(bank, Full (u), view, BankIdAccountId (bankAccount.bankId, bankAccount.accountId), Some (cc), params)
3458+ } yield transactions).getOrElse(Nil )
3459+ }
3460+ }
3461+ } yield JSONFactory_UKOpenBanking_401 .createTransactionsJson(bank.bankId, allTxns)
3462+ }
32873463 }
32883464 resourceDocs += ResourceDoc (
32893465 implementedInApiVersion,
0 commit comments