From 9309842e025c2c04b6dbff62fe9ff5251f9066ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 11 Oct 2019 16:30:24 +0200 Subject: [PATCH] Account Tags - WIP --- .../scala/code/api/v4_0_0/APIMethods400.scala | 103 +++++++++++++++++- .../scala/code/api/v4_0_0/OBPAPI4_0_0.scala | 2 + .../scala/code/metadata/tags/MappedTags.scala | 19 ++++ .../metadata/tags/MongoTransactionTags.scala | 12 ++ .../main/scala/code/metadata/tags/Tags.scala | 4 + .../code/remotedata/RemotedataTags.scala | 6 + .../code/remotedata/RemotedataTagsActor.scala | 8 ++ 7 files changed, 148 insertions(+), 6 deletions(-) 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 d433a841b..fa2851182 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 @@ -1,19 +1,21 @@ package code.api.v4_0_0 +import code.api.ChargePolicy import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil._ import code.api.util.ApiRole._ import code.api.util.ApiTag._ -import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankNotFound, CounterpartyBeneficiaryPermit, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserHasMissingRoles, UserNoPermissionAccessView, UserNotLoggedIn, ViewNotFound} +import code.api.util.ErrorMessages.{AccountNotFound, AllowedAttemptsUsedUp, BankAccountNotFound, BankNotFound, CounterpartyBeneficiaryPermit, InsufficientAuthorisationToCreateTransactionRequest, InvalidAccountIdFormat, InvalidBankIdFormat, InvalidChallengeAnswer, InvalidChallengeType, InvalidChargePolicy, InvalidISOCurrencyCode, InvalidJsonFormat, InvalidNumber, InvalidTransactionRequesChallengeId, InvalidTransactionRequestCurrency, InvalidTransactionRequestType, NoViewPermission, NotPositiveAmount, TransactionDisabled, TransactionRequestStatusNotInitiated, TransactionRequestTypeHasChanged, UnknownError, UserHasMissingRoles, UserNoPermissionAccessView, UserNotLoggedIn, ViewNotFound} import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample} import code.api.util.NewStyle.HttpCode import code.api.util._ +import code.api.v1_2_1.{JSONFactory, PostTransactionTagJSON} import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeAnswerJSON, TransactionRequestAccountJsonV140} import code.api.v2_0_0.{EntitlementJSON, EntitlementJSONs, JSONFactory200} import code.api.v2_1_0._ import code.api.v3_1_0.ListResult -import code.api.{APIFailureNewStyle, ChargePolicy} import code.dynamicEntity.DynamicEntityCommons +import code.metadata.tags.Tags import code.model.dataAccess.AuthUser import code.model.toUserExtended import code.transactionrequests.TransactionRequests.TransactionChallengeTypes._ @@ -28,6 +30,7 @@ import net.liftweb.common.{Box, Full, ParamFailure} import net.liftweb.http.rest.RestHelper import net.liftweb.json.Serialization.write import net.liftweb.json._ +import net.liftweb.util.Helpers.now import net.liftweb.util.StringHelpers import org.atteo.evo.inflector.English @@ -35,10 +38,6 @@ import scala.collection.immutable.{List, Nil} import scala.collection.mutable.ArrayBuffer import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future -import code.api.v2_0_0.{EntitlementJSON, EntitlementJSONs, JSONFactory200} -import code.api.v3_0_0.JSONFactory300 -import code.entitlement.Entitlement -import code.views.Views trait APIMethods400 { self: RestHelper => @@ -1090,6 +1089,98 @@ trait APIMethods400 { } } + + resourceDocs += ResourceDoc( + addTagForViewOnAccount, + implementedInApiVersion, + "addTagForViewOnAccount", + "POST", + "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/metadata/tags", + "Add a tag.", + s"""Posts a tag about an account ACCOUNT_ID on a [view](#1_2_1-getViewsForBankAccount) VIEW_ID. + | + |${authenticationRequiredMessage(true)} + | + |Authentication is required as the tag is linked with the user.""", + postTransactionTagJSON, + transactionTagJSON, + List( + UserNotLoggedIn, + BankAccountNotFound, + InvalidJsonFormat, + NoViewPermission, + ViewNotFound, + UnknownError), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagAccount)) + + lazy val addTagForViewOnAccount : OBPEndpoint = { + //add a tag + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "metadata" :: "tags" :: Nil JsonPost json -> _ => { + cc => + for { + (Full(u), callContext) <- authorizedAccess(cc) + (_, callContext) <- NewStyle.function.getBank(bankId, callContext) + (_, callContext) <- NewStyle.function.checkBankAccountExists(bankId, accountId, callContext) + view <- NewStyle.function.view(viewId, BankIdAccountId(bankId, accountId), callContext) + _ <- NewStyle.function.hasViewAccess(view, u) + _ <- Helper.booleanToFuture(failMsg = s"$NoViewPermission can_add_tag. Current ViewId($viewId)") { + view.canAddTag + } + tagJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostTransactionTagJSON ", 400, callContext) { + json.extract[PostTransactionTagJSON] + } + (postedTag, callContext) <- Future(Tags.tags.vend.addTagOnAccount(bankId, accountId)(u.userPrimaryKey, viewId, tagJson.value, now)) map { + i => (connectorEmptyResponse(i, callContext), callContext) + } + } yield { + (JSONFactory.createTransactionTagJSON(postedTag), HttpCode.`201`(callContext)) + } + } + } + + resourceDocs += ResourceDoc( + deleteTagForViewOnAccount, + implementedInApiVersion, + "deleteTagForViewOnAccount", + "DELETE", + "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/metadata/tags/TAG_ID", + "Delete a tag.", + """Deletes the tag TAG_ID about the account ACCOUNT_ID made on [view](#1_2_1-getViewsForBankAccount). + |Authentication via OAuth is required. The user must either have owner privileges for this account, + |or must be the user that posted the tag. + |""".stripMargin, + emptyObjectJson, + emptyObjectJson, + List(NoViewPermission, + ViewNotFound, + UnknownError), + Catalogs(notCore, notPSD2, notOBWG), + List(apiTagAccount)) + + lazy val deleteTagForViewOnAccount : OBPEndpoint = { + //delete a tag + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "metadata" :: "tags" :: tagId :: Nil JsonDelete _ => { + cc => + for { + (Full(u), callContext) <- authorizedAccess(cc) + (_, callContext) <- NewStyle.function.getBank(bankId, callContext) + (_, callContext) <- NewStyle.function.checkBankAccountExists(bankId, accountId, callContext) + view <- NewStyle.function.view(viewId, BankIdAccountId(bankId, accountId), callContext) + _ <- NewStyle.function.hasViewAccess(view, u) + _ <- Helper.booleanToFuture(failMsg = s"$NoViewPermission can_delete_tag. Current ViewId($viewId)") { + view.canDeleteTag + } + deleted <- Future(Tags.tags.vend.deleteTagOnAccount(bankId, accountId)(tagId)) map { + i => (connectorEmptyResponse(i, callContext), callContext) + } + } yield { + (Full(deleted), HttpCode.`200`(callContext)) + } + } + } + + } } 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 daaf40cec..326e42333 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 @@ -395,6 +395,8 @@ object OBPAPI4_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w Implementations4_0_0.root :: Implementations4_0_0.getCallContext :: Implementations4_0_0.getEntitlements :: + Implementations4_0_0.addTagForViewOnAccount :: + Implementations4_0_0.deleteTagForViewOnAccount :: Nil def allResourceDocs = MockerConnector.doc ++ diff --git a/obp-api/src/main/scala/code/metadata/tags/MappedTags.scala b/obp-api/src/main/scala/code/metadata/tags/MappedTags.scala index c8a79a8ad..46b0d3aed 100644 --- a/obp-api/src/main/scala/code/metadata/tags/MappedTags.scala +++ b/obp-api/src/main/scala/code/metadata/tags/MappedTags.scala @@ -32,11 +32,30 @@ object MappedTags extends Tags { .date(datePosted).saveMe } } + + override def addTagOnAccount(bankId: BankId, accountId: AccountId) + (userId: UserPrimaryKey, viewId: ViewId, tagText: String, datePosted: Date): Box[TransactionTag] = { + val metadateViewId = Views.views.vend.getMetadataViewId(BankIdAccountId(bankId, accountId), viewId) + tryo{ + MappedTag.create + .bank(bankId.value) + .account(accountId.value) + .transaction(null) + .view(metadateViewId) + .user(userId.value) + .tag(tagText) + .date(datePosted).saveMe + } + } override def deleteTag(bankId: BankId, accountId: AccountId, transactionId: TransactionId)(tagId: String): Box[Boolean] = { //tagId is always unique so we actually don't need to use bankId, accountId, or transactionId MappedTag.find(By(MappedTag.tagId, tagId)).map(_.delete_!) } + override def deleteTagOnAccount(bankId: BankId, accountId: AccountId)(tagId: String): Box[Boolean] = { + //tagId is always unique so we actually don't need to use bankId, accountId, or transactionId + MappedTag.find(By(MappedTag.tagId, tagId), By(MappedTag.bank, bankId.value), By(MappedTag.account, accountId.value)).map(_.delete_!) + } override def bulkDeleteTags(bankId: BankId, accountId: AccountId): Boolean = { val tagsDeleted = MappedTag.bulkDelete_!!( diff --git a/obp-api/src/main/scala/code/metadata/tags/MongoTransactionTags.scala b/obp-api/src/main/scala/code/metadata/tags/MongoTransactionTags.scala index 837de036a..3fe9755e3 100644 --- a/obp-api/src/main/scala/code/metadata/tags/MongoTransactionTags.scala +++ b/obp-api/src/main/scala/code/metadata/tags/MongoTransactionTags.scala @@ -27,6 +27,15 @@ private object MongoTransactionTags extends Tags { tag(tagText). date(datePosted).saveTheRecord() } + def addTagOnAccount(bankId : BankId, accountId : AccountId)(userId: UserPrimaryKey, viewId : ViewId, tagText : String, datePosted : Date) : Box[TransactionTag] = { + OBPTag.createRecord. + bankId(bankId.value). + accountId(accountId.value). + userId(userId.value). + forView(viewId.value). + tag(tagText). + date(datePosted).saveTheRecord() + } def deleteTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(tagId : String) : Box[Boolean] = { //use delete with find query to avoid concurrency issues OBPTag.delete(OBPTag.getFindQuery(bankId, accountId, transactionId, tagId)) @@ -34,6 +43,9 @@ private object MongoTransactionTags extends Tags { //we don't have any useful information here so just assume it worked Full(true) } + def deleteTagOnAccount(bankId : BankId, accountId : AccountId)(tagId : String) : Box[Boolean] = { + deleteTag(bankId, accountId, TransactionId(""))(tagId) + } def bulkDeleteTags(bankId: BankId, accountId: AccountId): Boolean = ??? diff --git a/obp-api/src/main/scala/code/metadata/tags/Tags.scala b/obp-api/src/main/scala/code/metadata/tags/Tags.scala index d68e3490d..63fb5c243 100644 --- a/obp-api/src/main/scala/code/metadata/tags/Tags.scala +++ b/obp-api/src/main/scala/code/metadata/tags/Tags.scala @@ -25,8 +25,10 @@ trait Tags { def getTags(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(viewId : ViewId) : List[TransactionTag] def addTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(userId: UserPrimaryKey, viewId : ViewId, tagText : String, datePosted : Date) : Box[TransactionTag] + def addTagOnAccount(bankId : BankId, accountId : AccountId)(userId: UserPrimaryKey, viewId : ViewId, tagText : String, datePosted : Date) : Box[TransactionTag] //TODO: viewId? should tagId always be unique -> in that case bankId, accountId, and transactionId would not be required def deleteTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(tagId : String) : Box[Boolean] + def deleteTagOnAccount(bankId : BankId, accountId : AccountId)(tagId : String) : Box[Boolean] def bulkDeleteTags(bankId: BankId, accountId: AccountId) : Boolean } @@ -34,7 +36,9 @@ trait Tags { class RemotedataTagsCaseClasses{ case class getTags(bankId : BankId, accountId : AccountId, transactionId: TransactionId, viewId : ViewId) case class addTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId, userId: UserPrimaryKey, viewId : ViewId, tagText : String, datePosted : Date) + case class addTagOnAccount(bankId : BankId, accountId : AccountId, userId: UserPrimaryKey, viewId : ViewId, tagText : String, datePosted : Date) case class deleteTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId, tagId : String) + case class deleteTagOnAccount(bankId : BankId, accountId : AccountId, tagId : String) case class bulkDeleteTags(bankId: BankId, accountId: AccountId) } diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataTags.scala b/obp-api/src/main/scala/code/remotedata/RemotedataTags.scala index d52878e4a..a53811016 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataTags.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataTags.scala @@ -23,10 +23,16 @@ object RemotedataTags extends ObpActorInit with Tags { def addTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(userId: UserPrimaryKey, viewId : ViewId, tagText : String, datePosted : Date) : Box[TransactionTag] = getValueFromFuture( (actor ? cc.addTag(bankId, accountId, transactionId, userId, viewId, tagText, datePosted)).mapTo[Box[TransactionTag]] ) + def addTagOnAccount(bankId : BankId, accountId : AccountId)(userId: UserPrimaryKey, viewId : ViewId, tagText : String, datePosted : Date) : Box[TransactionTag] = getValueFromFuture( + (actor ? cc.addTagOnAccount(bankId, accountId, userId, viewId, tagText, datePosted)).mapTo[Box[TransactionTag]] + ) def deleteTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId)(tagId : String) : Box[Boolean] = getValueFromFuture( (actor ? cc.deleteTag(bankId, accountId, transactionId, tagId)).mapTo[Box[Boolean]] ) + def deleteTagOnAccount(bankId : BankId, accountId : AccountId)(tagId : String) : Box[Boolean] = getValueFromFuture( + (actor ? cc.deleteTagOnAccount(bankId, accountId, tagId)).mapTo[Box[Boolean]] + ) def bulkDeleteTags(bankId: BankId, accountId: AccountId): Boolean = getValueFromFuture( (actor ? cc.bulkDeleteTags(bankId, accountId)).mapTo[Boolean] diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataTagsActor.scala b/obp-api/src/main/scala/code/remotedata/RemotedataTagsActor.scala index 579273069..cf5a24754 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataTagsActor.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataTagsActor.scala @@ -21,11 +21,19 @@ class RemotedataTagsActor extends Actor with ObpActorHelper with MdcLoggable { case cc.addTag(bankId, accountId, transactionId, userId, viewId, text, datePosted) => logger.debug("addTag(" + bankId +", "+ accountId +", "+ transactionId +", "+ text +", "+ text +", "+ datePosted +")") sender ! (mapper.addTag(bankId, accountId, transactionId)(userId, viewId, text, datePosted)) + + case cc.addTagOnAccount(bankId, accountId, userId, viewId, text, datePosted) => + logger.debug("addTag(" + bankId +", "+ accountId +", " + text +", "+ text +", "+ datePosted +")") + sender ! (mapper.addTagOnAccount(bankId, accountId)(userId, viewId, text, datePosted)) case cc.deleteTag(bankId : BankId, accountId : AccountId, transactionId: TransactionId, tagId : String) => logger.debug("deleteTag(" + bankId +", "+ accountId +", "+ transactionId + tagId +")") sender ! (mapper.deleteTag(bankId, accountId, transactionId)(tagId)) + case cc.deleteTagOnAccount(bankId : BankId, accountId : AccountId, tagId : String) => + logger.debug("deleteTag(" + bankId +", "+ accountId +", "+ tagId +")") + sender ! (mapper.deleteTagOnAccount(bankId, accountId)(tagId)) + case cc.bulkDeleteTags(bankId: BankId, accountId: AccountId) => logger.debug("bulkDeleteTags(" + bankId +", "+ accountId + ")") sender ! (mapper.bulkDeleteTags(bankId, accountId))