From 2b0780843891cacdadc87ee5a6187d8f03366d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 21 Jan 2022 11:45:16 +0100 Subject: [PATCH] feature/User Attributes; Add endpoint createCurrentUserAttribute v4.0.0 --- .../SwaggerDefinitionsJSON.scala | 16 ++++++ .../scala/code/api/util/ExampleValue.scala | 12 +++++ .../main/scala/code/api/util/NewStyle.scala | 29 +++++++++- .../scala/code/api/v4_0_0/APIMethods400.scala | 53 +++++++++++++++++++ .../code/api/v4_0_0/JSONFactory4.0.0.scala | 27 +++++++++- .../scala/code/bankconnectors/Connector.scala | 15 +++++- .../bankconnectors/LocalMappedConnector.scala | 25 ++++++++- .../remotedata/RemotedataUserAttribute.scala | 4 +- .../RemotedataUserAttributeActor.scala | 6 +-- .../code/users/MappedUserAttribute.scala | 44 ++++++++++++--- .../code/users/UserAttributeProvider.scala | 4 +- .../commons/model/CommonModelTrait.scala | 1 + 12 files changed, 219 insertions(+), 17 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index d4ac297cd..c61a51109 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 @@ -4130,6 +4130,22 @@ object SwaggerDefinitionsJSON { value = customerAttributeValueExample.value ) + + val userAttributeResponseJson = UserAttributeResponseJsonV400 ( + user_attribute_id = userAttributeIdExample.value, + name = userAttributeNameExample.value, + `type` = userAttributeTypeExample.value, + value = userAttributeValueExample.value + ) + val userAttributesResponseJson = UserAttributesResponseJson ( + user_attributes = List(userAttributeResponseJson) + ) + val userAttributeJsonV400 = UserAttributeJsonV400( + name = userAttributeNameExample.value, + `type` = userAttributeTypeExample.value, + value = userAttributeValueExample.value + ) + val transactionAttributeResponseJson = TransactionAttributeResponseJson( transaction_attribute_id = transactionAttributeIdExample.value, name = transactionAttributeNameExample.value, diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index fb7044526..a6cfc68ff 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -111,14 +111,23 @@ object ExampleValue { lazy val customerAttributeIdExample = ConnectorField("7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", s"Customer attribute id") glossaryItems += makeGlossaryItem("Customer.attributeId", customerAttributeIdExample) + lazy val userAttributeIdExample = ConnectorField("7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", s"User attribute id") + glossaryItems += makeGlossaryItem("Customer.attributeId", userAttributeIdExample) + lazy val customerAttributeNameExample = ConnectorField("SPECIAL_TAX_NUMBER", s"Customer attribute name") glossaryItems += makeGlossaryItem("Customer.attributeName", customerAttributeNameExample) + lazy val userAttributeNameExample = ConnectorField("ROLE", s"User attribute name") + glossaryItems += makeGlossaryItem("User.attributeName", userAttributeNameExample) + lazy val templateAttributeNameExample = ConnectorField("SPECIAL_TAX_NUMBER", s"Attribute name") glossaryItems += makeGlossaryItem("Template.attributeName", templateAttributeNameExample) lazy val customerAttributeTypeExample = ConnectorField("STRING", s"Customer attribute type.") glossaryItems += makeGlossaryItem("Customer.attributeType", customerAttributeTypeExample) + + lazy val userAttributeTypeExample = ConnectorField("STRING", s"User attribute type.") + glossaryItems += makeGlossaryItem("User.attributeType", userAttributeTypeExample) lazy val templateAttributeTypeExample = ConnectorField("STRING", s"Attribute type.") glossaryItems += makeGlossaryItem("Template.attributeType", templateAttributeTypeExample) @@ -128,6 +137,9 @@ object ExampleValue { lazy val customerAttributeValueExample = ConnectorField("123456789", s"Customer attribute value.") glossaryItems += makeGlossaryItem("Customer.attributeValue", customerAttributeValueExample) + + lazy val userAttributeValueExample = ConnectorField("123456789", s"Uset attribute value.") + glossaryItems += makeGlossaryItem("User.attributeValue", userAttributeValueExample) lazy val labelExample = ConnectorField("My Account", s"A lable that describes the Account") lazy val legalNameExample = ConnectorField("Eveline Tripman", s"The legal name of the Customer.") 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 8603ff1de..642cd3375 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -36,7 +36,7 @@ import code.apicollection.{ApiCollectionTrait, MappedApiCollectionsProvider} import code.model.dataAccess.{AuthUser, BankAccountRouting} import code.standingorders.StandingOrderTrait import code.usercustomerlinks.UserCustomerLink -import code.users.{UserAgreement, UserAgreementProvider, UserInvitation, UserInvitationProvider, Users} +import code.users.{UserAgreement, UserAgreementProvider, UserAttribute, UserInvitation, UserInvitationProvider, Users} import code.util.Helper import com.openbankproject.commons.util.{ApiVersion, JsonUtils} import code.views.Views @@ -1596,6 +1596,33 @@ object NewStyle { } } + def getUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[List[UserAttribute]] = { + Connector.connector.vend.getUserAttributes( + userId: String, callContext: Option[CallContext] + ) map { + i => (connectorEmptyResponse(i._1, callContext), i._2) + } + } + def createOrUpdateUserAttribute( + userId: String, + userAttributeId: Option[String], + name: String, + attributeType: UserAttributeType.Value, + value: String, + callContext: Option[CallContext] + ): OBPReturnType[UserAttribute] = { + Connector.connector.vend.createOrUpdateUserAttribute( + userId: String, + userAttributeId: Option[String], + name: String, + attributeType: UserAttributeType.Value, + value: String, + callContext: Option[CallContext] + ) map { + i => (connectorEmptyResponse(i._1, callContext), i._2) + } + } + def createAccountAttributes(bankId: BankId, accountId: AccountId, productCode: ProductCode, diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index cff9ca316..7e4a7a522 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -8105,6 +8105,59 @@ trait APIMethods400 { } } } + + + staticResourceDocs += ResourceDoc( + createCurrentUserAttribute, + implementedInApiVersion, + nameOf(createCurrentUserAttribute), + "POST", + "/my/user/attribute", + "Create User Attribute for current user", + s""" Create User Attribute forcurrent user + | + |The type field must be one of "STRING", "INTEGER", "DOUBLE" or DATE_WITH_DAY" + | + |${authenticationRequiredMessage(true)} + | + |""", + userAttributeJsonV400, + userAttributeResponseJson, + List( + $UserNotLoggedIn, + InvalidJsonFormat, + UnknownError + ), + List(apiTagUser, apiTagNewStyle), + Some(List())) + + lazy val createCurrentUserAttribute : OBPEndpoint = { + case "my" :: "user" :: "attribute" :: Nil JsonPost json -> _=> { + cc => + val failMsg = s"$InvalidJsonFormat The Json body should be the $TransactionAttributeJsonV400 " + for { + (attributes, callContext) <- NewStyle.function.getUserAttributes(cc.userId, cc.callContext) + postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { + json.extract[TransactionAttributeJsonV400] + } + failMsg = s"$InvalidJsonFormat The `Type` field can only accept the following field: " + + s"${TransactionAttributeType.DOUBLE}(12.1234), ${TransactionAttributeType.STRING}(TAX_NUMBER), ${TransactionAttributeType.INTEGER} (123)and ${TransactionAttributeType.DATE_WITH_DAY}(2012-04-23)" + userAttributeType <- NewStyle.function.tryons(failMsg, 400, callContext) { + UserAttributeType.withName(postedData.`type`) + } + (userAttribute, callContext) <- NewStyle.function.createOrUpdateUserAttribute( + cc.userId, + None, + postedData.name, + userAttributeType, + postedData.value, + callContext + ) + } yield { + (JSONFactory400.createUserAttributeJson(userAttribute), HttpCode.`201`(callContext)) + } + } + } staticResourceDocs += ResourceDoc( diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala index 6ac025190..0bef731e0 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala @@ -56,7 +56,7 @@ import code.ratelimiting.RateLimiting import code.standingorders.StandingOrderTrait import code.transactionrequests.TransactionRequests.TransactionChallengeTypes import code.userlocks.UserLocks -import code.users.{UserAgreement, UserInvitation} +import code.users.{UserAgreement, UserAttribute, UserInvitation} import com.openbankproject.commons.model.{DirectDebitTrait, ProductFeeTrait, _} import net.liftweb.common.{Box, Full} import net.liftweb.json.JValue @@ -425,6 +425,22 @@ case class RefundJson( reason_code: String ) +case class UserAttributeJsonV400( + name: String, + `type`: String, + value: String, +) +case class UserAttributeResponseJsonV400( + user_attribute_id: String, + name: String, + `type`: String, + value: String +) +case class UserAttributesResponseJson( + user_attributes: List[UserAttributeResponseJsonV400] +) + + case class CustomerAttributeJsonV400( name: String, `type`: String, @@ -1281,6 +1297,15 @@ object JSONFactory400 { value = transactionAttribute.value ) } + + def createUserAttributeJson(userAttribute: UserAttribute) : UserAttributeResponseJsonV400 = { + UserAttributeResponseJsonV400( + user_attribute_id = userAttribute.userAttributeId, + name = userAttribute.name, + `type` = userAttribute.attributeType.toString, + value = userAttribute.value + ) + } def createTransactionAttributesJson(transactionAttributes: List[TransactionAttribute]) : TransactionAttributesResponseJson = { TransactionAttributesResponseJson (transactionAttributes.map( transactionAttribute => TransactionAttributeResponseJson( diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 56c71d1e3..ea752176d 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -2,6 +2,7 @@ package code.bankconnectors import java.util.Date import java.util.UUID.randomUUID + import _root_.akka.http.scaladsl.model.HttpMethod import code.accountholders.{AccountHolders, MapperAccountHolders} import code.api.attributedefinition.AttributeDefinition @@ -33,7 +34,7 @@ import code.standingorders.StandingOrderTrait import code.transactionrequests.TransactionRequests import code.transactionrequests.TransactionRequests.TransactionRequestTypes._ import code.transactionrequests.TransactionRequests._ -import code.users.Users +import code.users.{UserAttribute, Users} import code.util.Helper._ import code.views.Views import com.openbankproject.commons.ExecutionContext.Implicits.global @@ -2123,6 +2124,18 @@ trait Connector extends MdcLoggable { (Failure(setUnimplementedError), callContext) } + def getUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = + Future{(Failure(setUnimplementedError), callContext)} + + def createOrUpdateUserAttribute( + userId: String, + userAttributeId: Option[String], + name: String, + attributeType: UserAttributeType.Value, + value: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[UserAttribute]] = Future{(Failure(setUnimplementedError), callContext)} + def createOrUpdateTransactionAttribute( bankId: BankId, transactionId: TransactionId, diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index a3721b72f..52768e01d 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -2,6 +2,7 @@ package code.bankconnectors import java.util.Date import java.util.UUID.randomUUID + import _root_.akka.http.scaladsl.model.HttpMethod import code.DynamicData.DynamicDataProvider import code.DynamicEndpoint.{DynamicEndpointProvider, DynamicEndpointT} @@ -66,7 +67,7 @@ import code.transactionattribute.TransactionAttributeX import code.transactionrequests.TransactionRequests.TransactionRequestTypes._ import code.transactionrequests.TransactionRequests.{TransactionChallengeTypes, TransactionRequestTypes} import code.transactionrequests._ -import code.users.Users +import code.users.{UserAttribute, UserAttributeProvider, Users} import code.util.Helper import code.util.Helper.{MdcLoggable, _} import code.views.Views @@ -3721,6 +3722,28 @@ object LocalMappedConnector extends Connector with MdcLoggable { } } + override def getUserAttributes(userId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[UserAttribute]]] = { + UserAttributeProvider.userAttributeProvider.vend.getUserAttributesByUser(userId: String) map {(_, callContext)} + } + override def createOrUpdateUserAttribute( + userId: String, + userAttributeId: Option[String], + name: String, + attributeType: UserAttributeType.Value, + value: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[UserAttribute]] = { + UserAttributeProvider.userAttributeProvider.vend.createOrUpdateUserAttribute( + userId: String, + userAttributeId: Option[String], + name: String, + attributeType: UserAttributeType.Value, + value: String + ) map { + (_, callContext) + } + } + override def createOrUpdateTransactionAttribute( bankId: BankId, transactionId: TransactionId, diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala index 02eefe4c8..169a8b461 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttribute.scala @@ -16,8 +16,8 @@ object RemotedataUserAttribute extends ObpActorInit with UserAttributeProvider { val cc = RemotedataUserAttributeCaseClasses - override def getAccountAttributesByUser(userId: String): Future[Box[List[UserAttribute]]] = - (actor ? cc.getAccountAttributesByUser(userId)).mapTo[Box[List[UserAttribute]]] + override def getUserAttributesByUser(userId: String): Future[Box[List[UserAttribute]]] = + (actor ? cc.getUserAttributesByUser(userId)).mapTo[Box[List[UserAttribute]]] override def createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttributeActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttributeActor.scala index 84b0e3670..884089527 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataUserAttributeActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataUserAttributeActor.scala @@ -15,9 +15,9 @@ class RemotedataUserAttributeActor extends Actor with ObpActorHelper with MdcLog def receive: PartialFunction[Any, Unit] = { - case cc.getAccountAttributesByUser(userId: String) => - logger.debug(s"getAccountAttributesByUser(${userId})") - mapper.getAccountAttributesByUser(userId) pipeTo sender + case cc.getUserAttributesByUser(userId: String) => + logger.debug(s"getUserAttributesByUser(${userId})") + mapper.getUserAttributesByUser(userId) pipeTo sender case cc.createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, value: String) => logger.debug(s"createOrUpdateUserAttribute(${userId}, ${userAttributeId}, ${name}, ${attributeType}, ${value})") diff --git a/obp-api/src/main/scala/code/users/MappedUserAttribute.scala b/obp-api/src/main/scala/code/users/MappedUserAttribute.scala index ceb067148..2c1f4a14e 100644 --- a/obp-api/src/main/scala/code/users/MappedUserAttribute.scala +++ b/obp-api/src/main/scala/code/users/MappedUserAttribute.scala @@ -1,34 +1,66 @@ package code.users import code.util.MappedUUID -import com.openbankproject.commons.model.enums.{AccountAttributeType, UserAttributeType} -import com.openbankproject.commons.model.{AccountAttribute, UserAttributeTrait} -import net.liftweb.common.Box +import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.model.UserAttributeTrait +import com.openbankproject.commons.model.enums.UserAttributeType +import net.liftweb.common.{Box, Empty, Full} import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo import scala.collection.immutable.List import scala.concurrent.Future - object MappedUserAttributeProvider extends UserAttributeProvider { - override def getAccountAttributesByUser(userId: String): Future[Box[List[UserAttribute]]] = ??? + override def getUserAttributesByUser(userId: String): Future[Box[List[UserAttribute]]] = Future { + tryo( + UserAttribute.findAll(By(UserAttribute.UserId, userId)) + ) + } override def createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, attributeType: UserAttributeType.Value, - value: String): Future[Box[UserAttribute]] = ??? + value: String): Future[Box[UserAttribute]] = { + userAttributeId match { + case Some(id) => Future { + UserAttribute.find(By(UserAttribute.UserAttributeId, id)) match { + case Full(attribute) => tryo { + attribute + .Name(name) + .Type(attributeType.toString) + .Value(value) + .saveMe() + } + case _ => Empty + } + } + case None => Future { + Full { + UserAttribute.create + .Name(name) + .Type(attributeType.toString()) + .Value(value) + .saveMe() + } + } + } + } + } class UserAttribute extends UserAttributeTrait with LongKeyedMapper[UserAttribute] with IdPK { override def getSingleton = UserAttribute object UserAttributeId extends MappedUUID(this) + object UserId extends MappedUUID(this) object Name extends MappedString(this, 50) object Type extends MappedString(this, 50) object Value extends MappedString(this, 255) override def userAttributeId: String = UserAttributeId.get + override def userId: String = UserId.get override def name: String = Name.get override def attributeType: UserAttributeType.Value = UserAttributeType.withName(Type.get) override def value: String = Value.get diff --git a/obp-api/src/main/scala/code/users/UserAttributeProvider.scala b/obp-api/src/main/scala/code/users/UserAttributeProvider.scala index e58087815..3fb601887 100644 --- a/obp-api/src/main/scala/code/users/UserAttributeProvider.scala +++ b/obp-api/src/main/scala/code/users/UserAttributeProvider.scala @@ -37,7 +37,7 @@ trait UserAttributeProvider { private val logger = Logger(classOf[UserAttributeProvider]) - def getAccountAttributesByUser(userId: String): Future[Box[List[UserAttribute]]] + def getUserAttributesByUser(userId: String): Future[Box[List[UserAttribute]]] def createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, @@ -47,7 +47,7 @@ trait UserAttributeProvider { } class RemotedataUserAttributeCaseClasses { - case class getAccountAttributesByUser(userId: String) + case class getUserAttributesByUser(userId: String) case class createOrUpdateUserAttribute(userId: String, userAttributeId: Option[String], name: String, diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala index 80bd1f5ef..686296edd 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala @@ -97,6 +97,7 @@ trait AccountApplication { trait UserAttributeTrait { def userAttributeId: String + def userId: String def name: String def attributeType: UserAttributeType.Value def value: String