From 83371f6fbded3602dd7ecb79dbbd463e4800193b Mon Sep 17 00:00:00 2001 From: Arav Date: Thu, 10 Oct 2024 16:51:40 +0530 Subject: [PATCH] Created NameTransaction model and updated logic to sync usernames in this table with Identities --- app/constants/Module.scala | 1 + app/constants/Response.scala | 3 + app/models/blockchain/Identity.scala | 9 +- .../masterTransaction/NameTransaction.scala | 102 ++++++++++++++++++ conf/evolutions/default/1.sql | 10 ++ 5 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 app/models/masterTransaction/NameTransaction.scala diff --git a/app/constants/Module.scala b/app/constants/Module.scala index 9f8b1eb29..149bfad10 100644 --- a/app/constants/Module.scala +++ b/app/constants/Module.scala @@ -54,6 +54,7 @@ object Module { val BLOCKCHAIN_PROPOSAL_VOTE = "BLOCKCHAIN_PROPOSAL_VOTE" val BLOCKCHAIN_PROPOSAL_DEPOSIT = "BLOCKCHAIN_PROPOSAL_DEPOSIT" val BLOCKCHAIN_IBC_TRANSFER = "BLOCKCHAIN_IBC_TRANSFER" + val BLOCKCHAIN_NAME_TRANSACTION = "BLOCKCHAIN_NAME_TRANSACTION" //Archive val ARCHIVE_BLOCK = "ARCHIVE_BLOCK" diff --git a/app/constants/Response.scala b/app/constants/Response.scala index 12db438f8..3a5382bf4 100644 --- a/app/constants/Response.scala +++ b/app/constants/Response.scala @@ -152,6 +152,9 @@ object Response { val PARAMETER_NOT_FOUND = new Failure("PARAMETER_NOT_FOUND") val PARAMETER_TYPE_NOT_FOUND = new Failure("PARAMETER_TYPE_NOT_FOUND") val EMPTY_QUERY = new Failure("EMPTY_QUERY") + val NAME_TRANSACTION_INSERT_FAILED = new Failure("NAME_TRANSACTION_INSERT_FAILED") + val NAME_TRANSACTION_UPSERT_FAILED = new Failure("NAME_TRANSACTION_INSERT_FAILED") + val NAME_TRANSACTION_NOT_FOUND = new Failure("NAME_TRANSACTION_NOT_FOUND") class Failure(private val response: String) { val message: String = PREFIX + FAILURE_PREFIX + response diff --git a/app/models/blockchain/Identity.scala b/app/models/blockchain/Identity.scala index 281b98e99..46b844eda 100644 --- a/app/models/blockchain/Identity.scala +++ b/app/models/blockchain/Identity.scala @@ -13,6 +13,7 @@ import schema.id.base._ import schema.list.PropertyList import schema.property.Property import schema.property.base.MetaProperty +import models.masterTransaction.{NameTransaction, NameTransactions} import schema.qualified.{Immutables, Mutables} import slick.jdbc.H2Profile.api._ @@ -92,6 +93,7 @@ private[blockchain] object Identities { class Identities @Inject()( blockchainMaintainers: Maintainers, blockchainClassifications: Classifications, + nameTransactions: NameTransactions, protected val dbConfigProvider: DatabaseConfigProvider )(implicit val executionContext: ExecutionContext) extends GenericDaoImpl[Identities.IdentityTable, Identity, Array[Byte]]() { @@ -207,10 +209,13 @@ class Identities @Inject()( val mutables = Mutables(PropertyList(Seq(schema.constants.Properties.AuthenticationProperty.mutate(ListData(Seq(AccAddressData(msg.getFrom))))))) val identityID = schema.utilities.ID.getIdentityID(classificationID = schema.document.NameIdentity.DocumentClassificationID, immutables = immutables) val identity = Identity(id = identityID.getBytes, idString = identityID.asString, classificationID = schema.document.NameIdentity.DocumentClassificationID.getBytes, immutables = immutables.getProtoBytes, mutables = mutables.getProtoBytes) - val add = Service.add(identity) + val nameTransaction = NameTransaction(username = msg.getName.getIDString, email = None, idString = identityID.asString, isClaimed = false) + val addIdentity = Service.add(identity) + val addNameTransaction = nameTransactions.Service.insertOrUpdate(nameTransaction) (for { - _ <- add + _ <- addIdentity + _ <- addNameTransaction } yield msg.getFrom).recover { case _: BaseException => logger.error(schema.constants.Messages.IDENTITY_NAME + ": " + constants.Response.TRANSACTION_PROCESSING_FAILED.logMessage + " at height " + header.height.toString) msg.getFrom diff --git a/app/models/masterTransaction/NameTransaction.scala b/app/models/masterTransaction/NameTransaction.scala new file mode 100644 index 000000000..0adb879f2 --- /dev/null +++ b/app/models/masterTransaction/NameTransaction.scala @@ -0,0 +1,102 @@ +package models.masterTransaction + +import play.api.Logger +import play.api.db.slick.DatabaseConfigProvider +import slick.jdbc.JdbcProfile +import javax.inject.{Inject, Singleton} +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} +import exceptions.BaseException +import org.postgresql.util.PSQLException + +case class NameTransaction(username: String, email: Option[String], idString: String, isClaimed: Boolean) + +@Singleton +class NameTransactions @Inject()( + protected val databaseConfigProvider: DatabaseConfigProvider + )(implicit executionContext: ExecutionContext) { + + val databaseConfig = databaseConfigProvider.get[JdbcProfile] + val db = databaseConfig.db + + private implicit val logger: Logger = Logger(this.getClass) + private implicit val module: String = constants.Module.BLOCKCHAIN_NAME_TRANSACTION + + import databaseConfig.profile.api._ + + private[models] val nameTransactionTable = TableQuery[NameTransactionTable] + + case class NameTransactionSerialized(username: String, email: Option[String], idString: String, isClaimed: Boolean) { + def deserialize: NameTransaction = NameTransaction(username = username, email = email, idString = idString, isClaimed = isClaimed) + } + + def serialize(nameTransaction: NameTransaction): NameTransactionSerialized = + NameTransactionSerialized( + username = nameTransaction.username, + email = nameTransaction.email, + idString = nameTransaction.idString, + isClaimed = nameTransaction.isClaimed + ) + + // Insert new NameTransaction + def add(nameTransaction: NameTransaction): Future[String] = db.run((nameTransactionTable returning nameTransactionTable.map(_.idString) += serialize(nameTransaction)).asTry).map { + case Success(result) => result + case Failure(exception) => exception match { + case psqlException: PSQLException => throw new BaseException(constants.Response.NAME_TRANSACTION_INSERT_FAILED, psqlException) + } + } + + // Upsert (insert or update) NameTransaction + def upsert(nameTransaction: NameTransaction): Future[Int] = db.run(nameTransactionTable.insertOrUpdate(serialize(nameTransaction)).asTry).map { + case Success(result) => result + case Failure(exception) => exception match { + case psqlException: PSQLException => throw new BaseException(constants.Response.NAME_TRANSACTION_UPSERT_FAILED, psqlException) + } + } + + // Fetch by idString + private def tryGetByIdString(idString: String): Future[NameTransactionSerialized] = db.run(nameTransactionTable.filter(_.idString === idString).result.head.asTry).map { + case Success(result) => result + case Failure(exception) => exception match { + case noSuchElementException: NoSuchElementException => throw new BaseException(constants.Response.NAME_TRANSACTION_NOT_FOUND, noSuchElementException) + } + } + + // Fetch by username + private def tryGetByUsername(username: String): Future[NameTransactionSerialized] = db.run(nameTransactionTable.filter(_.username === username).result.head.asTry).map { + case Success(result) => result + case Failure(exception) => exception match { + case noSuchElementException: NoSuchElementException => throw new BaseException(constants.Response.NAME_TRANSACTION_NOT_FOUND, noSuchElementException) + } + } + + // Get NameTransaction by idString + def getByIdString(idString: String): Future[Option[NameTransactionSerialized]] = db.run(nameTransactionTable.filter(_.idString === idString).result.headOption) + + // Get NameTransaction by username + def getByUsername(username: String): Future[Option[NameTransactionSerialized]] = db.run(nameTransactionTable.filter(_.username === username).result.headOption) + + private[models] class NameTransactionTable(tag: Tag) extends Table[NameTransactionSerialized](tag, "NameTransaction") { + + def * = (username, email, idString, isClaimed) <> (NameTransactionSerialized.tupled, NameTransactionSerialized.unapply) + + def username = column[String]("username") + def email = column[Option[String]]("email") + def idString = column[String]("idString", O.PrimaryKey) + def isClaimed = column[Boolean]("isClaimed") + } + + // Service to provide additional functionality + object Service { + def tryGet(idString: String): Future[NameTransaction] = tryGetByIdString(idString).map(_.deserialize) + + def tryFindByUsername(username: String): Future[NameTransaction] = tryGetByUsername(username).map(_.deserialize) + + def insertOrUpdate(nameTransaction: NameTransaction): Future[Int] = upsert(nameTransaction) + + def get(idString: String): Future[Option[NameTransaction]] = getByIdString(idString).map(_.map(_.deserialize)) + + def findByUsername(username: String): Future[Option[NameTransaction]] = getByUsername(username).map(_.map(_.deserialize)) + } + +} diff --git a/conf/evolutions/default/1.sql b/conf/evolutions/default/1.sql index 737bd2b17..c25718861 100644 --- a/conf/evolutions/default/1.sql +++ b/conf/evolutions/default/1.sql @@ -421,6 +421,15 @@ CREATE TABLE IF NOT EXISTS MASTER_TRANSACTION."WalletTransaction" PRIMARY KEY ("address", "txHash") ); +CREATE TABLE IF NOT EXISTS MASTER_TRANSACTION."NameTransaction" +( + "username" VARCHAR NOT NULL, + "email" VARCHAR, + "idString" VARCHAR NOT NULL, + "isClaimed" BOOLEAN NOT NULL, + PRIMARY KEY ("username", "idString") +); + ALTER TABLE BLOCKCHAIN."Asset" ADD CONSTRAINT Asset_ClassificationID FOREIGN KEY ("classificationID") REFERENCES BLOCKCHAIN."Classification" ("id"); ALTER TABLE BLOCKCHAIN."Delegation" @@ -691,6 +700,7 @@ DROP TABLE IF EXISTS MASTER_TRANSACTION."WalletTransaction" CASCADE; DROP TABLE IF EXISTS MASTER_TRANSACTION."Notification" CASCADE; DROP TABLE IF EXISTS MASTER_TRANSACTION."TokenPrice" CASCADE; DROP TABLE IF EXISTS MASTER_TRANSACTION."ValidatorTransaction" CASCADE; +DROP TABLE IF EXISTS MASTER_TRANSACTION."NameTransaction" CASCADE; DROP SCHEMA IF EXISTS ANALYTICS CASCADE; DROP SCHEMA IF EXISTS BLOCKCHAIN CASCADE;