diff --git a/src/main/scala/code/accountholder/AccountHolders.scala b/src/main/scala/code/accountholder/AccountHolders.scala index 0011dfeaa..9a1fffe4c 100644 --- a/src/main/scala/code/accountholder/AccountHolders.scala +++ b/src/main/scala/code/accountholder/AccountHolders.scala @@ -23,6 +23,7 @@ object AccountHolders extends SimpleInjector { trait AccountHolders { def getAccountHolders(bankId: BankId, accountId: AccountId): Set[User] + def getAccountsHeld(bankId: BankId, user: User): Set[BankIdAccountId] def createAccountHolder(userId: Long, bankId: String, accountId: String): Boolean def getOrCreateAccountHolder(user: User, bankAccountUID :BankIdAccountId): Box[MapperAccountHolders] //There is no AccountHolder trait, database structure different with view def bulkDeleteAllAccountHolders(): Box[Boolean] @@ -31,6 +32,7 @@ trait AccountHolders { class RemotedataAccountHoldersCaseClasses { case class createAccountHolder(userId: Long, bankId: String, accountId: String) case class getAccountHolders(bankId: BankId, accountId: AccountId) + case class getAccountsHeld(bankId: BankId, user: User) case class getOrCreateAccountHolder(user: User, bankAccountUID :BankIdAccountId) case class bulkDeleteAllAccountHolders() } diff --git a/src/main/scala/code/accountholder/MapperAccountHolders.scala b/src/main/scala/code/accountholder/MapperAccountHolders.scala index 36b4e8cdd..9b2093578 100644 --- a/src/main/scala/code/accountholder/MapperAccountHolders.scala +++ b/src/main/scala/code/accountholder/MapperAccountHolders.scala @@ -88,6 +88,18 @@ object MapperAccountHolders extends MapperAccountHolders with AccountHolders wit ResourceUser.find(By(ResourceUser.id, accHolder.user.get)) }.toSet } + + def getAccountsHeld(bankId: BankId, user: User): Set[BankIdAccountId] = { + val accountHolders = MapperAccountHolders.findAll( + By(MapperAccountHolders.accountBankPermalink, bankId.value), + By(MapperAccountHolders.user, user.asInstanceOf[ResourceUser]) + ) + + //accountHolders --> BankIdAccountIds + accountHolders.map { accHolder => + BankIdAccountId(bankId,AccountId(accHolder.accountPermalink.get)) + }.toSet + } def bulkDeleteAllAccountHolders(): Box[Boolean] = { Full( MapperAccountHolders.bulkDelete_!!() ) diff --git a/src/main/scala/code/api/v1_2_1/APIMethods121.scala b/src/main/scala/code/api/v1_2_1/APIMethods121.scala index 91c823577..d0649a1dd 100644 --- a/src/main/scala/code/api/v1_2_1/APIMethods121.scala +++ b/src/main/scala/code/api/v1_2_1/APIMethods121.scala @@ -296,7 +296,7 @@ trait APIMethods121 { "getPrivateAccountsAtOneBank", "GET", "/banks/BANK_ID/accounts", - "Get accounts at bank (Private, inc views).", + "Get accounts at bank (Private).", s"""Returns the list of accounts at BANK_ID that the user has access to. |For each account the API returns the account ID and the available views. | diff --git a/src/main/scala/code/api/v2_0_0/APIMethods200.scala b/src/main/scala/code/api/v2_0_0/APIMethods200.scala index 5a6f701ad..ed3f20986 100644 --- a/src/main/scala/code/api/v2_0_0/APIMethods200.scala +++ b/src/main/scala/code/api/v2_0_0/APIMethods200.scala @@ -248,7 +248,7 @@ trait APIMethods200 { "getPrivateAccountsAtOneBank", "GET", "/banks/BANK_ID/accounts", - "Get Accounts at Bank (Private, inc views).", + "Get Accounts at Bank (Private).", s"""Get accounts at one bank that the user has access to. |Returns the list of accounts at BANK_ID that the user has access to. |For each account the API returns the account ID and the available views. diff --git a/src/main/scala/code/api/v3_0_0/APIMethods300.scala b/src/main/scala/code/api/v3_0_0/APIMethods300.scala index 98b192370..bb6e81444 100644 --- a/src/main/scala/code/api/v3_0_0/APIMethods300.scala +++ b/src/main/scala/code/api/v3_0_0/APIMethods300.scala @@ -1,5 +1,6 @@ package code.api.v3_0_0 +import code.accountholder.AccountHolders import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil.{canGetAtm, _} @@ -1800,9 +1801,43 @@ trait APIMethods300 { Full(successJsonResponse(Extraction.decompose(json))) } } - - - + + resourceDocs += ResourceDoc( + getAccountsHeld, + implementedInApiVersion, + "getAccountsHeld", + "GET", + "/banks/BANK_ID/accounts-held", + "get Accounts Held", + s"""lists accounts for the current user where the current user is a holder but doesn't have the owner view + | + |${authenticationRequiredMessage(true)} + """, + emptyObjectJson, + JSONFactory300.createGlossaryItemsJsonV300(getExampleGlossaryItems), + List(UnknownError), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagAccount) + ) + + lazy val getAccountsHeld : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts-held" :: Nil JsonGet json => { + cc => + for { + (user, callContext) <- extractCallContext(UserNotLoggedIn, cc) + u <- unboxFullAndWrapIntoFuture{ user } + bank <- Future { Bank(bankId) } map { + x => fullBoxOrException(x ?~! BankNotFound) + } + availableAccounts <- Future{ AccountHolders.accountHolders.vend.getAccountsHeld(bankId, u)} + accounts <- Connector.connector.vend.getCoreBankAccountsHeldFuture(availableAccounts.toList, callContext) map { + x => fullBoxOrException(x ?~! ConnectorEmptyResponse) + } map { unboxFull(_) } + } yield { + (JSONFactory300.createCoreAccountsByCoreAccountsJSON(accounts), callContext) + } + } + } /* WIP diff --git a/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala b/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala index c87b19b7d..a0321bf5d 100644 --- a/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala +++ b/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala @@ -233,6 +233,7 @@ case class CoreAccountJsonV300( account_routing: AccountRoutingJsonV121 ) case class CoreAccountsJsonV300(accounts: List[CoreAccount]) +case class CoreAccountsHeldJsonV300(accounts: List[AccountHeld]) case class AccountIdJson( id: String @@ -664,25 +665,13 @@ object JSONFactory300{ def createCoreAccountsByCoreAccountsJSON(coreAccounts : List[CoreAccount]): CoreAccountsJsonV300 = CoreAccountsJsonV300(coreAccounts) - + + def createCoreAccountsByCoreAccountsJSON(accountsHeld : List[AccountHeld]): CoreAccountsHeldJsonV300 = + CoreAccountsHeldJsonV300(accountsHeld) + def createAccountsIdsByBankIdAccountIds(bankaccountIds : List[BankIdAccountId]): AccountsIdsJsonV300 = AccountsIdsJsonV300(bankaccountIds.map(x => AccountIdJson(x.accountId.value))) - - def createBankAccountJSON(account : ModeratedBankAccount, viewsAvailable : List[ViewJsonV300]) : ModeratedAccountJsonV300 = { - val bankName = account.bankName.getOrElse("") - ModeratedAccountJsonV300( - account.accountId.value, - stringOrNull(account.bankId.value), - stringOptionOrNull(account.label), - stringOptionOrNull(account.number), - createOwnersJSON(account.owners.getOrElse(Set()), bankName), - stringOptionOrNull(account.accountType), - createAmountOfMoneyJSON(account.currency.getOrElse(""), account.balance), - viewsAvailable, - AccountRoutingJsonV121(stringOptionOrNull(account.accountRoutingScheme),stringOptionOrNull(account.accountRoutingAddress)) - ) - } def createAccountRulesJSON(rules: List[AccountRule]): List[AccountRuleJsonV300] = { rules.map(i => AccountRuleJsonV300(scheme = i.scheme, value = i.value)) } diff --git a/src/main/scala/code/api/v3_0_0/OBPAPI3_0_0.scala b/src/main/scala/code/api/v3_0_0/OBPAPI3_0_0.scala index 30c5806a2..1b83661a5 100644 --- a/src/main/scala/code/api/v3_0_0/OBPAPI3_0_0.scala +++ b/src/main/scala/code/api/v3_0_0/OBPAPI3_0_0.scala @@ -266,6 +266,7 @@ object OBPAPI3_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations3_0_0.getEntitlementsForCurrentUser :: Implementations3_0_0.getFirehoseTransactionsForBankAccount :: Implementations3_0_0.getApiGlossary :: + Implementations3_0_0.getAccountsHeld :: Nil diff --git a/src/main/scala/code/bankconnectors/Connector.scala b/src/main/scala/code/bankconnectors/Connector.scala index 48076f6bb..b9b8a69a7 100644 --- a/src/main/scala/code/bankconnectors/Connector.scala +++ b/src/main/scala/code/bankconnectors/Connector.scala @@ -255,6 +255,9 @@ trait Connector extends MdcLoggable{ def getCoreBankAccounts(bankIdAccountIds: List[BankIdAccountId], session: Option[CallContext]) : Box[List[CoreAccount]]= Failure(NotImplemented + currentMethodName) def getCoreBankAccountsFuture(bankIdAccountIds: List[BankIdAccountId], session: Option[CallContext]) : Future[Box[List[CoreAccount]]]= Future{Failure(NotImplemented + currentMethodName)} + + def getBankAccountsHeld(bankIdAccountIds: List[BankIdAccountId], session: Option[CallContext]) : Box[List[AccountHeld]]= Failure(NotImplemented + currentMethodName) + def getCoreBankAccountsHeldFuture(bankIdAccountIds: List[BankIdAccountId], session: Option[CallContext]) : Future[Box[List[AccountHeld]]]= Future {Failure(NotImplemented + currentMethodName)} def checkBankAccountExists(bankId : BankId, accountId : AccountId, session: Option[CallContext] = None) : Box[BankAccount]= Failure(NotImplemented + currentMethodName) diff --git a/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index d0d8c593b..e4f28c4a7 100644 --- a/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -293,14 +293,14 @@ object LocalMappedConnector extends Connector with MdcLoggable { getBankAccount(bankId: BankId, accountId: AccountId) } - override def getCoreBankAccounts(BankIdAcountIds: List[BankIdAccountId], session: Option[CallContext]) : Box[List[CoreAccount]]= { + override def getCoreBankAccounts(bankIdAcountIds: List[BankIdAccountId], session: Option[CallContext]) : Box[List[CoreAccount]]= { Full( - BankIdAcountIds + bankIdAcountIds .map(bankIdAccountId => getBankAccount( bankIdAccountId.bankId, bankIdAccountId.accountId) - .openOrThrowException(ErrorMessages.BankAccountNotFound)) + .openOrThrowException(s"${ErrorMessages.BankAccountNotFound} current BANK_ID(${bankIdAccountId.bankId}) and ACCOUNT_ID(${bankIdAccountId.accountId})")) .map(account => CoreAccount( account.accountId.value, @@ -310,23 +310,29 @@ object LocalMappedConnector extends Connector with MdcLoggable { ) } - override def getCoreBankAccountsFuture(BankIdAcountIds: List[BankIdAccountId], session: Option[CallContext]) : Future[Box[List[CoreAccount]]] = { - Future { - Full( - BankIdAcountIds - .map(bankIdAccountId => - getBankAccount( - bankIdAccountId.bankId, - bankIdAccountId.accountId) - .openOrThrowException(ErrorMessages.BankAccountNotFound)) - .map(account => - CoreAccount( - account.accountId.value, - stringOrNull(account.label), - account.bankId.value, - AccountRouting(account.accountRoutingScheme,account.accountRoutingAddress))) - ) - } + override def getCoreBankAccountsFuture(bankIdAcountIds: List[BankIdAccountId], session: Option[CallContext]) : Future[Box[List[CoreAccount]]] = { + Future {getCoreBankAccounts(bankIdAcountIds: List[BankIdAccountId], session: Option[CallContext])} + } + + override def getBankAccountsHeld(bankIdAccountIds: List[BankIdAccountId], session: Option[CallContext]) : Box[List[AccountHeld]]= { + Full( + bankIdAccountIds + .map(bankIdAccountId => + getBankAccount( + bankIdAccountId.bankId, + bankIdAccountId.accountId) + .openOrThrowException(s"${ErrorMessages.BankAccountNotFound} current BANK_ID(${bankIdAccountId.bankId}) and ACCOUNT_ID(${bankIdAccountId.accountId})")) + .map(account => + AccountHeld( + account.accountId.value, + account.bankId.value, + stringOrNull(account.number), + AccountRouting(account.accountRoutingScheme,account.accountRoutingAddress))) + ) + } + + override def getCoreBankAccountsHeldFuture(bankIdAcountIds: List[BankIdAccountId], session: Option[CallContext]) : Future[Box[List[AccountHeld]]] = { + Future {getBankAccountsHeld(bankIdAcountIds: List[BankIdAccountId], session: Option[CallContext])} } diff --git a/src/main/scala/code/model/BankingData.scala b/src/main/scala/code/model/BankingData.scala index 0191881d0..61b79d9e8 100644 --- a/src/main/scala/code/model/BankingData.scala +++ b/src/main/scala/code/model/BankingData.scala @@ -877,6 +877,13 @@ case class CoreAccount( account_routing: AccountRouting ) +case class AccountHeld( + id: String, + bank_id: String, + number: String, + account_routing: AccountRouting +) + case class CounterpartyBespoke( key: String, value: String diff --git a/src/main/scala/code/remotedata/RemotedataAccountHolders.scala b/src/main/scala/code/remotedata/RemotedataAccountHolders.scala index 5d81498c4..342c6a6c9 100644 --- a/src/main/scala/code/remotedata/RemotedataAccountHolders.scala +++ b/src/main/scala/code/remotedata/RemotedataAccountHolders.scala @@ -19,6 +19,9 @@ object RemotedataAccountHolders extends ObpActorInit with AccountHolders { override def getAccountHolders(bankId: BankId, accountId: AccountId): Set[User] = extractFuture(actor ? cc.getAccountHolders(bankId, accountId)) + + override def getAccountsHeld(bankId: BankId, user: User): Set[BankIdAccountId] = + extractFuture(actor ? cc.getAccountsHeld(bankId: BankId, user: User)) def bulkDeleteAllAccountHolders(): Box[Boolean] = extractFutureToBox(actor ? cc.bulkDeleteAllAccountHolders()) diff --git a/src/main/scala/code/remotedata/RemotedataAccountHoldersActor.scala b/src/main/scala/code/remotedata/RemotedataAccountHoldersActor.scala index deb2251fe..6f01c85eb 100644 --- a/src/main/scala/code/remotedata/RemotedataAccountHoldersActor.scala +++ b/src/main/scala/code/remotedata/RemotedataAccountHoldersActor.scala @@ -26,6 +26,10 @@ class RemotedataAccountHoldersActor extends Actor with ObpActorHelper with MdcLo logger.debug("getAccountHolders(" + bankId +", "+ accountId +")") sender ! extractResult(mapper.getAccountHolders(bankId, accountId)) + case cc.getAccountsHeld(bankId: BankId, user: User) => + logger.debug("getAccountsHeld(" + bankId +", "+ user+")") + sender ! extractResult(mapper.getAccountsHeld(bankId: BankId, user: User)) + case cc.bulkDeleteAllAccountHolders() => logger.debug("bulkDeleteAllAccountHolders()") sender ! extractResult(mapper.bulkDeleteAllAccountHolders()) diff --git a/src/main/scala/code/search/search.scala b/src/main/scala/code/search/search.scala index 4ac566c3c..dd5498dd4 100644 --- a/src/main/scala/code/search/search.scala +++ b/src/main/scala/code/search/search.scala @@ -1,5 +1,7 @@ package code.search +import java.nio.charset.Charset + import dispatch.{Http, url} import code.util.Helper.MdcLoggable @@ -19,6 +21,7 @@ import org.elasticsearch.common.settings.Settings import com.sksamuel.elastic4s.TcpClient import com.sksamuel.elastic4s.mappings.FieldType._ import com.sksamuel.elastic4s.ElasticDsl._ +import dispatch.as.String.charset import net.liftweb.http.provider.HTTPCookie import net.liftweb.json.JsonAST @@ -59,7 +62,7 @@ class elasticsearch extends MdcLoggable { val esUrl = s"${httpHost}${uri.replaceAll("\"" , "")}" logger.debug(esUrl) logger.debug(body) - val request = url(esUrl).<<(body).GET // Note that WE ONLY do GET - Keep it this way! + val request: Req = (url(esUrl).<<(body).GET).setContentType("application/json", Charset.forName("UTF-8")) // Note that WE ONLY do GET - Keep it this way! val response = getAPIResponse(request) ESJsonResponse(response.body, ("Access-Control-Allow-Origin", "*") :: Nil, Nil, response.code) } else {