From 0ad212ce781e5d63562597a226fd0364864a1977 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 26 May 2025 12:48:53 +0200 Subject: [PATCH 01/10] feature/add consents page and implement getConsents method for consent management --- .../main/scala/bootstrap/liftweb/Boot.scala | 1 + .../scala/code/snippet/ConsentScreen.scala | 58 ++++++++++++++++-- obp-api/src/main/webapp/consents.html | 59 +++++++++++++++++++ 3 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 obp-api/src/main/webapp/consents.html diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 6782f3deb..f29b72999 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -564,6 +564,7 @@ class Boot extends MdcLoggable { Menu.i("Plain") / "plain", Menu.i("Static") / "static", Menu.i("SDKs") / "sdks", + Menu.i("Consents") / "consents", Menu.i("Debug") / "debug", Menu.i("debug-basic") / "debug" / "debug-basic", Menu.i("debug-localization") / "debug" / "debug-localization", diff --git a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala index 8874cd2f3..7425977a7 100644 --- a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala +++ b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala @@ -26,25 +26,33 @@ TESOBE (http://www.tesobe.com/) */ package code.snippet -import code.api.util.APIUtil +import code.api.util.APIUtil.callEndpoint +import code.api.util.{CustomJsonFormats, ErrorMessages} +import code.api.v5_1_0.ConsentsInfoJsonV510 +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 import code.consumer.Consumers import code.model.dataAccess.AuthUser import code.util.Helper.{MdcLoggable, ObpS} import code.util.HydraUtil.integrateWithHydra -import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue -import net.liftweb.http.{RequestVar, S, SHtml} +import net.liftweb.common.{Failure, Full} +import net.liftweb.http.{GetRequest, RequestVar, S, SHtml} +import net.liftweb.json +import net.liftweb.json.{Extraction, Formats, JNothing} +import net.liftweb.util.CssSel import net.liftweb.util.Helpers._ import sh.ory.hydra.api.AdminApi import sh.ory.hydra.model.{AcceptConsentRequest, RejectRequest} import scala.jdk.CollectionConverters.seqAsJavaListConverter +import scala.xml.NodeSeq class ConsentScreen extends MdcLoggable { private object skipConsentScreenVar extends RequestVar(false) private object consentChallengeVar extends RequestVar(ObpS.param("consent_challenge").getOrElse("")) private object csrfVar extends RequestVar(ObpS.param("_csrf").getOrElse("")) - + implicit val formats: Formats = CustomJsonFormats.formats + def submitAllowAction: Unit = { integrateWithHydra match { case true if !consentChallengeVar.isEmpty => @@ -87,5 +95,45 @@ class ConsentScreen extends MdcLoggable { "#deny_access_to_consent" #> SHtml.submit(s"Deny access", () => submitDenyAction) } } - + + /** + * Renders the consents page. + */ + def getConsents: CssSel = { + + val pathOfEndpoint = List( + "my", + "consents" + ) + + val getMyConsentsResult = callEndpoint(Implementations5_1_0.getMyConsents, pathOfEndpoint, GetRequest) + + getMyConsentsResult match { + case Left(error) => { + S.error(error._1) + ".consent-entry" #> NodeSeq.Empty + } + case Right(response) => { + tryo {json.parse(response).extract[ConsentsInfoJsonV510]} match { + case Full(consentsInfoJsonV510) => + val consents = consentsInfoJsonV510.consents + ".consent-entry" #> consents.map { consent => + ".consent-id *" #> consent.consent_id & + ".consumer-id *" #> consent.consumer_id & + ".jwt-payload *" #> json.prettyRender(consent.jwt_payload.map(Extraction.decompose).openOr(JNothing)) & + ".status *" #> consent.status & + ".api-standard *" #> consent.api_standard & + ".revoke-form [action]" #> s"/my/consents/consent_id/${consent.consent_id}" & + ".consent-id-input [value]" #> consent.consent_id + } + case Failure(msg, t, c) => + S.error(s"${ErrorMessages.UnknownError} $msg") + ".consent-entry" #> NodeSeq.Empty + case _ => + S.error(s"${ErrorMessages.UnknownError} Failed to parse response") + ".consent-entry" #> NodeSeq.Empty + } + } + } + } } diff --git a/obp-api/src/main/webapp/consents.html b/obp-api/src/main/webapp/consents.html new file mode 100644 index 000000000..732354a37 --- /dev/null +++ b/obp-api/src/main/webapp/consents.html @@ -0,0 +1,59 @@ + +
+
+

Consents

+ + + + + + + + + + + + + + + + + + + + + +
Consent IdConsumer IdJwt PayloadStatusApi StandardRevoke
+
+ + +
+
+
+
+ + From 9bdd2b3275be26f6ae1d388e90faad4d5eb79107 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 26 May 2025 14:46:38 +0200 Subject: [PATCH 02/10] feature/add self-revoke consent functionality and update consent management UI --- .../main/scala/code/api/util/APIUtil.scala | 8 ++++++- .../scala/code/snippet/ConsentScreen.scala | 23 +++++++++++++++---- obp-api/src/main/webapp/consents.html | 2 +- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 5ba84c140..4dea90c35 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -4732,7 +4732,13 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val user = AuthUser.getCurrentUser val result = tryo { - endpoint(newRequest)(CallContext(user = user)) + val headers: List[HTTPParam] = addlParams.get(RequestHeader.`Consent-Id`) + .map(consentId => List(HTTPParam(RequestHeader.`Consent-Id`, List(consentId)))) + .getOrElse(Nil) + + endpoint(newRequest)(CallContext( + user = user, + requestHeaders = headers)) } val func: ((=> LiftResponse) => Unit) => Unit = result match { diff --git a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala index 7425977a7..b192f7cb6 100644 --- a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala +++ b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala @@ -26,6 +26,7 @@ TESOBE (http://www.tesobe.com/) */ package code.snippet +import code.api.RequestHeader import code.api.util.APIUtil.callEndpoint import code.api.util.{CustomJsonFormats, ErrorMessages} import code.api.v5_1_0.ConsentsInfoJsonV510 @@ -35,7 +36,7 @@ import code.model.dataAccess.AuthUser import code.util.Helper.{MdcLoggable, ObpS} import code.util.HydraUtil.integrateWithHydra import net.liftweb.common.{Failure, Full} -import net.liftweb.http.{GetRequest, RequestVar, S, SHtml} +import net.liftweb.http.{DeleteRequest, GetRequest, RequestVar, S, SHtml} import net.liftweb.json import net.liftweb.json.{Extraction, Formats, JNothing} import net.liftweb.util.CssSel @@ -107,7 +108,12 @@ class ConsentScreen extends MdcLoggable { ) val getMyConsentsResult = callEndpoint(Implementations5_1_0.getMyConsents, pathOfEndpoint, GetRequest) - + + def selfRevokeConsent(consentId: String) = { + val addlParams = Map(RequestHeader.`Consent-Id`-> consentId) + callEndpoint(Implementations5_1_0.selfRevokeConsent, List("my", "consent", "current"), DeleteRequest, addlParams=addlParams) + } + getMyConsentsResult match { case Left(error) => { S.error(error._1) @@ -123,8 +129,17 @@ class ConsentScreen extends MdcLoggable { ".jwt-payload *" #> json.prettyRender(consent.jwt_payload.map(Extraction.decompose).openOr(JNothing)) & ".status *" #> consent.status & ".api-standard *" #> consent.api_standard & - ".revoke-form [action]" #> s"/my/consents/consent_id/${consent.consent_id}" & - ".consent-id-input [value]" #> consent.consent_id + ".revoke-form [action]" #> s"/my/consent/current" & + ".consent-id-input [value]" #> consent.consent_id & + ".revoke-button-placeholder *" #> SHtml.ajaxButton("Revoke", () => { + selfRevokeConsent(consent.consent_id) match { + case Left(errorMsg) => + S.error(errorMsg._1) + case Right(_) => + S.notice("Consent successfully revoked.") + } + S.redirectTo("/consents") + }) } case Failure(msg, t, c) => S.error(s"${ErrorMessages.UnknownError} $msg") diff --git a/obp-api/src/main/webapp/consents.html b/obp-api/src/main/webapp/consents.html index 732354a37..d0f593e56 100644 --- a/obp-api/src/main/webapp/consents.html +++ b/obp-api/src/main/webapp/consents.html @@ -47,7 +47,7 @@ Berlin 13359, Germany
- + Revoke
From e1e349788ce7b73e195d2a9c61101ccb15ee29ac Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 26 May 2025 15:44:28 +0200 Subject: [PATCH 03/10] feature/enhance consent management UI with AJAX support and improve consent revocation process --- .../scala/code/snippet/ConsentScreen.scala | 116 ++++++++++-------- obp-api/src/main/webapp/consents.html | 20 +-- 2 files changed, 71 insertions(+), 65 deletions(-) diff --git a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala index b192f7cb6..b432b5ad8 100644 --- a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala +++ b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala @@ -28,14 +28,16 @@ package code.snippet import code.api.RequestHeader import code.api.util.APIUtil.callEndpoint -import code.api.util.{CustomJsonFormats, ErrorMessages} -import code.api.v5_1_0.ConsentsInfoJsonV510 +import code.api.util.CustomJsonFormats import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 +import code.api.v5_1_0.{ConsentInfoJsonV510, ConsentsInfoJsonV510} import code.consumer.Consumers import code.model.dataAccess.AuthUser import code.util.Helper.{MdcLoggable, ObpS} import code.util.HydraUtil.integrateWithHydra -import net.liftweb.common.{Failure, Full} +import net.liftweb.common.Full +import net.liftweb.http.js.JsCmd +import net.liftweb.http.js.JsCmds._ import net.liftweb.http.{DeleteRequest, GetRequest, RequestVar, S, SHtml} import net.liftweb.json import net.liftweb.json.{Extraction, Formats, JNothing} @@ -47,6 +49,8 @@ import sh.ory.hydra.model.{AcceptConsentRequest, RejectRequest} import scala.jdk.CollectionConverters.seqAsJavaListConverter import scala.xml.NodeSeq + + class ConsentScreen extends MdcLoggable { private object skipConsentScreenVar extends RequestVar(false) @@ -97,58 +101,68 @@ class ConsentScreen extends MdcLoggable { } } - /** - * Renders the consents page. - */ def getConsents: CssSel = { - - val pathOfEndpoint = List( - "my", - "consents" - ) - - val getMyConsentsResult = callEndpoint(Implementations5_1_0.getMyConsents, pathOfEndpoint, GetRequest) - - def selfRevokeConsent(consentId: String) = { - val addlParams = Map(RequestHeader.`Consent-Id`-> consentId) - callEndpoint(Implementations5_1_0.selfRevokeConsent, List("my", "consent", "current"), DeleteRequest, addlParams=addlParams) - } - - getMyConsentsResult match { - case Left(error) => { - S.error(error._1) - ".consent-entry" #> NodeSeq.Empty - } - case Right(response) => { - tryo {json.parse(response).extract[ConsentsInfoJsonV510]} match { + callEndpoint(Implementations5_1_0.getMyConsents, List("my", "consents"), GetRequest) match { + case Right(response) => + tryo(json.parse(response).extract[ConsentsInfoJsonV510]) match { case Full(consentsInfoJsonV510) => - val consents = consentsInfoJsonV510.consents - ".consent-entry" #> consents.map { consent => - ".consent-id *" #> consent.consent_id & - ".consumer-id *" #> consent.consumer_id & - ".jwt-payload *" #> json.prettyRender(consent.jwt_payload.map(Extraction.decompose).openOr(JNothing)) & - ".status *" #> consent.status & - ".api-standard *" #> consent.api_standard & - ".revoke-form [action]" #> s"/my/consent/current" & - ".consent-id-input [value]" #> consent.consent_id & - ".revoke-button-placeholder *" #> SHtml.ajaxButton("Revoke", () => { - selfRevokeConsent(consent.consent_id) match { - case Left(errorMsg) => - S.error(errorMsg._1) - case Right(_) => - S.notice("Consent successfully revoked.") - } - S.redirectTo("/consents") - }) - } - case Failure(msg, t, c) => - S.error(s"${ErrorMessages.UnknownError} $msg") - ".consent-entry" #> NodeSeq.Empty + "#consent-table-body *" #> renderConsentRows(consentsInfoJsonV510.consents) case _ => - S.error(s"${ErrorMessages.UnknownError} Failed to parse response") - ".consent-entry" #> NodeSeq.Empty +// ShowMessage("Consent successfully revoked.", isError = false) + "#consent-table-body *" #> Parse error } - } + case Left((msg, _)) => +// ShowMessage("Consent successfully revoked.", isError = false) + "#consent-table-body *" #> {msg} + } + } + + private def selfRevokeConsent(consentId: String): Either[(String, Int), String] = { + val addlParams = Map(RequestHeader.`Consent-Id` -> consentId) + callEndpoint(Implementations5_1_0.selfRevokeConsent, List("my", "consent", "current"), DeleteRequest, addlParams = addlParams) + } + + private def refreshTable(): JsCmd = { + callEndpoint(Implementations5_1_0.getMyConsents, List("my", "consents"), GetRequest) match { + case Right(response) => + tryo(json.parse(response).extract[ConsentsInfoJsonV510]) match { + case Full(consentsInfoJsonV510) => + SetHtml("consent-table-body", renderConsentRows(consentsInfoJsonV510.consents)) + case _ => + SetHtml("consent-table-body", Error parsing consent data) + } + case Left((msg, _)) => + SetHtml("consent-table-body", {msg}) + } + } + + private def ShowMessage(msg: String, isError: Boolean): JsCmd = { + val alertClass = if (isError) "alert-danger" else "alert-success" + val html = + SetHtml("flash-message", html) + } + + private def renderConsentRows(consents: List[ConsentInfoJsonV510]): NodeSeq = { + consents.map { consent => + + {consent.consent_id} + {consent.consumer_id} + {json.prettyRender(consent.jwt_payload.map(Extraction.decompose).openOr(JNothing))} + {consent.status} + {consent.api_standard} + + { + SHtml.ajaxButton("Revoke", () => { + val result = selfRevokeConsent(consent.consent_id) + val message = result match { + case Left((msg, _)) => ShowMessage(msg, isError = true) + case Right(_) => ShowMessage("Consent successfully revoked.", isError = false) + } + message & refreshTable() + }) + } + + } } } diff --git a/obp-api/src/main/webapp/consents.html b/obp-api/src/main/webapp/consents.html index d0f593e56..bb24cbda0 100644 --- a/obp-api/src/main/webapp/consents.html +++ b/obp-api/src/main/webapp/consents.html @@ -26,6 +26,9 @@ Berlin 13359, Germany

Consents

+ +
+ @@ -37,23 +40,12 @@ Berlin 13359, Germany - - - - - - - - - + +
Revoke
-
- - Revoke -
-
+ From 8bff556fc253aaad5e559385a760425c44781aec Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 26 May 2025 16:09:52 +0200 Subject: [PATCH 04/10] feature/improve consent revocation feedback and add consents navigation link --- .../scala/code/snippet/ConsentScreen.scala | 18 ++++++++++++------ .../main/webapp/templates-hidden/default.html | 5 +++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala index b432b5ad8..a10a7d666 100644 --- a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala +++ b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala @@ -106,17 +106,23 @@ class ConsentScreen extends MdcLoggable { case Right(response) => tryo(json.parse(response).extract[ConsentsInfoJsonV510]) match { case Full(consentsInfoJsonV510) => - "#consent-table-body *" #> renderConsentRows(consentsInfoJsonV510.consents) + "#consent-table-body *" #> renderConsentRows(consentsInfoJsonV510.consents) & + "#flash-message *" #> NodeSeq.Empty case _ => -// ShowMessage("Consent successfully revoked.", isError = false) - "#consent-table-body *" #> Parse error + "#consent-table-body *" #> NodeSeq.Empty & + "#flash-message *" #> renderAlert("Failed to parse consent data.", isError = true) } case Left((msg, _)) => -// ShowMessage("Consent successfully revoked.", isError = false) - "#consent-table-body *" #> {msg} + "#consent-table-body *" #> NodeSeq.Empty & + "#flash-message *" #> renderAlert(msg, isError = true) } } + private def renderAlert(msg: String, isError: Boolean): NodeSeq = { + val alertClass = if (isError) "alert-danger" else "alert-success" + + } + private def selfRevokeConsent(consentId: String): Either[(String, Int), String] = { val addlParams = Map(RequestHeader.`Consent-Id` -> consentId) callEndpoint(Implementations5_1_0.selfRevokeConsent, List("my", "consent", "current"), DeleteRequest, addlParams = addlParams) @@ -156,7 +162,7 @@ class ConsentScreen extends MdcLoggable { val result = selfRevokeConsent(consent.consent_id) val message = result match { case Left((msg, _)) => ShowMessage(msg, isError = true) - case Right(_) => ShowMessage("Consent successfully revoked.", isError = false) + case Right(_) => ShowMessage(s"Consent (${consent.consent_id}) successfully revoked.", isError = false) } message & refreshTable() }) diff --git a/obp-api/src/main/webapp/templates-hidden/default.html b/obp-api/src/main/webapp/templates-hidden/default.html index 16d92c555..e7193429d 100644 --- a/obp-api/src/main/webapp/templates-hidden/default.html +++ b/obp-api/src/main/webapp/templates-hidden/default.html @@ -119,6 +119,11 @@ Berlin 13359, Germany API Explorer + From a8063e649c86d92b14db6a516d164f07e0f8016c Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 26 May 2025 16:19:41 +0200 Subject: [PATCH 05/10] feature/update flash message comment for clarity in consents.html --- obp-api/src/main/webapp/consents.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/webapp/consents.html b/obp-api/src/main/webapp/consents.html index bb24cbda0..3fbcb1b08 100644 --- a/obp-api/src/main/webapp/consents.html +++ b/obp-api/src/main/webapp/consents.html @@ -27,7 +27,7 @@ Berlin 13359, Germany

Consents

-
+
From c3ae3e9d6b0bdae0606d0bb6f9b3a82a5e362022 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 27 May 2025 08:42:27 +0200 Subject: [PATCH 06/10] feature/update consent reference id usage in JSON and UI --- .../ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala | 5 ++--- .../src/main/scala/code/api/util/ExampleValue.scala | 11 +++++++---- .../main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala | 4 +++- .../src/main/scala/code/snippet/ConsentScreen.scala | 4 ++-- obp-api/src/main/webapp/consents.html | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 9f417d64f..49a9728c2 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -12,7 +12,6 @@ import code.api.util.{ApiRole, ApiTrigger, ExampleValue} import code.api.v2_2_0.JSONFactory220.{AdapterImplementationJson, MessageDocJson, MessageDocsJson} import code.api.v3_0_0.JSONFactory300.createBranchJsonV300 import code.api.v3_0_0._ -import code.api.v3_0_0.custom.JSONFactoryCustom300 import code.api.v3_1_0._ import code.api.v4_0_0._ import code.api.v5_0_0._ @@ -22,7 +21,6 @@ import code.connectormethod.{JsonConnectorMethod, JsonConnectorMethodMethodBody} import code.consent.ConsentStatus import code.dynamicMessageDoc.JsonDynamicMessageDoc import code.dynamicResourceDoc.JsonDynamicResourceDoc -import code.sandbox.SandboxData import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.model import com.openbankproject.commons.model.PinResetReason.{FORGOT, GOOD_SECURITY_PRACTICE} @@ -4248,7 +4246,7 @@ object SwaggerDefinitionsJSON { ) lazy val allConsentJsonV510 = AllConsentJsonV510( - consent_reference_id = "9d429899-24f5-42c8-8565-943ffa6a7945", + consent_reference_id = consentReferenceIdExample.value, consumer_id = consumerIdExample.value, created_by_user_id = userIdExample.value, last_action_date = dateExample.value, @@ -4259,6 +4257,7 @@ object SwaggerDefinitionsJSON { jwt_payload = Some(consentJWT) ) lazy val consentInfoJsonV510 = ConsentInfoJsonV510( + consent_reference_id = consentReferenceIdExample.value, consent_id = consentIdExample.value, consumer_id = consumerIdExample.value, created_by_user_id = userIdExample.value, diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 18c385d30..f304f87ba 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -2,18 +2,18 @@ package code.api.util import code.api.Constant -import code.api.util.APIUtil.{DateWithMs, DateWithMsExampleString, formatDate, oneYearAgo, oneYearAgoDate, parseDate} +import code.api.util.APIUtil.{DateWithMs, DateWithMsExampleString, formatDate, oneYearAgoDate, parseDate} import code.api.util.ErrorMessages.{InvalidJsonFormat, UnknownError, UserHasMissingRoles, UserNotLoggedIn} -import net.liftweb.json.JsonDSL._ import code.api.util.Glossary.{glossaryItems, makeGlossaryItem} import code.apicollection.ApiCollection -import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityIntTypeExample, DynamicEntityStringTypeExample} +import code.dynamicEntity._ import com.openbankproject.commons.model.CardAction import com.openbankproject.commons.model.enums.{CustomerAttributeType, DynamicEntityFieldType, UserInvitationPurpose} import com.openbankproject.commons.util.ReflectUtils import net.liftweb.json import net.liftweb.json.JObject import net.liftweb.json.JsonAST.JField +import net.liftweb.json.JsonDSL._ case class ConnectorField(value: String, description: String) { @@ -1586,7 +1586,10 @@ object ExampleValue { lazy val directDebitIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("direct_debit_id", directDebitIdExample) - lazy val consentIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) + lazy val consentReferenceIdExample = ConnectorField("9d429899-24f5-42c8-8565-943ffa6a7946" ,NoDescriptionProvided) + glossaryItems += makeGlossaryItem("consent_id", consentReferenceIdExample) + + lazy val consentIdExample = ConnectorField("9d429899-24f5-42c8-8565-943ffa6a7947",NoDescriptionProvided) glossaryItems += makeGlossaryItem("consent_id", consentIdExample) lazy val basketIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 80bc6c368..adfa21e00 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -146,7 +146,8 @@ case class ConsentJsonV510(consent_id: String, consumer_id:String) -case class ConsentInfoJsonV510(consent_id: String, +case class ConsentInfoJsonV510(consent_reference_id: String, + consent_id: String, consumer_id: String, created_by_user_id: String, status: String, @@ -915,6 +916,7 @@ object JSONFactory510 extends CustomJsonFormats { consents.map { c => val jwtPayload: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(c.jsonWebToken).map(parse(_).extract[ConsentJWT]) ConsentInfoJsonV510( + consent_reference_id = c.consentReferenceId, consent_id = c.consentId, consumer_id = c.consumerId, created_by_user_id = c.userId, diff --git a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala index a10a7d666..d4c0d4edb 100644 --- a/obp-api/src/main/scala/code/snippet/ConsentScreen.scala +++ b/obp-api/src/main/scala/code/snippet/ConsentScreen.scala @@ -151,7 +151,7 @@ class ConsentScreen extends MdcLoggable { private def renderConsentRows(consents: List[ConsentInfoJsonV510]): NodeSeq = { consents.map { consent => - + @@ -162,7 +162,7 @@ class ConsentScreen extends MdcLoggable { val result = selfRevokeConsent(consent.consent_id) val message = result match { case Left((msg, _)) => ShowMessage(msg, isError = true) - case Right(_) => ShowMessage(s"Consent (${consent.consent_id}) successfully revoked.", isError = false) + case Right(_) => ShowMessage(s"Consent (reference_id ${consent.consent_reference_id}) successfully revoked.", isError = false) } message & refreshTable() }) diff --git a/obp-api/src/main/webapp/consents.html b/obp-api/src/main/webapp/consents.html index 3fbcb1b08..59cb52562 100644 --- a/obp-api/src/main/webapp/consents.html +++ b/obp-api/src/main/webapp/consents.html @@ -32,7 +32,7 @@ Berlin 13359, Germany
{consent.consumer_id} {json.prettyRender(consent.jwt_payload.map(Extraction.decompose).openOr(JNothing))} {consent.status}
- + From 574e3a6c3a53b8854233dae5ee3617eb53de7051 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 27 May 2025 10:06:55 +0200 Subject: [PATCH 07/10] feature/add bulk bank account balance retrieval functionality --- .../AccountInformationServiceAISApi.scala | 17 ++++++-------- .../newstyle/BankAccountBalanceNewStyle.scala | 20 ++++++++++++---- .../BankAccountBalanceProvider.scala | 12 ++++++++-- .../scala/code/bankconnectors/Connector.scala | 11 +++++---- .../bankconnectors/LocalMappedConnector.scala | 23 +++++++++++-------- 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala index 9afe94867..ca92704d5 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala @@ -1,18 +1,16 @@ package code.api.builder.AccountInformationServiceAISApi -import java.text.SimpleDateFormat import code.api.APIFailureNewStyle import code.api.Constant.{SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID} import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostConsentResponseJson, _} -import code.api.berlin.group.v1_3.model.{HrefType, LinksAll, ScaStatusResponse} -import code.api.berlin.group.v1_3.{BgSpecValidation, JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass, OBP_BERLIN_GROUP_1_3} import code.api.berlin.group.v1_3.model._ +import code.api.berlin.group.v1_3.{BgSpecValidation, JSONFactory_BERLIN_GROUP_1_3, JvalueCaseClass} import code.api.util.APIUtil.{passesPsd2Aisp, _} import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ import code.api.util.NewStyle.HttpCode import code.api.util.newstyle.BalanceNewStyle -import code.api.util.{APIUtil, ApiTag, CallContext, Consent, ExampleValue, NewStyle} +import code.api.util._ import code.bankconnectors.Connector import code.consent.{ConsentStatus, Consents} import code.context.{ConsentAuthContextProvider, UserAuthContextProvider} @@ -23,16 +21,15 @@ import code.views.Views import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.model._ -import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthentication, StrongCustomerAuthenticationStatus, SuppliedAnswerType} +import com.openbankproject.commons.model.enums.{ChallengeType, StrongCustomerAuthenticationStatus, SuppliedAnswerType} import com.openbankproject.commons.util.ApiVersion +import net.liftweb import net.liftweb.common.{Empty, Full} import net.liftweb.http.js.JE.JsRaw import net.liftweb.http.rest.RestHelper -import net.liftweb import net.liftweb.json import net.liftweb.json._ -import scala.collection.immutable.Nil import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future @@ -342,10 +339,10 @@ of the PSU at this ASPSP. attribute.value.equalsIgnoreCase("card") ).isEmpty) - (balances, callContext) <- JSONFactory_BERLIN_GROUP_1_3.flattenOBPReturnType(bankAccountsFiltered.map(bankAccont => code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( - bankAccont.accountId, + (balances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountsBalances( + bankAccountsFiltered.map(_.accountId), callContext - ))) + ) } yield { (JSONFactory_BERLIN_GROUP_1_3.createAccountListJson( diff --git a/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala b/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala index c45360194..ea23386ef 100644 --- a/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala @@ -1,13 +1,11 @@ package code.api.util.newstyle import code.api.util.APIUtil.{OBPReturnType, unboxFullOrFail} -import code.bankconnectors.Connector -import code.api.util.{APIUtil, CallContext} -import code.api.util.CallContext import code.api.util.ErrorMessages.BankAccountBalanceNotFoundById +import code.api.util.{APIUtil, CallContext} +import code.bankconnectors.Connector import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model.{AccountId, BankAccountBalanceTrait, BankId} -import com.openbankproject.commons.model.BalanceId +import com.openbankproject.commons.model.{AccountId, BalanceId, BankAccountBalanceTrait, BankId} object BankAccountBalanceNewStyle { @@ -23,6 +21,18 @@ object BankAccountBalanceNewStyle { i => (APIUtil.connectorEmptyResponse(i._1, callContext), i._2) } } + + def getBankAccountsBalances( + accountIds: List[AccountId], + callContext: Option[CallContext] + ): OBPReturnType[List[BankAccountBalanceTrait]] = { + Connector.connector.vend.getBankAccountsBalancesByAccountIds( + accountIds: List[AccountId], + callContext: Option[CallContext] + ) map { + i => (APIUtil.connectorEmptyResponse(i._1, callContext), i._2) + } + } def getBankAccountBalanceById( balanceId: BalanceId, diff --git a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala index 2568257b4..ae2e6372c 100644 --- a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala +++ b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala @@ -3,12 +3,11 @@ package code.bankaccountbalance import code.model.dataAccess.MappedBankAccount import code.util.Helper import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model.{BankId, AccountId} +import com.openbankproject.commons.model.{AccountId, BalanceId, BankId} import net.liftweb.common.{Box, Empty, Full} import net.liftweb.mapper._ import net.liftweb.util.Helpers.tryo import net.liftweb.util.SimpleInjector -import com.openbankproject.commons.model.BalanceId import scala.concurrent.Future @@ -31,6 +30,8 @@ object BankAccountBalanceX extends SimpleInjector { trait BankAccountBalanceProviderTrait { def getBankAccountBalances(accountId: AccountId): Future[Box[List[BankAccountBalance]]] + + def getBankAccountsBalances(accountIds: List[AccountId]): Future[Box[List[BankAccountBalance]]] def getBankAccountBalanceById(balanceId: BalanceId): Future[Box[BankAccountBalance]] @@ -53,6 +54,13 @@ object MappedBankAccountBalanceProvider extends BankAccountBalanceProviderTrait By(BankAccountBalance.AccountId_,accountId.value) )} } + override def getBankAccountsBalances(accountIds: List[AccountId]): Future[Box[List[BankAccountBalance]]] = Future { + tryo { + BankAccountBalance.findAll( + ByList(BankAccountBalance.AccountId_, accountIds.map(_.value)) + ) + } + } override def getBankAccountBalanceById(balanceId: BalanceId): Future[Box[BankAccountBalance]] = Future { // Find a balance by its ID diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index d73e249de..956e70a18 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -12,20 +12,16 @@ import code.bankconnectors.akka.AkkaConnector_vDec2018 import code.bankconnectors.rabbitmq.RabbitMQConnector_vOct2024 import code.bankconnectors.rest.RestConnector_vMar2019 import code.bankconnectors.storedprocedure.StoredProcedureConnector_vDec2019 -import com.openbankproject.commons.model.CounterpartyLimitTrait -import com.openbankproject.commons.model.CustomerAccountLinkTrait -import com.openbankproject.commons.model.EndpointTagT import code.model.dataAccess.BankAccountRouting -import com.openbankproject.commons.model.StandingOrderTrait import code.users.UserAttribute import code.util.Helper._ import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.dto.{CustomerAndAttribute, GetProductsParam, InBoundTrait, ProductCollectionItemsTree} +import com.openbankproject.commons.model.{TransactionRequestStatus, _} import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatus.SCAStatus import com.openbankproject.commons.model.enums._ -import com.openbankproject.commons.model.{TransactionRequestStatus, _} import com.openbankproject.commons.util.{JsonUtils, ReflectUtils} import net.liftweb.common._ import net.liftweb.json @@ -1898,6 +1894,11 @@ trait Connector extends MdcLoggable { accountId: AccountId, callContext: Option[CallContext] ): OBPReturnType[Box[List[BankAccountBalanceTrait]]] = Future{(Failure(setUnimplementedError(nameOf(getBankAccountBalancesByAccountId(_, _)))), callContext)} + + def getBankAccountsBalancesByAccountIds( + accountIds: List[AccountId], + callContext: Option[CallContext] + ): OBPReturnType[Box[List[BankAccountBalanceTrait]]] = Future{(Failure(setUnimplementedError(nameOf(getBankAccountsBalancesByAccountIds(_, _)))), callContext)} def getBankAccountBalanceById( balanceId: BalanceId, diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index c10405e2a..fa617fc63 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -4,7 +4,7 @@ import _root_.akka.http.scaladsl.model.HttpMethod import code.DynamicData.DynamicDataProvider import code.accountapplication.AccountApplicationX import code.accountattribute.AccountAttributeX -import code.accountholders.{AccountHolders, MapperAccountHolders} +import code.accountholders.AccountHolders import code.api.Constant import code.api.Constant._ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON @@ -18,6 +18,7 @@ import code.api.v2_1_0._ import code.api.v4_0_0.{AgentCashWithdrawalJson, PostSimpleCounterpartyJson400, TransactionRequestBodyAgentJsonV400, TransactionRequestBodySimpleJsonV400} import code.atmattribute.{AtmAttribute, AtmAttributeX} import code.atms.{Atms, MappedAtm} +import code.bankaccountbalance.BankAccountBalanceX import code.bankattribute.{BankAttribute, BankAttributeX} import code.branches.MappedBranch import code.cardattribute.CardAttributeX @@ -27,13 +28,10 @@ import code.counterpartylimit.CounterpartyLimitProvider import code.customer._ import code.customer.agent.AgentX import code.customeraccountlinks.CustomerAccountLinkX -import com.openbankproject.commons.model.CustomerAccountLinkTrait import code.customeraddress.CustomerAddressX import code.customerattribute.CustomerAttributeX import code.directdebit.DirectDebits import code.endpointTag.EndpointTag -import code.bankaccountbalance.BankAccountBalanceX -import com.openbankproject.commons.model.EndpointTagT import code.fx.{MappedFXRate, fx} import code.kycchecks.KycChecks import code.kycdocuments.KycDocuments @@ -52,7 +50,6 @@ import code.productfee.ProductFeeX import code.products.MappedProduct import code.regulatedentities.MappedRegulatedEntityProvider import code.standingorders.StandingOrders -import com.openbankproject.commons.model.StandingOrderTrait import code.taxresidence.TaxResidenceX import code.transaction.MappedTransaction import code.transactionChallenge.Challenges @@ -65,14 +62,13 @@ import code.util.Helper._ import code.views.Views import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.dto.{CustomerAndAttribute, GetProductsParam, ProductCollectionItemsTree} +import com.openbankproject.commons.model._ import com.openbankproject.commons.model.enums.ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE import com.openbankproject.commons.model.enums.DynamicEntityOperation._ import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatus.SCAStatus import com.openbankproject.commons.model.enums.TransactionRequestTypes._ import com.openbankproject.commons.model.enums.{TransactionRequestStatus, _} -import com.openbankproject.commons.model.CustomerAccountLinkTrait -import com.openbankproject.commons.model._ import com.tesobe.CacheKeyFromArguments import com.tesobe.model.UpdateBankAccount import com.twilio.Twilio @@ -95,7 +91,7 @@ import scala.collection.immutable.{List, Nil} import scala.concurrent._ import scala.concurrent.duration._ import scala.language.postfixOps -import scala.math.{BigDecimal, BigInt} +import scala.math.BigDecimal import scala.util.{Random, Try} object LocalMappedConnector extends Connector with MdcLoggable { @@ -129,7 +125,7 @@ object LocalMappedConnector extends Connector with MdcLoggable { } override def validateAndCheckIbanNumber(iban: String, callContext: Option[CallContext]): OBPReturnType[Box[IbanChecker]] = Future { - import org.iban4j.{IbanFormat, IbanFormatException, IbanUtil, InvalidCheckDigitException, UnsupportedCountryException} + import org.iban4j._ if(getPropsAsBoolValue("validate_iban", false)) { // Validate Iban @@ -5374,6 +5370,15 @@ object LocalMappedConnector extends Connector with MdcLoggable { } } + override def getBankAccountsBalancesByAccountIds( + accountIds: List[AccountId], + callContext: Option[CallContext] + ): OBPReturnType[Box[List[BankAccountBalanceTrait]]] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.getBankAccountsBalances(accountIds).map { + (_, callContext) + } + } + override def getBankAccountBalanceById( balanceId: BalanceId, callContext: Option[CallContext] From 9bcd1055d71e722ee03a01889ceb3f40ac773087 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 27 May 2025 10:27:09 +0200 Subject: [PATCH 08/10] feature/add support for retrieving bank account balances by multiple account IDs --- .../generator/ConnectorBuilderUtil.scala | 1 + .../Adapter/MockedRabbitMqAdapter.scala | 118 ++++++++++++++++-- .../rabbitmq/RabbitMQConnector_vOct2024.scala | 36 +++++- .../commons/dto/JsonsTransfer.scala | 6 +- 4 files changed, 150 insertions(+), 11 deletions(-) diff --git a/obp-api/src/main/scala/code/bankconnectors/generator/ConnectorBuilderUtil.scala b/obp-api/src/main/scala/code/bankconnectors/generator/ConnectorBuilderUtil.scala index f518a7bb4..95d94df17 100644 --- a/obp-api/src/main/scala/code/bankconnectors/generator/ConnectorBuilderUtil.scala +++ b/obp-api/src/main/scala/code/bankconnectors/generator/ConnectorBuilderUtil.scala @@ -438,6 +438,7 @@ object ConnectorBuilderUtil { "getRegulatedEntities", "getRegulatedEntityByEntityId", "getBankAccountBalancesByAccountId", + "getBankAccountsBalancesByAccountIds", "getBankAccountBalanceById", "createOrUpdateBankAccountBalance", "deleteBankAccountBalance", diff --git a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/Adapter/MockedRabbitMqAdapter.scala b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/Adapter/MockedRabbitMqAdapter.scala index 132538260..3d0b863d5 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/Adapter/MockedRabbitMqAdapter.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/Adapter/MockedRabbitMqAdapter.scala @@ -3,6 +3,8 @@ package code.bankconnectors.rabbitmq.Adapter import bootstrap.liftweb.ToSchemify import code.api.util.APIUtil import code.bankconnectors.rabbitmq.RabbitMQUtils +import code.bankconnectors.rabbitmq.RabbitMQUtils._ +import code.util.Helper.MdcLoggable import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.dto._ import com.openbankproject.commons.model._ @@ -13,11 +15,8 @@ import net.liftweb.json import net.liftweb.json.Serialization.write import net.liftweb.mapper.Schemifier -import scala.concurrent.Future -import com.openbankproject.commons.ExecutionContext.Implicits.global -import code.bankconnectors.rabbitmq.RabbitMQUtils._ import java.util.Date -import code.util.Helper.MdcLoggable +import scala.concurrent.Future class ServerCallback(val ch: Channel) extends DeliverCallback with MdcLoggable{ @@ -77,7 +76,7 @@ class ServerCallback(val ch: Channel) extends DeliverCallback with MdcLoggable{ )) } //---------------- dynamic start -------------------please don't modify this line -// ---------- created on 2025-04-07T14:53:47Z +// ---------- created on 2025-05-27T08:15:58Z } else if (obpMessageId.contains("get_adapter_info")) { val outBound = json.parse(message).extract[OutBoundGetAdapterInfo] @@ -3151,6 +3150,111 @@ class ServerCallback(val ch: Channel) extends DeliverCallback with MdcLoggable{ data = null )) } + } else if (obpMessageId.contains("get_bank_account_balances_by_account_id")) { + val outBound = json.parse(message).extract[OutBoundGetBankAccountBalancesByAccountId] + val obpMappedResponse = code.bankconnectors.LocalMappedConnector.getBankAccountBalancesByAccountId(outBound.accountId,None).map(_._1.head) + + obpMappedResponse.map(response => InBoundGetBankAccountBalancesByAccountId( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status("", Nil), + data = response + )).recoverWith { + case e: Exception => Future(InBoundGetBankAccountBalancesByAccountId( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status(e.getMessage, Nil), + data = null + )) + } + } else if (obpMessageId.contains("get_bank_accounts_balances_by_account_ids")) { + val outBound = json.parse(message).extract[OutBoundGetBankAccountsBalancesByAccountIds] + val obpMappedResponse = code.bankconnectors.LocalMappedConnector.getBankAccountsBalancesByAccountIds(outBound.accountIds,None).map(_._1.head) + + obpMappedResponse.map(response => InBoundGetBankAccountsBalancesByAccountIds( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status("", Nil), + data = response + )).recoverWith { + case e: Exception => Future(InBoundGetBankAccountsBalancesByAccountIds( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status(e.getMessage, Nil), + data = null + )) + } + } else if (obpMessageId.contains("get_bank_account_balance_by_id")) { + val outBound = json.parse(message).extract[OutBoundGetBankAccountBalanceById] + val obpMappedResponse = code.bankconnectors.LocalMappedConnector.getBankAccountBalanceById(outBound.balanceId,None).map(_._1.head) + + obpMappedResponse.map(response => InBoundGetBankAccountBalanceById( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status("", Nil), + data = response + )).recoverWith { + case e: Exception => Future(InBoundGetBankAccountBalanceById( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status(e.getMessage, Nil), + data = null + )) + } + } else if (obpMessageId.contains("create_or_update_bank_account_balance")) { + val outBound = json.parse(message).extract[OutBoundCreateOrUpdateBankAccountBalance] + val obpMappedResponse = code.bankconnectors.LocalMappedConnector.createOrUpdateBankAccountBalance(outBound.bankId,outBound.accountId,outBound.balanceId,outBound.balanceType,outBound.balanceAmount,None).map(_._1.head) + + obpMappedResponse.map(response => InBoundCreateOrUpdateBankAccountBalance( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status("", Nil), + data = response + )).recoverWith { + case e: Exception => Future(InBoundCreateOrUpdateBankAccountBalance( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status(e.getMessage, Nil), + data = null + )) + } + } else if (obpMessageId.contains("delete_bank_account_balance")) { + val outBound = json.parse(message).extract[OutBoundDeleteBankAccountBalance] + val obpMappedResponse = code.bankconnectors.LocalMappedConnector.deleteBankAccountBalance(outBound.balanceId,None).map(_._1.head) + + obpMappedResponse.map(response => InBoundDeleteBankAccountBalance( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status("", Nil), + data = response + )).recoverWith { + case e: Exception => Future(InBoundDeleteBankAccountBalance( + + inboundAdapterCallContext = InboundAdapterCallContext( + correlationId = outBound.outboundAdapterCallContext.correlationId + ), + status = Status(e.getMessage, Nil), + data = false + )) + } } else if (obpMessageId.contains("dynamic_entity_process")) { val outBound = json.parse(message).extract[OutBoundDynamicEntityProcess] val obpMappedResponse = code.bankconnectors.LocalMappedConnector.dynamicEntityProcess(outBound.operation,outBound.entityName,outBound.requestBody,outBound.entityId,None,None,None,false,None).map(_._1.head) @@ -3172,8 +3276,8 @@ class ServerCallback(val ch: Channel) extends DeliverCallback with MdcLoggable{ data = null )) } -// ---------- created on 2025-04-07T14:53:47Z -//---------------- dynamic end ---------------------please don't modify this line +// ---------- created on 2025-05-27T08:15:58Z +//---------------- dynamic end ---------------------please don't modify this line } else { Future { 1 diff --git a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala index 30472e31a..08184bfb4 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala @@ -67,7 +67,7 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable { val errorCodeExample = "INTERNAL-OBP-ADAPTER-6001: ..." //---------------- dynamic start -------------------please don't modify this line -// ---------- created on 2025-05-22T11:32:05Z +// ---------- created on 2025-05-27T10:14:24Z messageDocs += getAdapterInfoDoc def getAdapterInfoDoc = MessageDoc( @@ -7176,6 +7176,36 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable { response.map(convertToTuple[List[BankAccountBalanceTraitCommons]](callContext)) } + messageDocs += getBankAccountsBalancesByAccountIdsDoc + def getBankAccountsBalancesByAccountIdsDoc = MessageDoc( + process = "obp.getBankAccountsBalancesByAccountIds", + messageFormat = messageFormat, + description = "Get Bank Accounts Balances By Account Ids", + outboundTopic = None, + inboundTopic = None, + exampleOutboundMessage = ( + OutBoundGetBankAccountsBalancesByAccountIds(outboundAdapterCallContext=MessageDocsSwaggerDefinitions.outboundAdapterCallContext, + accountIds=List(AccountId(accountIdExample.value))) + ), + exampleInboundMessage = ( + InBoundGetBankAccountsBalancesByAccountIds(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext, + status=MessageDocsSwaggerDefinitions.inboundStatus, + data=List( BankAccountBalanceTraitCommons(bankId=BankId(bankIdExample.value), + accountId=AccountId(accountIdExample.value), + balanceId=BalanceId(balanceIdExample.value), + balanceType=balanceTypeExample.value, + balanceAmount=BigDecimal(balanceAmountExample.value)))) + ), + adapterImplementation = Some(AdapterImplementation("- Core", 1)) + ) + + override def getBankAccountsBalancesByAccountIds(accountIds: List[AccountId], callContext: Option[CallContext]): OBPReturnType[Box[List[BankAccountBalanceTrait]]] = { + import com.openbankproject.commons.dto.{InBoundGetBankAccountsBalancesByAccountIds => InBound, OutBoundGetBankAccountsBalancesByAccountIds => OutBound} + val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, accountIds) + val response: Future[Box[InBound]] = sendRequest[InBound]("obp_get_bank_accounts_balances_by_account_ids", req, callContext) + response.map(convertToTuple[List[BankAccountBalanceTraitCommons]](callContext)) + } + messageDocs += getBankAccountBalanceByIdDoc def getBankAccountBalanceByIdDoc = MessageDoc( process = "obp.getBankAccountBalanceById", @@ -7266,8 +7296,8 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable { response.map(convertToTuple[Boolean](callContext)) } -// ---------- created on 2025-05-22T11:32:05Z -//---------------- dynamic end ---------------------please don't modify this line +// ---------- created on 2025-05-27T10:14:24Z +//---------------- dynamic end ---------------------please don't modify this line private val availableOperation = DynamicEntityOperation.values.map(it => s""""$it"""").mkString("[", ", ", "]") diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/dto/JsonsTransfer.scala b/obp-commons/src/main/scala/com/openbankproject/commons/dto/JsonsTransfer.scala index aa44348d0..8df423045 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/dto/JsonsTransfer.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/dto/JsonsTransfer.scala @@ -26,10 +26,10 @@ package com.openbankproject.commons.dto +import com.openbankproject.commons.model._ import com.openbankproject.commons.model.enums.StrongCustomerAuthentication.SCA import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatus.SCAStatus import com.openbankproject.commons.model.enums.{TransactionRequestStatus, _} -import com.openbankproject.commons.model._ import net.liftweb.json.{JObject, JValue} import java.util.Date @@ -392,6 +392,10 @@ case class OutBoundCreateTaxResidence(outboundAdapterCallContext: OutboundAdapte case class InBoundCreateTaxResidence(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: TaxResidenceCommons) extends InBoundTrait[TaxResidenceCommons] +case class OutBoundGetBankAccountsBalancesByAccountIds (outboundAdapterCallContext: OutboundAdapterCallContext, + accountIds: List[AccountId]) extends TopicTrait +case class InBoundGetBankAccountsBalancesByAccountIds (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: List[BankAccountBalanceTraitCommons]) extends InBoundTrait[List[BankAccountBalanceTraitCommons]] + case class OutBoundGetTaxResidence(outboundAdapterCallContext: OutboundAdapterCallContext, customerId: String) extends TopicTrait From 0a298d1e28c3898706e7289d339cc484f80920dc Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 27 May 2025 12:07:17 +0200 Subject: [PATCH 09/10] refactor/update cash account type retrieval in JSON response --- .../berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala index eb49ebeea..6d5897d0f 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala @@ -8,8 +8,9 @@ import code.api.util.{APIUtil, ConsentJWT, CustomJsonFormats, JwtUtil} import code.consent.ConsentTrait import code.model.ModeratedTransaction import code.util.Helper.MdcLoggable -import com.openbankproject.commons.model.enums.AccountRoutingScheme +import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.model._ +import com.openbankproject.commons.model.enums.AccountRoutingScheme import net.liftweb.common.Box.tryo import net.liftweb.common.{Box, Full} import net.liftweb.json.{JValue, parse} @@ -17,7 +18,6 @@ import net.liftweb.json.{JValue, parse} import java.text.SimpleDateFormat import java.util.Date import scala.concurrent.Future -import com.openbankproject.commons.ExecutionContext.Implicits.global case class JvalueCaseClass(jvalueToCaseclass: JValue) object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{ @@ -341,13 +341,15 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats with MdcLoggable{ None } + val cashAccountType = x.attributes.getOrElse(Nil).filter(_.name== "cashAccountType").map(_.value).headOption.getOrElse("") + CoreAccountJsonV13( resourceId = x.accountId.value, iban = iBan, bban = bBan, currency = x.currency, name = x.name, - cashAccountType = x.accountType, + cashAccountType = cashAccountType, product = x.accountType, balances = accountBalances, _links = CoreAccountLinksJsonV13( From a13aa9f32eb1e51c7e5f6992c151a325035c2734 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 27 May 2025 12:10:26 +0200 Subject: [PATCH 10/10] refactor/update example consent reference ID in glossary --- obp-api/src/main/scala/code/api/util/ExampleValue.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index f304f87ba..4f434cbc3 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -1586,7 +1586,7 @@ object ExampleValue { lazy val directDebitIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("direct_debit_id", directDebitIdExample) - lazy val consentReferenceIdExample = ConnectorField("9d429899-24f5-42c8-8565-943ffa6a7946" ,NoDescriptionProvided) + lazy val consentReferenceIdExample = ConnectorField("123456" ,NoDescriptionProvided) glossaryItems += makeGlossaryItem("consent_id", consentReferenceIdExample) lazy val consentIdExample = ConnectorField("9d429899-24f5-42c8-8565-943ffa6a7947",NoDescriptionProvided)
Consent IdConsent Reference Id Consumer Id Jwt Payload Status