Skip to content

Commit 0ff6a48

Browse files
Merge remote-tracking branch 'upstream/develop' into develop
2 parents 5f0cdf9 + 96318c6 commit 0ff6a48

File tree

11 files changed

+282
-18
lines changed

11 files changed

+282
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ marketing_diagram_generation/outputs/*
3939
.specstory
4040
project/project
4141
coursier
42+
metals.sbt

obp-api/src/main/resources/props/sample.props.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ transactionRequests_enabled=false
362362
transactionRequests_connector=mapped
363363

364364
## Transaction Request Types that are supported on this server. Possible values might include SANDBOX_TAN, COUNTERPARTY, SEPA, FREE_FORM
365-
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE
365+
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE,HOLD
366366

367367
## Transaction request challenge threshold. Level at which challenge is created and needs to be answered.
368368
## The Currency is EUR unless set with transactionRequests_challenge_currency.
@@ -1075,6 +1075,7 @@ database_messages_scheduler_interval=3600
10751075
# -- SCA (Strong Customer Authentication) method for OTP challenge-------
10761076
# ACCOUNT_OTP_INSTRUCTION_TRANSPORT=DUMMY
10771077
# SIMPLE_OTP_INSTRUCTION_TRANSPORT=DUMMY
1078+
# HOLD_OTP_INSTRUCTION_TRANSPORT=DUMMY
10781079
# SEPA_OTP_INSTRUCTION_TRANSPORT=DUMMY
10791080
# FREE_FORM_OTP_INSTRUCTION_TRANSPORT=DUMMY
10801081
# COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=DUMMY

obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5820,6 +5820,12 @@ object SwaggerDefinitionsJSON {
58205820
description = descriptionExample.value
58215821
)
58225822

5823+
// HOLD sample (V600)
5824+
lazy val transactionRequestBodyHoldJsonV600 = TransactionRequestBodyHoldJsonV600(
5825+
value = amountOfMoneyJsonV121,
5826+
description = descriptionExample.value
5827+
)
5828+
58235829
//The common error or success format.
58245830
//Just some helper format to use in Json
58255831
case class NotSupportedYet()

obp-api/src/main/scala/code/api/util/migration/Migration.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ object Migration extends MdcLoggable {
8181
populateTheFieldDeletedAtResourceUser(startedBeforeSchemifier)
8282
populateTheFieldIsActiveAtProductAttribute(startedBeforeSchemifier)
8383
alterColumnUsernameProviderFirstnameAndLastnameAtAuthUser(startedBeforeSchemifier)
84+
populateMissingProviderAtAuthUser(startedBeforeSchemifier)
8485
alterColumnEmailAtResourceUser(startedBeforeSchemifier)
8586
alterColumnNameAtProductFee(startedBeforeSchemifier)
8687
addFastFirehoseAccountsView(startedBeforeSchemifier)
@@ -347,6 +348,17 @@ object Migration extends MdcLoggable {
347348
}
348349
}
349350
}
351+
private def populateMissingProviderAtAuthUser(startedBeforeSchemifier: Boolean): Boolean = {
352+
if(startedBeforeSchemifier == true) {
353+
logger.warn(s"Migration.database.populateMissingProviderAtAuthUser(true) cannot be run before Schemifier.")
354+
true
355+
} else {
356+
val name = nameOf(populateMissingProviderAtAuthUser(startedBeforeSchemifier))
357+
runOnce(name) {
358+
MigrationOfAuthUser.populateMissingProviderWithLocalIdentity(name)
359+
}
360+
}
361+
}
350362
private def alterColumnEmailAtResourceUser(startedBeforeSchemifier: Boolean): Boolean = {
351363
if(startedBeforeSchemifier == true) {
352364
logger.warn(s"Migration.database.alterColumnEmailAtResourceUser(true) cannot be run before Schemifier.")

obp-api/src/main/scala/code/api/util/migration/MigrationOfAuthUser.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package code.api.util.migration
33
import java.time.format.DateTimeFormatter
44
import java.time.{ZoneId, ZonedDateTime}
55

6+
import code.api.Constant
67
import code.api.util.APIUtil
78
import code.api.util.migration.Migration.{DbFunction, saveLog}
89
import code.util.Helper
@@ -74,6 +75,45 @@ object MigrationOfAuthUser {
7475
}
7576
}
7677

78+
def populateMissingProviderWithLocalIdentity(name: String): Boolean = {
79+
DbFunction.tableExists(AuthUser) match {
80+
case true =>
81+
val startDate = System.currentTimeMillis()
82+
val commitId: String = APIUtil.gitCommit
83+
var isSuccessful = false
84+
85+
// Make back up
86+
DbFunction.makeBackUpOfTable(AuthUser)
87+
88+
val updatedRows =
89+
for {
90+
user <- AuthUser.findAll()
91+
providerValue = Option(user.provider.get).map(_.trim).getOrElse("") if providerValue.isEmpty
92+
} yield {
93+
user.provider(Constant.localIdentityProvider).saveMe()
94+
}
95+
96+
val endDate = System.currentTimeMillis()
97+
val comment: String =
98+
s"""Updated number of rows:
99+
|${updatedRows.size}
100+
|""".stripMargin
101+
isSuccessful = true
102+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
103+
isSuccessful
104+
105+
case false =>
106+
val startDate = System.currentTimeMillis()
107+
val commitId: String = APIUtil.gitCommit
108+
val isSuccessful = false
109+
val endDate = System.currentTimeMillis()
110+
val comment: String =
111+
s"""${AuthUser._dbTableNameLC} table does not exist""".stripMargin
112+
saveLog(name, commitId, isSuccessful, startDate, endDate, comment)
113+
isSuccessful
114+
}
115+
}
116+
77117
def dropIndexAtColumnUsername(name: String): Boolean = {
78118
DbFunction.tableExists(AuthUser) match {
79119
case true =>

obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
package code.api.v6_0_0
22

3-
import code.api.{APIFailureNewStyle, DirectLogin, ObpApiFailure}
4-
import code.api.v6_0_0.JSONFactory600
3+
import code.accountattribute.AccountAttributeX
4+
import code.api.{DirectLogin, ObpApiFailure}
55
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
66
import code.api.util.APIUtil._
7-
import code.api.util.ApiRole.{CanCreateEntitlementAtOneBank, CanReadDynamicResourceDocsAtOneBank, canCreateBank, canCreateRateLimits, canDeleteRateLimits, canReadCallLimits, canUpdateRateLimits}
7+
import code.api.util.ApiRole._
88
import code.api.util.ApiTag._
99
import code.api.util.ErrorMessages.{$UserNotLoggedIn, InvalidDateFormat, InvalidJsonFormat, UnknownError, _}
1010
import code.api.util.FutureUtil.EndpointContext
11-
import code.api.util.{APIUtil, ErrorMessages, NewStyle, RateLimitingUtil}
1211
import code.api.util.NewStyle.HttpCode
12+
import code.api.util.{APIUtil, ErrorMessages, NewStyle, RateLimitingUtil}
13+
import code.api.v3_0_0.JSONFactory300
1314
import code.api.v4_0_0.CallLimitPostJsonV400
1415
import code.api.v4_0_0.JSONFactory400.createCallsLimitJson
15-
import code.api.v5_0_0.{JSONFactory500, PostBankJson500}
16+
import code.api.v5_0_0.JSONFactory500
1617
import code.api.v6_0_0.JSONFactory600.{createActiveCallLimitsJsonV600, createCallLimitJsonV600, createCurrentUsageJson}
1718
import code.bankconnectors.LocalMappedConnectorInternal
1819
import code.bankconnectors.LocalMappedConnectorInternal._
1920
import code.entitlement.Entitlement
21+
import code.model._
2022
import code.ratelimiting.RateLimitingDI
2123
import code.util.Helper
2224
import code.util.Helper.SILENCE_IS_GOLDEN
@@ -25,11 +27,10 @@ import com.github.dwickern.macros.NameOf.nameOf
2527
import com.openbankproject.commons.ExecutionContext.Implicits.global
2628
import com.openbankproject.commons.model._
2729
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
28-
import net.liftweb.common.{Box, Empty, Full}
30+
import net.liftweb.common.{Empty, Full}
2931
import net.liftweb.http.rest.RestHelper
3032

3133
import java.text.SimpleDateFormat
32-
import java.util.Date
3334
import scala.collection.immutable.{List, Nil}
3435
import scala.collection.mutable.ArrayBuffer
3536
import scala.concurrent.Future
@@ -51,6 +52,102 @@ trait APIMethods600 {
5152
val codeContext = CodeContext(staticResourceDocs, apiRelations)
5253

5354

55+
staticResourceDocs += ResourceDoc(
56+
createTransactionRequestHold,
57+
implementedInApiVersion,
58+
nameOf(createTransactionRequestHold),
59+
"POST",
60+
"/banks/BANK_ID/accounts/ACCOUNT_ID/owner/transaction-request-types/HOLD/transaction-requests",
61+
"Create Transaction Request (HOLD)",
62+
s"""
63+
|
64+
|Create a transaction request to move funds from the account to its Holding Account.
65+
|If the Holding Account does not exist, it will be created automatically.
66+
|
67+
|${transactionRequestGeneralText}
68+
|
69+
""".stripMargin,
70+
transactionRequestBodyHoldJsonV600,
71+
transactionRequestWithChargeJSON400,
72+
List(
73+
$UserNotLoggedIn,
74+
$BankNotFound,
75+
$BankAccountNotFound,
76+
InsufficientAuthorisationToCreateTransactionRequest,
77+
InvalidTransactionRequestType,
78+
InvalidJsonFormat,
79+
NotPositiveAmount,
80+
InvalidTransactionRequestCurrency,
81+
TransactionDisabled,
82+
UnknownError
83+
),
84+
List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagPsd2)
85+
)
86+
87+
lazy val createTransactionRequestHold: OBPEndpoint = {
88+
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
89+
"HOLD" :: "transaction-requests" :: Nil JsonPost json -> _ =>
90+
cc => implicit val ec = EndpointContext(Some(cc))
91+
val transactionRequestType = TransactionRequestType("HOLD")
92+
LocalMappedConnectorInternal.createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json)
93+
}
94+
95+
// --- GET Holding Account by Parent ---
96+
staticResourceDocs += ResourceDoc(
97+
getHoldingAccountByReleaser,
98+
implementedInApiVersion,
99+
nameOf(getHoldingAccountByReleaser),
100+
"GET",
101+
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/holding-accounts",
102+
"Get Holding Accounts By Releaser",
103+
s"""
104+
|
105+
|Return the first Holding Account linked to the given releaser account via account attribute `RELEASER_ACCOUNT_ID`.
106+
|Response is wrapped in a list and includes account attributes.
107+
|
108+
""".stripMargin,
109+
EmptyBody,
110+
moderatedCoreAccountsJsonV300,
111+
List(
112+
$UserNotLoggedIn,
113+
$BankNotFound,
114+
$BankAccountNotFound,
115+
$UserNoPermissionAccessView,
116+
UnknownError
117+
),
118+
List(apiTagAccount)
119+
)
120+
121+
lazy val getHoldingAccountByReleaser: OBPEndpoint = {
122+
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "holding-accounts" :: Nil JsonGet _ =>
123+
cc => implicit val ec = EndpointContext(Some(cc))
124+
for {
125+
(user @Full(u), _, _, view, callContext) <- SS.userBankAccountView
126+
// Find accounts by attribute RELEASER_ACCOUNT_ID
127+
(accountIdsBox, callContext) <- AccountAttributeX.accountAttributeProvider.vend.getAccountIdsByParams(bankId, Map("RELEASER_ACCOUNT_ID" -> List(accountId.value))) map { ids => (ids, callContext) }
128+
accountIds = accountIdsBox.getOrElse(Nil)
129+
// load the first holding account
130+
holdingOpt <- {
131+
def firstHolding(ids: List[String]): Future[Option[BankAccount]] = ids match {
132+
case Nil => Future.successful(None)
133+
case id :: tail =>
134+
NewStyle.function.getBankAccount(bankId, AccountId(id), callContext).flatMap { case (acc, cc) =>
135+
if (acc.accountType == "HOLDING") Future.successful(Some(acc)) else firstHolding(tail)
136+
}
137+
}
138+
firstHolding(accountIds)
139+
}
140+
holding <- NewStyle.function.tryons($BankAccountNotFound, 404, callContext) { holdingOpt.get }
141+
moderatedAccount <- Future { holding.moderatedBankAccount(view, BankIdAccountId(holding.bankId, holding.accountId), user, callContext) } map {
142+
x => unboxFullOrFail(x, callContext, UnknownError)
143+
}
144+
(attributes, callContext) <- NewStyle.function.getAccountAttributesByAccount(bankId, holding.accountId, callContext)
145+
} yield {
146+
val accountsJson = JSONFactory300.createFirehoseCoreBankAccountJSON(List(moderatedAccount), Some(attributes))
147+
(accountsJson, HttpCode.`200`(callContext))
148+
}
149+
}
150+
54151
staticResourceDocs += ResourceDoc(
55152
getCurrentCallsLimit,
56153
implementedInApiVersion,

obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ case class TransactionRequestBodyEthSendRawTransactionJsonV600(
128128
description: String
129129
)
130130

131+
// ---------------- HOLD models (V600) ----------------
132+
case class TransactionRequestBodyHoldJsonV600(
133+
value: AmountOfMoneyJsonV121,
134+
description: String
135+
) extends TransactionRequestCommonBodyJSON
136+
131137
case class UserJsonV600(
132138
user_id: String,
133139
email : String,

0 commit comments

Comments
 (0)