diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 4bb7318dd..557ae450d 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -360,6 +360,17 @@ client 4.0.1 + + io.swagger.parser.v3 + swagger-parser + 2.0.13 + + + com.fasterxml.jackson.core + jackson-core + 2.9.8 + + diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 31047a7a8..097d85a5e 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -6,7 +6,7 @@ ### Base configuration -## Which data connector to use, if config star as connector, should make sure other connector config appropriate and works +## Which data connector to use, if config `star` as connector, please also check `starConnector_supported_types` connector=mapped #connector=mongodb #connector=kafka @@ -17,6 +17,9 @@ connector=mapped #connector=star #connector=... +## if connector = star, then need to set which connectors will be used. For now, obp support rest, akka, kafka. If you set kafka, then you need to start the kafka server. +#starConnector_supported_types=rest,akka,kafka + ## whether export LocalMappedConnector methods as endpoints, it is just for develop, default is false #connector.export.LocalMappedConnector=false @@ -32,9 +35,9 @@ connector=mapped #connector.cache.ttl.seconds.getCounterpartiesFromTransaction=10 #connector.cache.ttl.seconds.APIMethods121.getTransactions=10 -## MethodRouting cache time-to-live in seconds, caching defaults to 30 seconds +## MethodRouting cache time-to-live in seconds #methodRouting.cache.ttl.seconds=30 -## webui props cache time-to-live in seconds, caching defaults to 20 seconds +## webui props cache time-to-live in seconds #webui.props.cache.ttl.seconds=20 ## enable logging all the database queries in log file diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 001e57782..05bea2a6c 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -220,7 +220,7 @@ class Boot extends MdcLoggable { // ensure our relational database's tables are created/fit the schema val connector = APIUtil.getPropsValue("connector").openOrThrowException("no connector set") - if(connector != "mongodb" || connector == "star") + if(connector != "mongodb") schemifyAll() // This sets up MongoDB config (for the mongodb connector) @@ -242,9 +242,11 @@ class Boot extends MdcLoggable { val actorSystem = ObpActorSystem.startLocalActorSystem() connector match { - case ("akka_vDec2018"| "star") => + case "akka_vDec2018" => // Start Actor system of Akka connector ObpActorSystem.startNorthSideAkkaConnectorActorSystem() + case "star" if (APIUtil.getPropsValue("starConnector_supported_types","").split(",").contains("akka")) => + ObpActorSystem.startNorthSideAkkaConnectorActorSystem() case _ => // Do nothing } @@ -336,7 +338,7 @@ class Boot extends MdcLoggable { WebhookHelperActors.startLocalWebhookHelperWorkers(actorSystem) - if (connector.startsWith("kafka") || connector == "star") { + if (connector.startsWith("kafka") || (connector == "star" && APIUtil.getPropsValue("starConnector_supported_types","").split(",").contains("kafka"))) { logger.info(s"KafkaHelperActors.startLocalKafkaHelperWorkers( ${actorSystem} ) starting") KafkaHelperActors.startLocalKafkaHelperWorkers(actorSystem) // Start North Side Consumer if it's not already started diff --git a/obp-api/src/main/scala/code/api/APIBuilder/APIBuilderModel.scala b/obp-api/src/main/scala/code/api/APIBuilder/APIBuilderModel.scala index 656dc3827..2d4d6be08 100644 --- a/obp-api/src/main/scala/code/api/APIBuilder/APIBuilderModel.scala +++ b/obp-api/src/main/scala/code/api/APIBuilder/APIBuilderModel.scala @@ -257,15 +257,15 @@ object APIBuilderModel def overwriteApiCode(apiSource: Source, jsonFactorySource:Source =jsonFactorySource) = { //APIMethods_APIBuilder.scala - overwriteCurrentFile(apiSource,"src/main/scala/code/api/builder/APIMethods_APIBuilder.scala") + overwriteCurrentFile(apiSource,"obp-api/src/main/scala/code/api/builder/APIMethods_APIBuilder.scala") //JsonFactory_APIBuilder.scala - overwriteCurrentFile(jsonFactorySource, "src/main/scala/code/api/builder/JsonFactory_APIBuilder.scala") + overwriteCurrentFile(jsonFactorySource, "obp-api/src/main/scala/code/api/builder/JsonFactory_APIBuilder.scala") println("Congratulations! You make the new APIs. Please restart OBP-API server!") } - val jsonJValueFromFile: JValue = APIUtil.getJValueFromFile("src/main/scala/code/api/APIBuilder/APIModelSource.json") + val jsonJValueFromFile: JValue = APIUtil.getJValueFromFile("obp-api/src/main/scala/code/api/APIBuilder/APIModelSource.json") //"/templates" val apiUrl= getApiUrl(jsonJValueFromFile) diff --git a/obp-api/src/main/scala/code/api/APIBuilder/apiResourceDoc/APIBuilder.scala b/obp-api/src/main/scala/code/api/APIBuilder/apiResourceDoc/APIBuilder.scala index d18b005ec..81c1ac0db 100644 --- a/obp-api/src/main/scala/code/api/APIBuilder/apiResourceDoc/APIBuilder.scala +++ b/obp-api/src/main/scala/code/api/APIBuilder/apiResourceDoc/APIBuilder.scala @@ -36,7 +36,7 @@ object APIBuilder { def main(args: Array[String]): Unit = overwriteApiCode(apiSource,jsonFactorySource) - val jsonJValueFromFile: JValue = APIUtil.getJValueFromFile("src/main/scala/code/api/APIBuilder/apiResourceDoc/apisResource.json") + val jsonJValueFromFile: JValue = APIUtil.getJValueFromFile("obp-api/src/main/scala/code/api/APIBuilder/apiResourceDoc/apisResource.json") val resourceDocsJObject= jsonJValueFromFile.\("resource_docs").children.asInstanceOf[List[JObject]] @@ -210,7 +210,7 @@ object APIBuilder cc => { for { u <- $getMultipleAuthenticationStatement - jsonStringFromFile = scala.io.Source.fromFile("src/main/scala/code/api/APIBuilder/apisResource.json").mkString + jsonStringFromFile = scala.io.Source.fromFile("obp-api/src/main/scala/code/api/APIBuilder/apisResource.json").mkString jsonJValueFromFile = json.parse(jsonStringFromFile) resourceDocsJObject= jsonJValueFromFile.\("resource_docs").children.asInstanceOf[List[JObject]] getMethodJValue = resourceDocsJObject.filter(jObject => jObject.\("request_verb") == JString("GET")&& !jObject.\("request_url").asInstanceOf[JString].values.contains("_ID")).head @@ -380,7 +380,7 @@ trait APIMethods_APIBuilder val ImplementationsBuilderAPI = new Object() { - val apiVersion: ApiVersion = ApiVersion.apiBuilder + val apiVersion = ApiVersion.apiBuilder val resourceDocs = ArrayBuffer[ResourceDoc]() val apiRelations = ArrayBuffer[ApiRelation]() val codeContext = CodeContext(resourceDocs, apiRelations) diff --git a/obp-api/src/main/scala/code/api/APIBuilder/swagger/APIBuilderSwagger.scala b/obp-api/src/main/scala/code/api/APIBuilder/swagger/APIBuilderSwagger.scala index 454a5c93a..a8c16cf2d 100644 --- a/obp-api/src/main/scala/code/api/APIBuilder/swagger/APIBuilderSwagger.scala +++ b/obp-api/src/main/scala/code/api/APIBuilder/swagger/APIBuilderSwagger.scala @@ -35,7 +35,7 @@ import scala.meta._ object APIBuilderSwagger { def main(args: Array[String]): Unit = overwriteApiCode(apiSource,jsonFactorySource) - val jsonJValueFromFile: JValue = APIUtil.getJValueFromFile("src/main/scala/code/api/APIBuilder/swagger/swaggerResource.json") + val jsonJValueFromFile: JValue = APIUtil.getJValueFromFile("obp-api/src/main/scala/code/api/APIBuilder/swagger/swaggerResource.json") val getSingleApiResponseBody: JValue = jsonJValueFromFile \\("foo")\"foo"\"value" //"template" @@ -328,7 +328,7 @@ trait APIMethods_APIBuilder val ImplementationsBuilderAPI = new Object() { - val apiVersion: ApiVersion = ApiVersion.apiBuilder + val apiVersion = ApiVersion.apiBuilder val resourceDocs = ArrayBuffer[ResourceDoc]() val apiRelations = ArrayBuffer[ApiRelation]() val codeContext = CodeContext(resourceDocs, apiRelations) 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 0c61dc161..cc648fac5 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 @@ -5,7 +5,7 @@ import java.util.Date import code.api.Constant import code.api.UKOpenBanking.v2_0_0.JSONFactory_UKOpenBanking_200 import code.api.UKOpenBanking.v2_0_0.JSONFactory_UKOpenBanking_200.{Account, AccountBalancesUKV200, AccountInner, AccountList, Accounts, BalanceJsonUKV200, BalanceUKOpenBankingJson, BankTransactionCodeJson, CreditLineJson, DataJsonUKV200, Links, MetaBisJson, MetaInnerJson, TransactionCodeJson, TransactionInnerJson, TransactionsInnerJson, TransactionsJsonUKV200} -import code.api.berlin.group.v1.JSONFactory_BERLIN_GROUP_1.{AccountBalance, AccountBalances, AmountOfMoneyV1, ClosingBookedBody, ExpectedBody, TransactionJsonV1, TransactionsJsonV1, ViewAccount} +import code.api.berlin.group.v1.JSONFactory_BERLIN_GROUP_1.{AccountBalanceV1, AccountBalances, AmountOfMoneyV1, Balances, ClosingBookedBody, CoreAccountJsonV1, CoreAccountsJsonV1, ExpectedBody, TransactionJsonV1, Transactions, TransactionsJsonV1, ViewAccount} import code.api.util.APIUtil.{defaultJValue, _} import code.api.util.ApiRole._ import code.api.util.{ApiTrigger, ExampleValue} @@ -15,6 +15,8 @@ import code.api.v3_0_0.JSONFactory300.createBranchJsonV300 import code.api.v3_0_0.custom.JSONFactoryCustom300 import code.api.v3_0_0.{LobbyJsonV330, _} import code.api.v3_1_0.{BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _} +import code.api.v3_0_0.{EmptyElasticSearch, LobbyJsonV330, NewModeratedCoreAccountJsonV300, _} +import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _} import code.branches.Branches.{Branch, DriveUpString, LobbyString} import code.sandbox.SandboxData import code.transactionrequests.TransactionRequests.TransactionRequestTypes._ @@ -25,6 +27,8 @@ import com.openbankproject.commons.model import com.openbankproject.commons.model.PinResetReason.{FORGOT, GOOD_SECURITY_PRACTICE} import com.openbankproject.commons.model.{ViewBasic, _} import com.openbankproject.commons.util.ReflectUtils +import com.openbankproject.commons.model.{AccountBalance, GeoTag, ViewBasic, _} +import net.liftweb.json import scala.collection.immutable.List @@ -2651,13 +2655,13 @@ object SwaggerDefinitionsJSON { lastActionDateTime = DateWithDayExampleObject ) - val accountBalance = AccountBalance( + val accountBalanceV1 = AccountBalanceV1( closingBooked = closingBookedBody, expected = expectedBody ) val accountBalances = AccountBalances( - `balances` = List(accountBalance) + `balances` = List(accountBalanceV1) ) val transactionJsonV1 = TransactionJsonV1( @@ -3414,7 +3418,21 @@ object SwaggerDefinitionsJSON { val elasticSearchQuery = ElasticSearchQuery(emptyElasticSearch) val elasticSearchJsonV300 = ElasticSearchJsonV300(elasticSearchQuery) + + val accountBalanceV310 = AccountBalanceV310( + id = accountIdExample.value, + label = labelExample.value, + bank_id = bankIdExample.value, + account_routings = List(accountRouting), + balance = amountOfMoney + ) + val accountBalancesV310Json = AccountsBalancesV310Json( + accounts = List(accountBalanceV310), + overall_balance = amountOfMoney, + overall_balance_date = DateWithMsExampleObject + ) + //The common error or success format. //Just some helper format to use in Json case class NoSupportYet() diff --git a/obp-api/src/main/scala/code/api/STET/v1_4/AISPApi.scala b/obp-api/src/main/scala/code/api/STET/v1_4/AISPApi.scala index 9b6cc38c7..1f4f20aab 100644 --- a/obp-api/src/main/scala/code/api/STET/v1_4/AISPApi.scala +++ b/obp-api/src/main/scala/code/api/STET/v1_4/AISPApi.scala @@ -209,8 +209,8 @@ The TPP sends a request to the ASPSP for retrieving the list of the PSU payment (_, callContext) <- NewStyle.function.getBank(bankId, callContext) availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) - - Full(accounts) <- {Connector.connector.vend.getBankAccounts(availablePrivateAccounts,callContext)} + + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) } yield { (createTransactionListJSON(accounts), callContext) diff --git a/obp-api/src/main/scala/code/api/UKOpenBanking/v2_0_0/APIMethods_UKOpenBanking_200.scala b/obp-api/src/main/scala/code/api/UKOpenBanking/v2_0_0/APIMethods_UKOpenBanking_200.scala index d9fa4dc9b..46ae49a49 100644 --- a/obp-api/src/main/scala/code/api/UKOpenBanking/v2_0_0/APIMethods_UKOpenBanking_200.scala +++ b/obp-api/src/main/scala/code/api/UKOpenBanking/v2_0_0/APIMethods_UKOpenBanking_200.scala @@ -57,9 +57,9 @@ object APIMethods_UKOpenBanking_200 extends RestHelper{ for { (Full(u), callContext) <- authorizedAccess(cc) availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) - accounts <- {Connector.connector.vend.getBankAccounts(availablePrivateAccounts, callContext)} + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) } yield { - (JSONFactory_UKOpenBanking_200.createAccountsListJSON(accounts.getOrElse(Nil)), callContext) + (JSONFactory_UKOpenBanking_200.createAccountsListJSON(accounts), callContext) } } } @@ -144,9 +144,9 @@ object APIMethods_UKOpenBanking_200 extends RestHelper{ availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) map { _.filter(_.accountId.value == accountId.value) } - accounts <- {Connector.connector.vend.getBankAccounts(availablePrivateAccounts, callContext)} + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) } yield { - (JSONFactory_UKOpenBanking_200.createAccountJSON(accounts.getOrElse(Nil)), callContext) + (JSONFactory_UKOpenBanking_200.createAccountJSON(accounts), callContext) } } } @@ -229,10 +229,10 @@ object APIMethods_UKOpenBanking_200 extends RestHelper{ availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) - accounts <- {Connector.connector.vend.getBankAccounts(availablePrivateAccounts, callContext)} + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) } yield { - (JSONFactory_UKOpenBanking_200.createBalancesJSON(accounts.getOrElse(Nil)), callContext) + (JSONFactory_UKOpenBanking_200.createBalancesJSON(accounts), callContext) } } } diff --git a/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/AccountsApi.scala b/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/AccountsApi.scala index 36bbbcf56..288524b79 100644 --- a/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/AccountsApi.scala +++ b/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/AccountsApi.scala @@ -2,7 +2,7 @@ package code.api.UKOpenBanking.v3_1_0 import code.api.berlin.group.v1_3.JvalueCaseClass import code.api.util.APIUtil._ -import code.api.util.ApiTag +import code.api.util.{ApiTag, NewStyle} import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ import code.bankconnectors.Connector @@ -110,9 +110,9 @@ object APIMethods_AccountsApi extends RestHelper { for { (Full(u), callContext) <- authorizedAccess(cc) availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) - accounts <- {Connector.connector.vend.getBankAccounts(availablePrivateAccounts, callContext)} + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) } yield { - (JSONFactory_UKOpenBanking_310.createAccountsListJSON(accounts.getOrElse(Nil)), callContext) + (JSONFactory_UKOpenBanking_310.createAccountsListJSON(accounts), callContext) } } @@ -202,9 +202,9 @@ object APIMethods_AccountsApi extends RestHelper { availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) map { _.filter(_.accountId.value == accountId.value) } - accounts <- {Connector.connector.vend.getBankAccounts(availablePrivateAccounts, callContext)} + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) } yield { - (JSONFactory_UKOpenBanking_310.createAccountJSON(accounts.getOrElse(Nil)), callContext) + (JSONFactory_UKOpenBanking_310.createAccountJSON(accounts), callContext) } } } diff --git a/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/BalancesApi.scala b/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/BalancesApi.scala index 572fd3fa6..228489d65 100644 --- a/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/BalancesApi.scala +++ b/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/BalancesApi.scala @@ -222,10 +222,10 @@ object APIMethods_BalancesApi extends RestHelper { availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) - accounts <- {Connector.connector.vend.getBankAccounts(availablePrivateAccounts, callContext)} + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) } yield { - (JSONFactory_UKOpenBanking_310.createBalancesJSON(accounts.getOrElse(Nil)), callContext) + (JSONFactory_UKOpenBanking_310.createBalancesJSON(accounts), callContext) } } } diff --git a/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/TransactionsApi.scala b/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/TransactionsApi.scala index 8e16d5212..186286446 100644 --- a/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/TransactionsApi.scala +++ b/obp-api/src/main/scala/code/api/UKOpenBanking/v3_1_0/TransactionsApi.scala @@ -1022,7 +1022,7 @@ object APIMethods_TransactionsApi extends RestHelper { availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) - Full(accounts) <- Connector.connector.vend.getBankAccounts(availablePrivateAccounts, callContext) + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) transactionAndTransactionRequestTuple = for{ bankAccount <- accounts diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1/JSONFactory_BERLIN_GROUP_1.scala b/obp-api/src/main/scala/code/api/berlin/group/v1/JSONFactory_BERLIN_GROUP_1.scala index 6dd68cbf0..bf66cecd2 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1/JSONFactory_BERLIN_GROUP_1.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1/JSONFactory_BERLIN_GROUP_1.scala @@ -44,11 +44,11 @@ object JSONFactory_BERLIN_GROUP_1 extends CustomJsonFormats { amount : AmountOfMoneyV1, lastActionDateTime: Date ) - case class AccountBalance( + case class AccountBalanceV1( closingBooked: ClosingBookedBody, expected: ExpectedBody ) - case class AccountBalances(`balances`: List[AccountBalance]) + case class AccountBalances(`balances`: List[AccountBalanceV1]) case class TransactionsJsonV1( transactions_booked: List[TransactionJsonV1], @@ -93,7 +93,7 @@ object JSONFactory_BERLIN_GROUP_1 extends CustomJsonFormats { val sumOfAll = (BigDecimal(moderatedAccount.balance) + sumOfAllUncompletedTransactionrequests).toString() AccountBalances( - AccountBalance( + AccountBalanceV1( closingBooked = ClosingBookedBody( amount = AmountOfMoneyV1(currency = moderatedAccount.currency.getOrElse(""), content = moderatedAccount.balance), date = APIUtil.DateWithDayFormat.format(latestCompletedEndDate) 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 661bf6556..a0fddfa76 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 @@ -270,10 +270,10 @@ of the PSU at this ASPSP. availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) - Full((coreAccounts)) <- {Connector.connector.vend.getBankAccounts(availablePrivateAccounts, callContext)} + (accounts, callContext)<- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) } yield { - (JSONFactory_BERLIN_GROUP_1_3.createAccountListJson(coreAccounts, u), callContext) + (JSONFactory_BERLIN_GROUP_1_3.createAccountListJson(accounts, u), callContext) } } } diff --git a/obp-api/src/main/scala/code/api/builder/APIMethods_APIBuilder.scala b/obp-api/src/main/scala/code/api/builder/APIMethods_APIBuilder.scala index d5cf37312..635dbb469 100644 --- a/obp-api/src/main/scala/code/api/builder/APIMethods_APIBuilder.scala +++ b/obp-api/src/main/scala/code/api/builder/APIMethods_APIBuilder.scala @@ -1,10 +1,9 @@ package code.api.builder import java.util.UUID - import code.api.builder.JsonFactory_APIBuilder._ import code.api.util.APIUtil._ import code.api.util.ApiTag._ -import code.api.util.{ApiVersion, CustomJsonFormats} +import code.api.util.ApiVersion import code.api.util.ErrorMessages._ import net.liftweb.common.Full import net.liftweb.http.rest.RestHelper @@ -13,7 +12,6 @@ import net.liftweb.json.Extraction._ import net.liftweb.json._ import net.liftweb.mapper.By import net.liftweb.util.Helpers.tryo - import scala.collection.immutable.Nil import scala.collection.mutable.ArrayBuffer trait APIMethods_APIBuilder { self: RestHelper => @@ -22,10 +20,10 @@ trait APIMethods_APIBuilder { self: RestHelper => val resourceDocs = ArrayBuffer[ResourceDoc]() val apiRelations = ArrayBuffer[ApiRelation]() val codeContext = CodeContext(resourceDocs, apiRelations) - implicit val formats = CustomJsonFormats.formats - val TemplateNotFound = "OBP-31001: Template not found. Please specify a valid value for TEMPLATE_ID." - def endpointsOfBuilderAPI = getTemplates :: getTemplate :: createTemplate :: deleteTemplate :: Nil - resourceDocs += ResourceDoc(getTemplates, apiVersion, "getTemplates", "GET", "/templates", "Get Templates", "Return All Templates", emptyObjectJson, templatesJson, List(UserNotLoggedIn, UnknownError), Catalogs(notCore, notPSD2, notOBWG), apiTagApiBuilder :: Nil) + implicit val formats = code.api.util.CustomJsonFormats.formats + val TemplatesNotFound = "OBP-31001: Templates not found. Please specify a valid value for TEMPLATES_ID." + def endpointsOfBuilderAPI = getTemplates :: createTemplate :: getTemplate :: deleteTemplate :: Nil + resourceDocs += ResourceDoc(getTemplates, apiVersion, "getTemplates", "GET", "/templates", "Get Templates", "Return All my templates. Authentication is Mandatory.", emptyObjectJson, templatesJson, List(UserNotLoggedIn, UnknownError), Catalogs(notCore, notPSD2, notOBWG), apiTagApiBuilder :: Nil) lazy val getTemplates: OBPEndpoint = { case ("templates" :: Nil) JsonGet req => cc => { @@ -34,16 +32,7 @@ trait APIMethods_APIBuilder { self: RestHelper => } } } - resourceDocs += ResourceDoc(getTemplate, apiVersion, "getTemplate", "GET", "/templates/TEMPLATE_ID", "Get Template", "Return One Template By Id", emptyObjectJson, templateJson, List(UserNotLoggedIn, UnknownError), Catalogs(notCore, notPSD2, notOBWG), apiTagApiBuilder :: Nil) - lazy val getTemplate: OBPEndpoint = { - case ("templates" :: templateId :: Nil) JsonGet _ => - cc => { - for (u <- cc.user ?~ UserNotLoggedIn; template <- APIBuilder_Connector.getTemplateById(templateId) ?~! TemplateNotFound; templateJson = JsonFactory_APIBuilder.createTemplate(template); jsonObject: JValue = decompose(templateJson)) yield { - successJsonResponse(jsonObject) - } - } - } - resourceDocs += ResourceDoc(createTemplate, apiVersion, "createTemplate", "POST", "/templates", "Create Template", "Create One Template", createTemplateJson, templateJson, List(UnknownError), Catalogs(notCore, notPSD2, notOBWG), apiTagApiBuilder :: Nil) + resourceDocs += ResourceDoc(createTemplate, apiVersion, "createTemplate", "POST", "/templates", "Create Template", "Create template. Authentication is Mandatory.", createTemplateJson, templateJson, List(UnknownError), Catalogs(notCore, notPSD2, notOBWG), apiTagApiBuilder :: Nil) lazy val createTemplate: OBPEndpoint = { case ("templates" :: Nil) JsonPost json -> _ => cc => { @@ -52,15 +41,20 @@ trait APIMethods_APIBuilder { self: RestHelper => } } } - resourceDocs += ResourceDoc(deleteTemplate, apiVersion, "deleteTemplate", "DELETE", "/templates/TEMPLATE_ID", "Delete Template", "Delete One Template", emptyObjectJson, emptyObjectJson.copy("true"), List(UserNotLoggedIn, UnknownError), Catalogs(notCore, notPSD2, notOBWG), apiTagApiBuilder :: Nil) + resourceDocs += ResourceDoc(getTemplate, apiVersion, "getTemplate", "GET", "/templates/{template_id}", "Get Template", "Return one template. Authentication is Mandatory.", emptyObjectJson, templateJson, List(UserNotLoggedIn, UnknownError), Catalogs(notCore, notPSD2, notOBWG), apiTagApiBuilder :: Nil) + lazy val getTemplate: OBPEndpoint = { + case ("templates" :: templateId :: Nil) JsonGet _ => + cc => { + for (u <- cc.user ?~ UserNotLoggedIn; template <- APIBuilder_Connector.getTemplateById(templateId) ?~! TemplatesNotFound; templateJson = JsonFactory_APIBuilder.createTemplate(template); jsonObject: JValue = decompose(templateJson)) yield { + successJsonResponse(jsonObject) + } + } + } + resourceDocs += ResourceDoc(deleteTemplate, apiVersion, "deleteTemplate", "DELETE", "/templates/{template_id}", "Delete Template", "Delete template. Authentication is Mandatory.", emptyObjectJson, emptyObjectJson.copy("true"), List(UserNotLoggedIn, UnknownError), Catalogs(notCore, notPSD2, notOBWG), apiTagApiBuilder :: Nil) lazy val deleteTemplate: OBPEndpoint = { case ("templates" :: templateId :: Nil) JsonDelete _ => cc => { - for ( - u <- cc.user ?~ UserNotLoggedIn; - template <- APIBuilder_Connector.getTemplateById(templateId) ?~! TemplateNotFound; - deleted <- APIBuilder_Connector.deleteTemplate(templateId) - ) yield { + for (u <- cc.user ?~ UserNotLoggedIn; template <- APIBuilder_Connector.getTemplateById(templateId) ?~! TemplatesNotFound; deleted <- APIBuilder_Connector.deleteTemplate(templateId)) yield { if (deleted) noContentJsonResponse else errorJsonResponse("Delete not completed") } } @@ -68,25 +62,25 @@ trait APIMethods_APIBuilder { self: RestHelper => } } object APIBuilder_Connector { - val allAPIBuilderModels = List(MappedTemplate_4824100653501473508) - def createTemplate(createTemplateJson: CreateTemplateJson) = Full(MappedTemplate_4824100653501473508.create.mTemplateId(generateUUID()).mAuthor(createTemplateJson.author).mPages(createTemplateJson.pages).mPoints(createTemplateJson.points).saveMe()) - def getTemplates() = Full(MappedTemplate_4824100653501473508.findAll()) - def getTemplateById(templateId: String) = MappedTemplate_4824100653501473508.find(By(MappedTemplate_4824100653501473508.mTemplateId, templateId)) - def deleteTemplate(templateId: String) = MappedTemplate_4824100653501473508.find(By(MappedTemplate_4824100653501473508.mTemplateId, templateId)).map(_.delete_!) + val allAPIBuilderModels = List(MappedTemplates_430794570390706560) + def createTemplate(createTemplateJson: CreateTemplateJson) = Full(MappedTemplates_430794570390706560.create.mTemplateId(UUID.randomUUID().toString).mAuthor(createTemplateJson.author).mPages(createTemplateJson.pages).mPoints(createTemplateJson.points).saveMe()) + def getTemplates() = Full(MappedTemplates_430794570390706560.findAll()) + def getTemplateById(templateId: String) = MappedTemplates_430794570390706560.find(By(MappedTemplates_430794570390706560.mTemplateId, templateId)) + def deleteTemplate(templateId: String) = MappedTemplates_430794570390706560.find(By(MappedTemplates_430794570390706560.mTemplateId, templateId)).map(_.delete_!) } import net.liftweb.mapper._ -class MappedTemplate_4824100653501473508 extends Template with LongKeyedMapper[MappedTemplate_4824100653501473508] with IdPK { +class MappedTemplates_430794570390706560 extends Template with LongKeyedMapper[MappedTemplates_430794570390706560] with IdPK { object mAuthor extends MappedString(this, 100) override def author: String = mAuthor.get object mPages extends MappedInt(this) override def pages: Int = mPages.get object mPoints extends MappedDouble(this) override def points: Double = mPoints.get - def getSingleton = MappedTemplate_4824100653501473508 + def getSingleton = MappedTemplates_430794570390706560 object mTemplateId extends MappedString(this, 100) override def templateId: String = mTemplateId.get } -object MappedTemplate_4824100653501473508 extends MappedTemplate_4824100653501473508 with LongKeyedMetaMapper[MappedTemplate_4824100653501473508] +object MappedTemplates_430794570390706560 extends MappedTemplates_430794570390706560 with LongKeyedMetaMapper[MappedTemplates_430794570390706560] trait Template { `_` => def author: String def pages: Int diff --git a/obp-api/src/main/scala/code/api/builder/JsonFactory_APIBuilder.scala b/obp-api/src/main/scala/code/api/builder/JsonFactory_APIBuilder.scala index f854cd1cf..abd4a78be 100644 --- a/obp-api/src/main/scala/code/api/builder/JsonFactory_APIBuilder.scala +++ b/obp-api/src/main/scala/code/api/builder/JsonFactory_APIBuilder.scala @@ -1,7 +1,7 @@ package code.api.builder import code.api.util.APIUtil +case class TemplateJson(id: String = """11231231312""", author: String = """Chinua Achebe""", pages: Int = 209, points: Double = 1.3) case class CreateTemplateJson(author: String = """Chinua Achebe""", pages: Int = 209, points: Double = 1.3) -case class TemplateJson(template_id: String = """11231231312""", author: String = """Chinua Achebe""", pages: Int = 209, points: Double = 1.3) object JsonFactory_APIBuilder { val templateJson = TemplateJson() val templatesJson = List(templateJson) 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 ba9dff744..58e8e1ab2 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -2496,6 +2496,18 @@ Returns a string showed to the developer def createBasicUserAuthContextJson(userAuthContexts : List[UserAuthContext]) : List[BasicUserAuthContext] = { userAuthContexts.map(createBasicUserAuthContext) } + + def createBasicUserAuthContextJsonFromCallContext(callContext : CallContext) : List[BasicGeneralContext] = { + val requestHeaders: List[HTTPParam] = callContext.requestHeaders + + //Only these Pass-Through headers can be propagated to Adapter side. + val passThroughHeaders: List[HTTPParam] = requestHeaders + .filter(requestHeader => requestHeader.name.startsWith("Pass-Through")) + + passThroughHeaders.map(header => BasicGeneralContext( + key = header.name, + value = if (header.values.isEmpty) "" else header.values.head)) + } def createAuthInfoCustomerJson(customer : Customer) : InternalBasicCustomer = { InternalBasicCustomer( diff --git a/obp-api/src/main/scala/code/api/util/ApiSession.scala b/obp-api/src/main/scala/code/api/util/ApiSession.scala index 5bf704896..a3fb4262b 100644 --- a/obp-api/src/main/scala/code/api/util/ApiSession.scala +++ b/obp-api/src/main/scala/code/api/util/ApiSession.scala @@ -57,8 +57,10 @@ case class CallContext( views <- tryo(permission.views) linkedCustomers <- tryo(CustomerX.customerProvider.vend.getCustomersByUserId(user.userId)) likedCustomersBasic = if (linkedCustomers.isEmpty) None else Some(createInternalLinkedBasicCustomersJson(linkedCustomers)) - userAuthContexts<- UserAuthContextProvider.userAuthContextProvider.vend.getUserAuthContextsBox(user.userId) - basicUserAuthContexts = if (userAuthContexts.isEmpty) None else Some(createBasicUserAuthContextJson(userAuthContexts)) + userAuthContexts<- UserAuthContextProvider.userAuthContextProvider.vend.getUserAuthContextsBox(user.userId) + basicUserAuthContextsFromDatabase = if (userAuthContexts.isEmpty) None else Some(createBasicUserAuthContextJson(userAuthContexts)) + generalContextFromPassThroughHeaders = createBasicUserAuthContextJsonFromCallContext(this) + basicUserAuthContexts = Some(basicUserAuthContextsFromDatabase.getOrElse(List.empty[BasicUserAuthContext])) authViews<- tryo( for{ view <- views @@ -79,7 +81,7 @@ case class CallContext( correlationId = this.correlationId, sessionId = this.sessionId, consumerId = Some(consumerId), - generalContext = None, + generalContext = Some(generalContextFromPassThroughHeaders), outboundAdapterAuthInfo = Some(OutboundAdapterAuthInfo( userId = currentResourceUserId, username = username, diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 97585c4a2..6793ac894 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -204,6 +204,7 @@ object NewStyle { (nameOf(Implementations3_1_0.createAccount), ApiVersion.v3_1_0.toString), (nameOf(Implementations3_1_0.saveHistoricalTransaction), ApiVersion.v3_1_0.toString), (nameOf(Implementations3_1_0.getPrivateAccountByIdFull), ApiVersion.v3_1_0.toString), + (nameOf(Implementations3_1_0.getBankAccountsBalances), ApiVersion.v3_1_0.toString), (nameOf(Implementations4_0_0.getBanks), ApiVersion.v4_0_0.toString) ) @@ -286,12 +287,27 @@ object NewStyle { connectorEmptyResponse(_, callContext) } } - + def getBalances(callContext: Option[CallContext]) : OBPReturnType[List[Bank]] = { + Connector.connector.vend.getBanks(callContext: Option[CallContext]) map { + connectorEmptyResponse(_, callContext) + } + } def getBankAccount(bankId : BankId, accountId : AccountId, callContext: Option[CallContext]): OBPReturnType[BankAccount] = { Connector.connector.vend.getBankAccount(bankId, accountId, callContext) map { i => (unboxFullOrFail(i._1, callContext,s"$BankAccountNotFound Current BankId is $bankId and Current AccountId is $accountId", 400 ), i._2) } } + def getBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]): OBPReturnType[List[BankAccount]] = { + Connector.connector.vend.getBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) map { i => + (unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2) + } + } + + def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]): OBPReturnType[AccountsBalances] = { + Connector.connector.vend.getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) map { i => + (unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2) + } + } def getBankAccountByIban(iban : String, callContext: Option[CallContext]) : OBPReturnType[BankAccount] = { Connector.connector.vend.getBankAccountByIban(iban : String, callContext: Option[CallContext]) map { i => @@ -1507,7 +1523,7 @@ object NewStyle { } } - private[this] val methodRoutingTTL = APIUtil.getPropsValue(s"methodRouting.cache.ttl.seconds", "30").toInt // default 30 seconds + private[this] val methodRoutingTTL = APIUtil.getPropsValue(s"methodRouting.cache.ttl.seconds", "0").toInt def getMethodRoutings(methodName: Option[String], isBankIdExactMatch: Option[Boolean] = None, bankIdPattern: Option[String] = None): List[MethodRoutingT] = { import scala.concurrent.duration._ diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index a1612e42d..e833caeb0 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -1,11 +1,9 @@ package code.api.v3_1_0 import java.text.SimpleDateFormat -import java.util.{Date, UUID} +import java.util.UUID import java.util.regex.Pattern -import code.accountholders.AccountHolders -import code.api.{APIFailureNewStyle, ChargePolicy} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.ResourceDocs1_4_0.{MessageDocsSwaggerDefinitions, SwaggerDefinitionsJSON, SwaggerJSONFactory} import code.api.util.APIUtil._ @@ -16,16 +14,13 @@ import code.api.util.ExampleValue._ import code.api.util.NewStyle.HttpCode import code.api.util._ import code.api.v1_2_1.{JSONFactory, RateLimiting} -import code.api.v1_3_0.{JSONFactory1_3_0, PostPhysicalCardJSON} -import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140 import code.api.v2_0_0.CreateMeetingJson import code.api.v2_1_0._ import code.api.v2_2_0.{CreateAccountJSONV220, JSONFactory220} import code.api.v3_0_0.JSONFactory300 -import code.api.v3_0_0.JSONFactory300.{createAdapterInfoJson, createCoreBankAccountJSON} +import code.api.v3_0_0.JSONFactory300.{createAdapterInfoJson} import code.api.v3_1_0.JSONFactory310._ import code.bankconnectors.Connector -import code.bankconnectors.akka.AkkaConnector_vDec2018 import code.bankconnectors.rest.RestConnector_vMar2019 import code.consent.{ConsentStatus, Consents} import code.consumer.Consumers @@ -33,31 +28,29 @@ import code.context.{UserAuthContextUpdateProvider, UserAuthContextUpdateStatus} import code.entitlement.Entitlement import code.kafka.KafkaHelper import code.loginattempts.LoginAttempt -import code.methodrouting.MethodRoutingCommons +import code.methodrouting.{MethodRoutingCommons, MethodRoutingParam} import code.metrics.APIMetrics import code.model._ import code.model.dataAccess.{AuthUser, BankAccountCreation} -import code.transactionrequests.TransactionRequests.TransactionRequestTypes -import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{COUNTERPARTY, FREE_FORM, SANDBOX_TAN, SEPA} import com.openbankproject.commons.model.Product import code.users.Users import code.util.Helper import code.views.Views import code.webhook.AccountWebhook -import code.webuiprops.{MappedWebUiPropsProvider, WebUiPropsCommons, WebUiPropsProvider} +import code.webuiprops.{MappedWebUiPropsProvider, WebUiPropsCommons} import com.github.dwickern.macros.NameOf.nameOf import com.nexmo.client.NexmoClient import com.nexmo.client.sms.messages.TextMessage import com.openbankproject.commons.model.{CreditLimit, _} -import net.liftweb.common.{Box, Empty, Failure, Full} +import net.liftweb.common.{Box, Empty, Full} import net.liftweb.http.provider.HTTPParam import net.liftweb.http.rest.RestHelper -import net.liftweb.json.Serialization.write + import net.liftweb.json._ import net.liftweb.util.Helpers.tryo import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To} -import net.liftweb.util.{Helpers, Mailer, Props} -import org.apache.commons.lang3.Validate +import net.liftweb.util.{Helpers, Mailer} +import org.apache.commons.lang3.{StringUtils, Validate} import scala.collection.immutable.{List, Nil} import scala.collection.mutable.ArrayBuffer @@ -3930,7 +3923,7 @@ trait APIMethods310 { emptyObjectJson, ListResult( "method_routings", - (List(MethodRoutingCommons("getBanks", "rest_vMar2019", false, Some("some_bank_.*"), Some("method-routing-id")))) + (List(MethodRoutingCommons("getBanks", "rest_vMar2019", false, Some("some_bank_.*"), Some(List(MethodRoutingParam("url", "http://mydomain.com/xxx"))), Some("method-routing-id")))) ) , List( @@ -3976,12 +3969,17 @@ trait APIMethods310 { |* connector_name is required String value |* is_bank_id_exact_match is required boolean value, if bank_id_pattern is exact bank_id value, this value is true; if bank_id_pattern is null or a regex, this value is false |* bank_id_pattern is optional String value, it can be null, a exact bank_id or a regex + |* parameters is optional array of key value pairs. You can set some paremeters for this method | - |note: if bank_id_pattern is regex, special characters need to do escape, for example: - |bank_id_pattern = "some\\-id_pattern_\\d+" + |note: + | + |* if bank_id_pattern is regex, special characters need to do escape, for example: bank_id_pattern = "some\\-id_pattern_\\d+" |""", - MethodRoutingCommons("getBank", "rest_vMar2019", false, Some("some_bankId_.*")), - MethodRoutingCommons("getBank", "rest_vMar2019", false, Some("some_bankId_.*"), Some("this-method-routing-Id")), + MethodRoutingCommons("getBank", "rest_vMar2019", false, Some("some_bankId_.*"), Some(List(MethodRoutingParam("url", "http://mydomain.com/xxx")))), + MethodRoutingCommons("getBank", "rest_vMar2019", false, Some("some_bankId_.*"), + Some(List(MethodRoutingParam("url", "http://mydomain.com/xxx"))), + Some("this-method-routing-Id") + ), List( UserNotLoggedIn, UserHasMissingRoles, @@ -4036,13 +4034,13 @@ trait APIMethods310 { |* connector_name is required String value |* is_bank_id_exact_match is required boolean value, if bank_id_pattern is exact bank_id value, this value is true; if bank_id_pattern is null or a regex, this value is false |* bank_id_pattern is optional String value, it can be null, a exact bank_id or a regex + |* parameters is optional array of key value pairs. You can set some paremeters for this method + |note: | - |note: if bank_id_pattern is regex, special characters need to do escape, for example: - |bank_id_pattern = "some\\-id_pattern_\\d+" - | + |* if bank_id_pattern is regex, special characters need to do escape, for example: bank_id_pattern = "some\\-id_pattern_\\d+" |""", - MethodRoutingCommons("getBank", "rest_vMar2019", true, Some("some_bankId"), None), - MethodRoutingCommons("getBank", "rest_vMar2019", true, Some("some_bankId"), None), + MethodRoutingCommons("getBank", "rest_vMar2019", true, Some("some_bankId"), Some(List(MethodRoutingParam("url", "http://mydomain.com/xxx")))), + MethodRoutingCommons("getBank", "rest_vMar2019", true, Some("some_bankId"),Some(List(MethodRoutingParam("url", "http://mydomain.com/xxx"))), Some("this-method-routing-Id")), List( UserNotLoggedIn, UserHasMissingRoles, @@ -4061,21 +4059,21 @@ trait APIMethods310 { _ <- NewStyle.function.hasEntitlement("", u.userId, canUpdateMethodRouting, callContext) failMsg = s"$InvalidJsonFormat The Json body should be the ${classOf[MethodRoutingCommons]} " - postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { + putData <- NewStyle.function.tryons(failMsg, 400, callContext) { json.extract[MethodRoutingCommons].copy(methodRoutingId = Some(methodRoutingId)) } (_, _) <- NewStyle.function.getMethodRoutingById(methodRoutingId, callContext) - invalidRegexMsg = s"$InvalidBankIdRegex The bankIdPattern is invalid regex, bankIdPatten: ${postedData.bankIdPattern.orNull} " + invalidRegexMsg = s"$InvalidBankIdRegex The bankIdPattern is invalid regex, bankIdPatten: ${putData.bankIdPattern.orNull} " _ <- NewStyle.function.tryons(invalidRegexMsg, 400, callContext) { // if do fuzzy match and bankIdPattern not empty, do check the regex is valid - if(!postedData.isBankIdExactMatch && postedData.bankIdPattern.isDefined) { - Pattern.compile(postedData.bankIdPattern.get) + if(!putData.isBankIdExactMatch && putData.bankIdPattern.isDefined) { + Pattern.compile(putData.bankIdPattern.get) } } - Full(methodRouting) <- NewStyle.function.createOrUpdateMethodRouting(postedData) + Full(methodRouting) <- NewStyle.function.createOrUpdateMethodRouting(putData) } yield { val commonsData: MethodRoutingCommons = methodRouting (commonsData, HttpCode.`200`(callContext)) @@ -5450,6 +5448,35 @@ trait APIMethods310 { } } + resourceDocs += ResourceDoc( + getBankAccountsBalances, + implementedInApiVersion, + nameOf(getBankAccountsBalances), + "GET", + "/banks/BANK_ID/balances", + "Get Accounts Balances", + """Get the Balances for the Accounts of the current User at one bank.""", + emptyObjectJson, + accountBalancesV310Json, + List(UnknownError), + Catalogs(Core, PSD2, OBWG), + apiTagAccount :: apiTagPSD2AIS :: apiTagNewStyle :: Nil + ) + + lazy val getBankAccountsBalances : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "balances" :: Nil JsonGet _ => { + cc => + for { + (Full(u), callContext) <- authorizedAccess(cc) + (_, callContext) <- NewStyle.function.getBank(bankId, callContext) + availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) + (accountsBalances, callContext)<- NewStyle.function.getBankAccountsBalances(availablePrivateAccounts, callContext) + } yield{ + (createBalancesJson(accountsBalances), HttpCode.`200`(callContext)) + } + } + } + } } diff --git a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala index 3debef2d8..246ba9c79 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/JSONFactory3.1.0.scala @@ -34,7 +34,7 @@ import code.api.util.APIUtil.{stringOptionOrNull, stringOrNull} import code.api.util.RateLimitPeriod.LimitCallPeriod import code.api.util.{APIUtil, RateLimitPeriod} import code.api.v1_2_1.JSONFactory.{createAmountOfMoneyJSON, createOwnersJSON} -import code.api.v1_2_1.{RateLimiting, UserJSONV121, ViewJSONV121} +import code.api.v1_2_1.{BankJSON, RateLimiting, UserJSONV121, ViewJSONV121} import code.api.v1_3_0.JSONFactory1_3_0._ import code.api.v1_3_0.{PinResetJSON, ReplacementJSON} import code.api.v1_4_0.JSONFactory1_4_0.{CustomerFaceImageJson, MetaJsonV140, TransactionRequestAccountJsonV140} @@ -42,7 +42,7 @@ import code.api.v2_0_0.{MeetingKeysJson, MeetingPresentJson} import code.api.v2_1_0.JSONFactory210.createLicenseJson import code.api.v2_1_0.{CustomerCreditRatingJSON, ResourceUserJSON} import code.api.v2_2_0._ -import code.api.v3_0_0.AccountRuleJsonV300 +import code.api.v3_0_0.{AccountRuleJsonV300, ViewBasicV300} import code.api.v3_0_0.JSONFactory300.{createAccountRoutingsJSON, createAccountRulesJSON} import code.consent.MappedConsent import code.context.UserAuthContextUpdate @@ -701,6 +701,24 @@ case class PostHistoricalTransactionResponseJson( charge_policy: String ) +case class AccountsBalancesV310Json( + accounts:List[AccountBalanceV310], + overall_balance: AmountOfMoney, + overall_balance_date: Date +) +case class AccountBalanceV310( + id: String, + label: String, + bank_id: String, + account_routings: List[AccountRouting], + balance: AmountOfMoney +) + +case class AccountBalancesJson( + accounts: List[AccountBalanceV310], + overall_balance: AmountOfMoney, + overall_balance_date: Date +) object JSONFactory310{ def createCheckbookOrdersJson(checkbookOrders: CheckbookOrdersJson): CheckbookOrdersJson = @@ -1309,5 +1327,19 @@ object JSONFactory310{ ) } + def createBalancesJson(accountsBalances: AccountsBalances) = { + AccountsBalancesV310Json( + accounts = accountsBalances.accounts.map(account => AccountBalanceV310( + account.id, + account.label, + account.bankId, + account.accountRoutings, + account.balance) + ), + overall_balance = accountsBalances.overallBalance, + overall_balance_date = accountsBalances.overallBalanceDate + ) + } + } diff --git a/obp-api/src/main/scala/code/api/v3_1_0/OBPAPI3_1_0.scala b/obp-api/src/main/scala/code/api/v3_1_0/OBPAPI3_1_0.scala index c8c2ea7ab..03f3bcbe8 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/OBPAPI3_1_0.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/OBPAPI3_1_0.scala @@ -376,6 +376,7 @@ object OBPAPI3_1_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations3_1_0.getWebUiProps :: Implementations3_1_0.createWebUiProps :: Implementations3_1_0.deleteWebUiProps :: + Implementations3_1_0.getBankAccountsBalances :: Nil val allResourceDocs = Implementations3_1_0.resourceDocs ++ diff --git a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala index 2c3278e3e..6027aeb7c 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/OBPAPI4_0_0.scala @@ -377,6 +377,7 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations3_1_0.getWebUiProps :: Implementations3_1_0.createWebUiProps :: Implementations3_1_0.deleteWebUiProps :: + Implementations3_1_0.getBankAccountsBalances :: Nil diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index eb77ce320..835d34df5 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -273,7 +273,9 @@ trait Connector extends MdcLoggable with CustomJsonFormats{ def getBankAccountByIban(iban : String, callContext: Option[CallContext]) : OBPReturnType[Box[BankAccount]]= Future{(Failure(setUnimplementedError),callContext)} def getBankAccountByRouting(scheme : String, address : String, callContext: Option[CallContext]) : Box[(BankAccount, Option[CallContext])]= Failure(setUnimplementedError) - def getBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : Future[Box[List[BankAccount]]]= Future{Failure(setUnimplementedError)} + def getBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : OBPReturnType[Box[List[BankAccount]]]= Future{(Failure(setUnimplementedError), callContext)} + + def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : OBPReturnType[Box[AccountsBalances]]= Future{(Failure(setUnimplementedError), callContext)} def getCoreBankAccountsLegacy(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : Box[(List[CoreAccount], Option[CallContext])] = Failure(setUnimplementedError) diff --git a/obp-api/src/main/scala/code/bankconnectors/ConnectorEndpoints.scala b/obp-api/src/main/scala/code/bankconnectors/ConnectorEndpoints.scala index d3104fff4..502497b01 100644 --- a/obp-api/src/main/scala/code/bankconnectors/ConnectorEndpoints.scala +++ b/obp-api/src/main/scala/code/bankconnectors/ConnectorEndpoints.scala @@ -5,7 +5,7 @@ import code.api.util.APIUtil.{OBPEndpoint, _} import code.api.util.NewStyle.HttpCode import code.api.util.{APIUtil, CallContext, OBPQueryParam} import code.api.v3_1_0.OBPAPI3_1_0.oauthServe -import com.openbankproject.commons.model.{AccountId, BankId, BankIdAccountId, InboundAdapterCallContext} +import com.openbankproject.commons.model._ import com.openbankproject.commons.util.ReflectUtils import com.openbankproject.commons.util.ReflectUtils.{getType, toValueObject} import net.liftweb.common.{Box, Empty, Failure, Full} @@ -55,7 +55,7 @@ object ConnectorEndpoints extends RestHelper{ inboundAdapterCallContextValue = InboundAdapterCallContext(cc.correlationId) } yield { // NOTE: if any filed type is BigDecimal, it is can't be serialized by lift json - val json = Map((inboundAdapterCallContextKey, inboundAdapterCallContextValue),("data", toValueObject(data))) + val json = Map((inboundAdapterCallContextKey, inboundAdapterCallContextValue),("status", Status("",List(InboundStatusMessage("","","","")))),("data", toValueObject(data))) (json, HttpCode.`200`(cc)) } } diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 541e72781..b2a201bdd 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -11,6 +11,7 @@ import code.api.cache.Caching import code.api.util.APIUtil.{OBPReturnType, isValidCurrencyISOCode, saveConnectorMetric, stringOrNull} import code.api.util.ErrorMessages._ import code.api.util._ +import code.api.v3_1_0.AccountBalanceV310 import code.atms.Atms.Atm import code.atms.MappedAtm import code.bankconnectors.vJune2017.InboundAccountJune2017 @@ -437,19 +438,61 @@ object LocalMappedConnector extends Connector with MdcLoggable { ).map(bankAccount => (bankAccount, callContext)) } - override def getBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) : Future[Box[List[BankAccount]]] = { + override def getBankAccounts(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) = { Future { - Full( + (Full( bankIdAccountIds.map( bankIdAccountId => getBankAccount( bankIdAccountId.bankId, bankIdAccountId.accountId ).openOrThrowException(s"${ErrorMessages.BankAccountNotFound} current BANK_ID(${bankIdAccountId.bankId}) and ACCOUNT_ID(${bankIdAccountId.accountId})")) - ) + ),callContext) } } + override def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) = + Future { + val accountsBalances = for{ + bankIdAccountId <- bankIdAccountIds + bankAccount <- getBankAccount(bankIdAccountId.bankId, bankIdAccountId.accountId) ?~! s"${ErrorMessages.BankAccountNotFound} current BANK_ID(${bankIdAccountId.bankId}) and ACCOUNT_ID(${bankIdAccountId.accountId})" + accountBalance = AccountBalance( + id = bankAccount.accountId.value, + label = bankAccount.label, + bankId = bankAccount.bankId.value, + accountRoutings = bankAccount.accountRoutings.map(accountRounting => AccountRouting(accountRounting.scheme, accountRounting.address)), + balance = AmountOfMoney(bankAccount.currency, bankAccount.balance.toString()) + ) + } yield { + (accountBalance) + } + + val allCurrencies = accountsBalances.map(_.balance.currency) + val mostCommonCurrency = if (allCurrencies.isEmpty) "EUR" else allCurrencies.groupBy(identity).mapValues(_.size).maxBy(_._2)._1 + + val allCommonCurrencyBalances = for { + accountBalance <- accountsBalances + requestAccountCurrency = accountBalance.balance.currency + requestAccountAmount = BigDecimal(accountBalance.balance.amount) + //From change from requestAccount Currency to mostCommon Currency + rate <- fx.exchangeRate(requestAccountCurrency, mostCommonCurrency) + requestChangedCurrencyAmount = fx.convert(requestAccountAmount, Some(rate)) + }yield { + requestChangedCurrencyAmount + } + + val overallBalance = allCommonCurrencyBalances.sum + + (Full(AccountsBalances( + accounts = accountsBalances, + overallBalance = AmountOfMoney( + mostCommonCurrency, + overallBalance.toString + ), + overallBalanceDate = now + )), callContext) + } + override def checkBankAccountExistsLegacy(bankId: BankId, accountId: AccountId, callContext: Option[CallContext]) = { getBankAccountLegacy(bankId: BankId, accountId: AccountId, callContext) } diff --git a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnectorBuilder.scala b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnectorBuilder.scala index cee6d76e6..5e8e0b2b7 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnectorBuilder.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnectorBuilder.scala @@ -17,24 +17,22 @@ import code.api.util.CodeGenerateUtils.createDocExample object RestConnectorBuilder extends App { - val genMethodNames1 = List( + val genMethodNames = List( // "getAdapterInfo", - "getAdapterInfo", +// "getAdapterInfo", // "getUser", // have problem, return type not common - // "getBanks", - "getBanksFuture", - // "getBank", - "getBankFuture", - // "getBankAccountsForUser", - "getBankAccountsForUserFuture", - "getCustomersByUserIdFuture", - // "getBankAccount", - // "checkBankAccountExists", - "checkBankAccountExistsFuture", + "getBankAccountsBalances", +// "getBanksLegacy", +// "getBank", +// "getBankLegacy", +// "getBankAccountsForUser", +// "getCustomersByUserIdFuture", +// "getBankAccount", +// "checkBankAccountExists", // "getCoreBankAccounts", - "getCoreBankAccountsFuture", +// "getCoreBankAccountsFuture", // "getTransactions", - "getTransactionsCore", +// "getTransactionsCore", // "getTransaction", // "getTransactionRequests210", //have problem params are not simple object // "getCounterparties", @@ -54,7 +52,7 @@ object RestConnectorBuilder extends App { // "createCounterparty" // not support ) //For vSept2018 - val genMethodNames = List( + val genMethodNames2 = List( // "createOrUpdateKycCheck", // "createOrUpdateKycDocument", // "createOrUpdateKycMedia", @@ -64,7 +62,7 @@ object RestConnectorBuilder extends App { // "getKycMedias", // "getKycStatuses", // "createBankAccount", - "createCustomer", +// "createCustomer", // "createMeeting", // "createMessage" ) diff --git a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala index 554c2162d..e8e180986 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala @@ -28,14 +28,13 @@ import java.util.UUID.randomUUID import java.util.Date import akka.http.scaladsl.model.{HttpProtocol, _} +import akka.http.scaladsl.model.headers.RawHeader import akka.util.ByteString import code.api.APIFailureNewStyle -import code.api.ResourceDocs1_4_0.MessageDocsSwaggerDefinitions.inboundStatus import code.api.cache.Caching import code.api.util.APIUtil.{AdapterImplementation, MessageDoc, OBPReturnType, saveConnectorMetric} import code.api.util.ErrorMessages._ -import code.api.util.ExampleValue._ -import code.api.util.{CallContext, OBPQueryParam} +import code.api.util.{CallContext, NewStyle, OBPQueryParam} import code.bankconnectors._ import code.bankconnectors.vJune2017.AuthInfo import code.kafka.{KafkaHelper, Topics} @@ -43,9 +42,8 @@ import code.util.AkkaHttpClient._ import code.util.Helper.MdcLoggable import com.openbankproject.commons.dto._ import com.openbankproject.commons.model._ -import com.tesobe.CacheKeyFromArguments +import com.tesobe.{CacheKeyFromArguments, CacheKeyOmit} import net.liftweb.common.{Box, Empty, _} -import net.liftweb.json._ import net.liftweb.util.Helpers.tryo import scala.collection.immutable.{List, Nil} @@ -54,10 +52,11 @@ import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import scala.language.postfixOps import scala.reflect.runtime.universe._ - import code.api.util.ExampleValue._ - import code.api.util.APIUtil._ +import code.methodrouting.MethodRoutingParam +import org.apache.commons.lang3.StringUtils +import net.liftweb.json._ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable { //this one import is for implicit convert, don't delete @@ -79,6 +78,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable val authInfoExample = AuthInfo(userId = "userId", username = "username", cbsToken = "cbsToken") val errorCodeExample = "INTERNAL-OBP-ADAPTER-6001: ..." + val connectorName = "rest_vMar2019" /* All the following code is created automatclly. @@ -166,16 +166,16 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable //---------------- dynamic start -------------------please don't modify this line -// ---------- create on Thu Jun 13 11:07:28 CST 2019 +// ---------- create on Tue Jul 23 18:38:46 CEST 2019 messageDocs += MessageDoc( - process = "obp.createCustomer", + process = "obp.getBankAccountsBalances", messageFormat = messageFormat, - description = "Create Customer", - outboundTopic = Some(Topics.createTopicByClassName(OutBoundCreateCustomer.getClass.getSimpleName).request), - inboundTopic = Some(Topics.createTopicByClassName(OutBoundCreateCustomer.getClass.getSimpleName).response), + description = "Get Bank Accounts Balances", + outboundTopic = None, + inboundTopic = None, exampleOutboundMessage = ( - OutBoundCreateCustomer(outboundAdapterCallContext= OutboundAdapterCallContext(correlationId=correlationIdExample.value, + OutBoundGetBankAccountsBalances(outboundAdapterCallContext= OutboundAdapterCallContext(correlationId=correlationIdExample.value, sessionId=Some(sessionIdExample.value), consumerId=Some(consumerIdExample.value), generalContext=Some(List( BasicGeneralContext(key=keyExample.value, @@ -201,30 +201,11 @@ messageDocs += MessageDoc( userOwners=List( InternalBasicUser(userId=userIdExample.value, emailAddress=emailExample.value, name=usernameExample.value))))))))), - bankId=BankId(bankIdExample.value), - legalName=legalNameExample.value, - mobileNumber=mobileNumberExample.value, - email=emailExample.value, - faceImage= CustomerFaceImage(date=parseDate(customerFaceImageDateExample.value).getOrElse(sys.error("customerFaceImageDateExample.value is not validate date format.")), - url=urlExample.value), - dateOfBirth=parseDate(dateOfBirthExample.value).getOrElse(sys.error("dateOfBirthExample.value is not validate date format.")), - relationshipStatus=relationshipStatusExample.value, - dependents=dependentsExample.value.toInt, - dobOfDependents=dobOfDependentsExample.value.split("[,;]").map(parseDate).flatMap(_.toSeq).toList, - highestEducationAttained=highestEducationAttainedExample.value, - employmentStatus=employmentStatusExample.value, - kycStatus=kycStatusExample.value.toBoolean, - lastOkDate=parseDate(outBoundCreateCustomerLastOkDateExample.value).getOrElse(sys.error("outBoundCreateCustomerLastOkDateExample.value is not validate date format.")), - creditRating=Some( CreditRating(rating=ratingExample.value, - source=sourceExample.value)), - creditLimit=Some( AmountOfMoney(currency=currencyExample.value, - amount=creditLimitAmountExample.value)), - title=titleExample.value, - branchId=branchIdExample.value, - nameSuffix=nameSuffixExample.value) + bankIdAccountIds=List( BankIdAccountId(bankId=BankId(bankIdExample.value), + accountId=AccountId(accountIdExample.value)))) ), exampleInboundMessage = ( - InBoundCreateCustomer(inboundAdapterCallContext= InboundAdapterCallContext(correlationId=correlationIdExample.value, + InBoundGetBankAccountsBalances(inboundAdapterCallContext= InboundAdapterCallContext(correlationId=correlationIdExample.value, sessionId=Some(sessionIdExample.value), generalContext=Some(List( BasicGeneralContext(key=keyExample.value, value=valueExample.value)))), @@ -233,48 +214,42 @@ messageDocs += MessageDoc( status=inboundStatusMessageStatusExample.value, errorCode=inboundStatusMessageErrorCodeExample.value, text=inboundStatusMessageTextExample.value))), - data= CustomerCommons(customerId=customerIdExample.value, + data= AccountsBalances(accounts=List( AccountBalance(id=accountIdExample.value, + label=labelExample.value, bankId=bankIdExample.value, - number=customerNumberExample.value, - legalName=legalNameExample.value, - mobileNumber=mobileNumberExample.value, - email=emailExample.value, - faceImage= CustomerFaceImage(date=parseDate(customerFaceImageDateExample.value).getOrElse(sys.error("customerFaceImageDateExample.value is not validate date format.")), - url=urlExample.value), - dateOfBirth=parseDate(dateOfBirthExample.value).getOrElse(sys.error("dateOfBirthExample.value is not validate date format.")), - relationshipStatus=relationshipStatusExample.value, - dependents=dependentsExample.value.toInt, - dobOfDependents=dobOfDependentsExample.value.split("[,;]").map(parseDate).flatMap(_.toSeq).toList, - highestEducationAttained=highestEducationAttainedExample.value, - employmentStatus=employmentStatusExample.value, - creditRating= CreditRating(rating=ratingExample.value, - source=sourceExample.value), - creditLimit= CreditLimit(currency=currencyExample.value, - amount=creditLimitAmountExample.value), - kycStatus=kycStatusExample.value.toBoolean, - lastOkDate=parseDate(customerLastOkDateExample.value).getOrElse(sys.error("customerLastOkDateExample.value is not validate date format.")), - title=customerTitleExample.value, - branchId=branchIdExample.value, - nameSuffix=nameSuffixExample.value)) + accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value, + address=accountRoutingAddressExample.value)), + balance= AmountOfMoney(currency=balanceCurrencyExample.value, + amount=balanceAmountExample.value))), + overallBalance= AmountOfMoney(currency=currencyExample.value, + amount="string"), + overallBalanceDate=new Date())) ), adapterImplementation = Some(AdapterImplementation("- Core", 1)) ) - // url example: /createCustomer - override def createCustomer(bankId: BankId, legalName: String, mobileNumber: String, email: String, faceImage: CustomerFaceImageTrait, dateOfBirth: Date, relationshipStatus: String, dependents: Int, dobOfDependents: List[Date], highestEducationAttained: String, employmentStatus: String, kycStatus: Boolean, lastOkDate: Date, creditRating: Option[CreditRatingTrait], creditLimit: Option[AmountOfMoneyTrait], title: String, branchId: String, nameSuffix: String, callContext: Option[CallContext]): OBPReturnType[Box[Customer]] = { - import net.liftweb.json.Serialization.write - - val url = getUrl("createCustomer") - val outboundAdapterCallContext = Box(callContext.map(_.toOutboundAdapterCallContext)).openOrThrowException(NoCallContext) - val jsonStr = write(OutBoundCreateCustomer(outboundAdapterCallContext , bankId, legalName, mobileNumber, email, faceImage, dateOfBirth, relationshipStatus, dependents, dobOfDependents, highestEducationAttained, employmentStatus, kycStatus, lastOkDate, creditRating, creditLimit, title, branchId, nameSuffix)) - sendPostRequest[InBoundCreateCustomer](url, callContext, jsonStr) - .map{ boxedResult => - boxedResult match { - case Full(result) => (Full(result.data), buildCallContext(result.inboundAdapterCallContext, callContext)) - case result: EmptyBox => (result, callContext) // Empty and Failure all match this case - } + // url example: /getBankAccountsBalances/bankIdAccountIds/{bankIdAccountIds} + override def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], @CacheKeyOmit callContext: Option[CallContext]): OBPReturnType[Box[AccountsBalances]] = saveConnectorMetric { + /** + * Please note that "var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)" + * is just a temporary value filed with UUID values in order to prevent any ambiguity. + * The real value will be assigned by Macro during compile time at this line of a code: + * https://github.com/OpenBankProject/scala-macros/blob/master/macros/src/main/scala/com/tesobe/CacheKeyFromArgumentsMacro.scala#L49 + */ + var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString) + CacheKeyFromArguments.buildCacheKey { + Caching.memoizeWithProvider(Some(cacheKey.toString()))(banksTTL second){ + val url = getUrl("getBankAccountsBalances" , ("bankIdAccountIds", bankIdAccountIds)) + sendGetRequest[InBoundGetBankAccountsBalances](url, callContext) + .map { boxedResult => + boxedResult match { + case Full(result) => (Full(result.data), buildCallContext(result.inboundAdapterCallContext, callContext)) + case result: EmptyBox => (result, callContext) // Empty and Failure all match this case + } + } + } } - } + }("getBankAccountsBalances") //---------------- dynamic end ---------------------please don't modify this line @@ -282,6 +257,7 @@ messageDocs += MessageDoc( + private[this] def sendGetRequest[T: TypeTag : Manifest](url: String, callContext: Option[CallContext]) = @@ -296,8 +272,12 @@ messageDocs += MessageDoc( private[this] def sendDelteRequest[T: TypeTag : Manifest](url: String, callContext: Option[CallContext]) = sendRequest[T](url, callContext, HttpMethods.DELETE) - //TODO every connector should implement this method to build authorization headers with callContext - private[this] implicit def buildHeaders(callContext: Option[CallContext]): List[HttpHeader] = Nil + //In RestConnector, we use the headers to propagate the parameters to Adapter. The parameters come from the CallContext.outboundAdapterAuthInfo.userAuthContext + //We can set them from UserOauthContext or the http request headers. + private[this] implicit def buildHeaders(callContext: Option[CallContext]): List[HttpHeader] = { + val generalContext = callContext.flatMap(_.toOutboundAdapterCallContext.generalContext).getOrElse(List.empty[BasicGeneralContext]) + generalContext.map(generalContext => RawHeader(generalContext.key,generalContext.value)) + } private[this] def buildAdapterCallContext(callContext: Option[CallContext]): OutboundAdapterCallContext = callContext.map(_.toOutboundAdapterCallContext).orNull @@ -314,6 +294,17 @@ messageDocs += MessageDoc( private[this] val baseUrl = "http://localhost:8080/restConnector" private[this] def getUrl(methodName: String, variables: (String, Any)*): String = { + // rest connector can have url value in the parameters, key is url + val urlInMethodRouting = NewStyle.function.getMethodRoutings(Some(methodName)) + .flatMap(_.parameters).flatten + .find(_.key == "url") + .map(_.value) + + + if(urlInMethodRouting.isDefined) { + return urlInMethodRouting.get + } + // convert any type value to string, to fill in the url def urlValueConverter(obj: Any):String = { val value = obj match { @@ -341,6 +332,7 @@ messageDocs += MessageDoc( private[this] def sendRequest[T: TypeTag : Manifest](url: String, callContext: Option[CallContext], method: HttpMethod, entityJsonString: String = ""): Future[Box[T]] = { val request = prepareHttpRequest(url, method, HttpProtocol("HTTP/1.1"), entityJsonString).withHeaders(callContext) + logger.debug(s"RestConnector_vMar2019 request is : $request") val responseFuture = makeHttpRequest(request) val jsonType = typeOf[T] responseFuture.map { diff --git a/obp-api/src/main/scala/code/methodrouting/MappedMethodRoutingProvider.scala b/obp-api/src/main/scala/code/methodrouting/MappedMethodRoutingProvider.scala index a475477ee..102846182 100644 --- a/obp-api/src/main/scala/code/methodrouting/MappedMethodRoutingProvider.scala +++ b/obp-api/src/main/scala/code/methodrouting/MappedMethodRoutingProvider.scala @@ -1,12 +1,15 @@ package code.methodrouting +import code.api.util.CustomJsonFormats import code.util.MappedUUID import net.liftweb.common.{Box, Empty, EmptyBox, Full} +import net.liftweb.json import net.liftweb.mapper._ import net.liftweb.util.Helpers.tryo import org.apache.commons.lang3.StringUtils +import net.liftweb.json.Serialization.write -object MappedMethodRoutingProvider extends MethodRoutingProvider { +object MappedMethodRoutingProvider extends MethodRoutingProvider with CustomJsonFormats{ override def getById(methodRoutingId: String): Box[MethodRoutingT] = MethodRouting.find( By(MethodRouting.MethodRoutingId, methodRoutingId) @@ -42,12 +45,18 @@ object MappedMethodRoutingProvider extends MethodRoutingProvider { // if not supply bankIdPattern, isExactMatch must be false val isExactMatch = if(bankIdPattern.isDefined) methodRouting.isBankIdExactMatch else false + val existsMethodRoutingParameters = methodRouting.parameters match { + case Some(parameters) if (parameters.nonEmpty) => parameters + case _ => List.empty[MethodRoutingParam] + } + tryo{ entityToPersist .MethodName(methodRouting.methodName) .BankIdPattern(bankIdPattern.orNull) .IsBankIdExactMatch(isExactMatch) .ConnectorName(methodRouting.connectorName) + .Parameters(write(existsMethodRoutingParameters)) .saveMe() } } @@ -59,7 +68,7 @@ object MappedMethodRoutingProvider extends MethodRoutingProvider { } -class MethodRouting extends MethodRoutingT with LongKeyedMapper[MethodRouting] with IdPK { +class MethodRouting extends MethodRoutingT with LongKeyedMapper[MethodRouting] with IdPK with CustomJsonFormats{ override def getSingleton = MethodRouting @@ -70,12 +79,16 @@ class MethodRouting extends MethodRoutingT with LongKeyedMapper[MethodRouting] w } object IsBankIdExactMatch extends MappedBoolean(this) object ConnectorName extends MappedString(this, 255) + object Parameters extends MappedString(this, 5000) override def methodRoutingId: Option[String] = Option(MethodRoutingId.get) override def methodName: String = MethodName.get override def bankIdPattern: Option[String] = Option(BankIdPattern.get) override def isBankIdExactMatch: Boolean = IsBankIdExactMatch.get override def connectorName: String = ConnectorName.get + + //Here we store all the key-value paris in one big String filed in database. + override def parameters: Option[List[MethodRoutingParam]] = Option(json.parse(if (Parameters.get != null) Parameters.get else "[]").extract[List[MethodRoutingParam]]) } object MethodRouting extends MethodRouting with LongKeyedMetaMapper[MethodRouting] { diff --git a/obp-api/src/main/scala/code/methodrouting/MethodRoutingProvider.scala b/obp-api/src/main/scala/code/methodrouting/MethodRoutingProvider.scala index 4c8ff4e18..c9dacef44 100644 --- a/obp-api/src/main/scala/code/methodrouting/MethodRoutingProvider.scala +++ b/obp-api/src/main/scala/code/methodrouting/MethodRoutingProvider.scala @@ -24,17 +24,21 @@ trait MethodRoutingT { */ def isBankIdExactMatch: Boolean def connectorName: String + def parameters: Option[List[MethodRoutingParam]] } case class MethodRoutingCommons(methodName: String, connectorName: String, isBankIdExactMatch: Boolean, bankIdPattern: Option[String], - methodRoutingId: Option[String] = None + parameters: Option[List[MethodRoutingParam]] = None, + methodRoutingId: Option[String] = None, ) extends MethodRoutingT with JsonFieldReName object MethodRoutingCommons extends Converter[MethodRoutingT, MethodRoutingCommons] +case class MethodRoutingParam(key: String, value: String) + trait MethodRoutingProvider { def getById(methodRoutingId: String): Box[MethodRoutingT] diff --git a/obp-api/src/main/scala/code/model/dataAccess/MappedBankAccount.scala b/obp-api/src/main/scala/code/model/dataAccess/MappedBankAccount.scala index e41247f21..a2a8139d7 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/MappedBankAccount.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/MappedBankAccount.scala @@ -31,7 +31,8 @@ class MappedBankAccount extends BankAccount with LongKeyedMapper[MappedBankAccou object accountLabel extends MappedString(this, 255) - //the last time this account was updated via hbci + //the last time this account was updated via hbci [when transaction data was refreshed from the bank.] + //It means last transaction refresh date only used for HBCI now. object accountLastUpdate extends MappedDateTime(this) object mAccountRoutingScheme extends MappedString(this, 32) diff --git a/obp-api/src/main/scala/code/webuiprops/MappedWebUiPropsProvider.scala b/obp-api/src/main/scala/code/webuiprops/MappedWebUiPropsProvider.scala index 47d9224e4..8d9a86aa7 100644 --- a/obp-api/src/main/scala/code/webuiprops/MappedWebUiPropsProvider.scala +++ b/obp-api/src/main/scala/code/webuiprops/MappedWebUiPropsProvider.scala @@ -15,7 +15,7 @@ import net.liftweb.mapper._ */ object MappedWebUiPropsProvider extends WebUiPropsProvider { // default webUiProps value cached seconds - private val webUiPropsTTL = APIUtil.getPropsAsIntValue("webui.props.cache.ttl.seconds", 20) + private val webUiPropsTTL = APIUtil.getPropsAsIntValue("webui.props.cache.ttl.seconds", 0) override def getAll(): List[WebUiPropsT] = WebUiProps.findAll() diff --git a/obp-api/src/main/scripts/migrate/migrate_00000018.sql b/obp-api/src/main/scripts/migrate/migrate_00000018.sql new file mode 100644 index 000000000..6912215c9 --- /dev/null +++ b/obp-api/src/main/scripts/migrate/migrate_00000018.sql @@ -0,0 +1 @@ +ALTER TABLE "methodrouting" ADD "parameters" varchar(5000) NULL; diff --git a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala new file mode 100644 index 000000000..8f62d51a8 --- /dev/null +++ b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala @@ -0,0 +1,277 @@ +package code.api.v3_1_0 + +import net.liftweb.json.JValue +import org.scalatest.Tag +import io.swagger.parser.OpenAPIParser +import java.util +import code.api.ResourceDocs1_4_0.ResourceDocsV140ServerSetup +import code.api.util.{ApiVersion, CustomJsonFormats} +import code.api.v1_4_0.JSONFactory1_4_0.ImplementedByJson +import net.liftweb.json + +import scala.collection.immutable.List +class ResourceDocsTest extends ResourceDocsV140ServerSetup with CustomJsonFormats{ + + object VersionOfApi extends Tag(ApiVersion.v1_4_0.toString) + object ApiEndpoint1 extends Tag("Get Swagger ResourceDoc") + object ApiEndpoint2 extends Tag("Get OBP ResourceDoc ") + + + feature(s"test ${ApiEndpoint1.name} ") { + + case class RoleJson ( + role: String, + requires_bank_id: Boolean + ) + + //This case class is for API_Explorer, it should make sure api_explorer can get proper doc. + case class ResourceDocJson(operation_id: String, + request_verb: String, + request_url: String, + summary: String, // Summary of call should be 120 characters max + description: String, // Description of call in markdown + example_request_body: JValue, // An example request body + success_response_body: JValue, // Success response body + error_response_bodies: List[String], + implemented_by: ImplementedByJson, + is_core : Boolean, + is_psd2 : Boolean, + is_obwg : Boolean, // This may be tracking isCore + tags : List[String], + roles: List[RoleJson], + is_featured: Boolean, + special_instructions: String, + specified_url: String // This is the URL that we want people to call. + ) + + case class ResourceDocsJson (resource_docs : List[ResourceDocJson]) + + + scenario(s"We will test ${ApiEndpoint1.name} Api -v4.0.0", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v4.0.0" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + + scenario(s"We will test ${ApiEndpoint1.name} Api -v3.1.0", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v3.1.0" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + + scenario(s"We will test ${ApiEndpoint1.name} Api -v3.0.0", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v3.0.0" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + + scenario(s"We will test ${ApiEndpoint1.name} Api -v2.2.0", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.2.0" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + + scenario(s"We will test ${ApiEndpoint1.name} Api -v2.1.0", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.1.0" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + + scenario(s"We will test ${ApiEndpoint1.name} Api -v2.0.0", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.0.0" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + + scenario(s"We will test ${ApiEndpoint1.name} Api -v1.4.0", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.4.0" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + + scenario(s"We will test ${ApiEndpoint1.name} Api -v1.3.0", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.3.0" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + + scenario(s"We will test ${ApiEndpoint1.name} Api -v1.2.1", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.2.1" / "obp").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + responseGetObp.body.extract[ResourceDocsJson] + } + } + + feature(s"test ${ApiEndpoint2.name} ") { + scenario(s"We will test ${ApiEndpoint2.name} Api - v4.0.0", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v4.0.0" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + if (!errors.isEmpty) logger.info(s"Here is the wrong swagger json: $swaggerJsonString") + errors.isEmpty should be (true) + } + + scenario(s"We will test ${ApiEndpoint2.name} Api - v3.1.1", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v3.1.0" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + errors.isEmpty should be (true) + } + + scenario(s"We will test ${ApiEndpoint2.name} Api - v3.0.0", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v3.0.0" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + errors.isEmpty should be (true) + } + + scenario(s"We will test ${ApiEndpoint2.name} Api - v2.2.0", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.2.0" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + errors.isEmpty should be (true) + } + + scenario(s"We will test ${ApiEndpoint2.name} Api - v2.1.0", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.1.0" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + errors.isEmpty should be (true) + } + + scenario(s"We will test ${ApiEndpoint2.name} Api - v2.0.0", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.0.0" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + errors.isEmpty should be (true) + } + + scenario(s"We will test ${ApiEndpoint2.name} Api - v1.4.0", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.4.0" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + errors.isEmpty should be (true) + } + + scenario(s"We will test ${ApiEndpoint2.name} Api - v1.3.0", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.3.0" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + errors.isEmpty should be (true) + } + + scenario(s"We will test ${ApiEndpoint2.name} Api - v1.2.1", ApiEndpoint2, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.2.1" / "swagger").GET + val responseGetObp = makeGetRequest(requestGetObp) + And("We should get 200 and the response can be extract to case classes") + responseGetObp.code should equal(200) + val swaggerJsonString = json.compactRender(responseGetObp.body) + + val validatedSwaggerResult = ValidateSwaggerString(swaggerJsonString) + val errors = validatedSwaggerResult._1 + errors.isEmpty should be (true) + } + + } + + + + + //Note: it is tricky to validate the swagger string, I just find this : https://github.com/swagger-api/swagger-parser/issues/718 + //So follow it to call the `Validate` method: + //https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Validate.java#L46 + def ValidateSwaggerString (swaggerJsonString: String)= { + val result = new OpenAPIParser().readContents(swaggerJsonString, null, null) + val messageList: util.List[String] = result.getMessages() + + val errors = new util.HashSet[String](messageList) + val warnings = new util.HashSet[String] + + val sb = new StringBuilder + + if (!errors.isEmpty) { + sb.append("Errors:").append(System.lineSeparator) + errors.forEach((msg: String) => sb.append("\t-").append(msg).append(System.lineSeparator)) + } + + if (!warnings.isEmpty) { + sb.append("Warnings: ").append(System.lineSeparator) + warnings.forEach((msg: String) => sb.append("\t-").append(msg).append(System.lineSeparator)) + } + + if (!errors.isEmpty) { + sb.append(System.lineSeparator) + sb.append("[error] Spec has ").append(errors.size).append(" errors.") + System.err.println(sb.toString) + System.exit(1) + } + else if (!warnings.isEmpty) { + sb.append(System.lineSeparator) + sb.append("[info] Spec has ").append(warnings.size).append(" recommendation(s).") + } + else { // we say "issues" here rather than "errors" to account for both errors and issues. + sb.append("No validation issues detected.") + } + val allMessages = sb.toString + logger.info(s"validatedSwaggerResult.errors $errors") + logger.info(s"validatedSwaggerResult.warnings $warnings") + logger.info(s"validatedSwaggerResult.allMessages $allMessages") + + (errors, warnings, allMessages) + } +} diff --git a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsV140ServerSetup.scala b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsV140ServerSetup.scala new file mode 100644 index 000000000..8757152ae --- /dev/null +++ b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsV140ServerSetup.scala @@ -0,0 +1,15 @@ +package code.api.ResourceDocs1_4_0 + +import code.setup.ServerSetupWithTestData + +trait ResourceDocsV140ServerSetup extends ServerSetupWithTestData { + + def ResourceDocsV1_4Request = baseRequest / "obp" / "v1.4.0" + def ResourceDocsV2_0Request = baseRequest / "obp" / "v2.0.0" + def ResourceDocsV2_1Request = baseRequest / "obp" / "v2.1.0" + def ResourceDocsV2_2Request = baseRequest / "obp" / "v2.2.0" + def ResourceDocsV3_0Request = baseRequest / "obp" / "v3.0.0" + def ResourceDocsV3_1Request = baseRequest / "obp" / "v3.1.0" + def ResourceDocsV4_0Request = baseRequest / "obp" / "v4.0.0" + +} diff --git a/obp-api/src/test/scala/code/api/v3_1_0/AccountTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/AccountTest.scala index d05b03c8c..41093c5da 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/AccountTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/AccountTest.scala @@ -20,6 +20,7 @@ class AccountTest extends V310ServerSetup with DefaultUsers { object VersionOfApi extends Tag(ApiVersion.v3_1_0.toString) object ApiEndpoint1 extends Tag(nameOf(Implementations3_1_0.updateAccount)) object ApiEndpoint2 extends Tag(nameOf(Implementations3_1_0.createAccount)) + object ApiEndpoint3 extends Tag(nameOf(Implementations3_1_0.getBankAccountsBalances)) lazy val testBankId = testBankId1 lazy val putCreateAccountJSONV310 = SwaggerDefinitionsJSON.createAccountJSONV220.copy(user_id = resourceUser1.userId) @@ -119,4 +120,19 @@ class AccountTest extends V310ServerSetup with DefaultUsers { } + feature(s"test ${ApiEndpoint3.name}") { + scenario("We will test ${ApiEndpoint3.name}", ApiEndpoint3, VersionOfApi) { + Given("The test bank and test accounts") + val requestGet = (v3_1_0_Request / "banks" / testBankId.value / "balances").GET <@ (user1) + + val responseGet = makeGetRequest(requestGet) + responseGet.code should equal(200) + responseGet.body.extract[AccountsBalancesV310Json].accounts.size > 0 should be (true) + responseGet.body.extract[AccountsBalancesV310Json].overall_balance.currency.nonEmpty should be (true) + responseGet.body.extract[AccountsBalancesV310Json].overall_balance.amount.nonEmpty should be (true) + responseGet.body.extract[AccountsBalancesV310Json].overall_balance_date.getTime >0 should be (true) + + } + } + } diff --git a/obp-api/src/test/scala/code/api/v3_1_0/MethodRoutingTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/MethodRoutingTest.scala index 7a2d8112e..678a7aea1 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/MethodRoutingTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/MethodRoutingTest.scala @@ -32,11 +32,13 @@ import code.api.util.ApiVersion import code.api.util.ErrorMessages._ import code.api.v3_1_0.OBPAPI3_1_0.Implementations3_1_0 import code.entitlement.Entitlement -import code.methodrouting.MethodRoutingCommons +import code.methodrouting.{MethodRoutingCommons, MethodRoutingParam} import com.github.dwickern.macros.NameOf.nameOf import net.liftweb.json.Serialization.write import org.scalatest.Tag +import scala.collection.immutable.List + class MethodRoutingTest extends V310ServerSetup { /** @@ -52,7 +54,7 @@ class MethodRoutingTest extends V310ServerSetup { object ApiEndpoint3 extends Tag(nameOf(Implementations3_1_0.getMethodRoutings)) object ApiEndpoint4 extends Tag(nameOf(Implementations3_1_0.deleteMethodRouting)) - val rightEntity = MethodRoutingCommons("getBank", "rest_vMar2019", false, Some("some_bankId_.*")) + val rightEntity = MethodRoutingCommons("getBank", "rest_vMar2019", false, Some("some_bankId_.*"), Some(List(MethodRoutingParam("url", "http://mydomain.com/xxx")))) val wrongEntity = MethodRoutingCommons("getBank", "rest_vMar2019", false, Some("some_bankId_([")) // wrong regex 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 c85cf4392..17b6ec4fe 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 @@ -84,6 +84,10 @@ case class OutBoundGetCoreBankAccounts(outboundAdapterCallContext: OutboundAdapt bankIdAccountIds: List[BankIdAccountId]) extends TopicTrait case class InBoundGetCoreBankAccounts(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: List[CoreAccount]) extends InBoundTrait[List[CoreAccount]] +case class OutBoundGetBankAccountsBalances(outboundAdapterCallContext: OutboundAdapterCallContext, + bankIdAccountIds: List[BankIdAccountId]) extends TopicTrait + +case class InBoundGetBankAccountsBalances(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: AccountsBalances) extends InBoundTrait[AccountsBalances] case class OutBoundGetCoreBankAccountsHeld(outboundAdapterCallContext: OutboundAdapterCallContext, bankIdAccountIds: List[BankIdAccountId]) extends TopicTrait diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala index 647d6e89a..d8cac21fc 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala @@ -184,6 +184,7 @@ trait BankAccount{ def iban : Option[String] def number : String def bankId : BankId + //It means last transaction refresh date only used for HBCI now. def lastUpdate : Date def branchId: String def accountRoutingScheme: String @@ -298,6 +299,20 @@ case class CoreAccount( accountRoutings: List[AccountRouting] ) +case class AccountBalance( + id: String, + label: String, + bankId: String, + accountRoutings: List[AccountRouting], + balance: AmountOfMoney +) + +case class AccountsBalances( + accounts: List[AccountBalance], + overallBalance: AmountOfMoney, + overallBalanceDate: Date +) + case class AccountHeld( id: String, bankId: String,