diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala index f79863d9b4..a8d2f0ddad 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala @@ -17,7 +17,6 @@ import org.hyperledger.identus.connect.core.service.ConnectionService import org.hyperledger.identus.mercury.model.* import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* -import zio.{IO, ZIO} import java.util.UUID import scala.util.Try diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletNonSecretStorage.scala index d817f0ba58..6a45bd2b09 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletNonSecretStorage.scala @@ -1,144 +1,71 @@ package org.hyperledger.identus.agent.walletapi.sql -import cats.data.NonEmptyList import doobie.* -import doobie.implicits.* -import doobie.postgres.implicits.* import doobie.util.transactor.Transactor import org.hyperledger.identus.agent.walletapi.model.Wallet +import org.hyperledger.identus.agent.walletapi.sql.model.{WalletNotificationSql, WalletSql} +import org.hyperledger.identus.agent.walletapi.sql.model as db import org.hyperledger.identus.agent.walletapi.storage.WalletNonSecretStorage import org.hyperledger.identus.event.notification.EventNotificationConfig import org.hyperledger.identus.shared.db.ContextAwareTask -import org.hyperledger.identus.shared.db.Implicits.{*, given} +import org.hyperledger.identus.shared.db.Implicits.* import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import zio.* -import java.net.URL -import java.time.Instant import java.util.UUID class JdbcWalletNonSecretStorage(xa: Transactor[ContextAwareTask]) extends WalletNonSecretStorage { override def createWallet(wallet: Wallet, seedDigest: Array[Byte]): UIO[Wallet] = { - val cxnIO = (row: WalletRow) => sql""" - | INSERT INTO public.wallet( - | wallet_id, - | name, - | created_at, - | updated_at, - | seed_digest - | ) - | VALUES ( - | ${row.id}, - | ${row.name}, - | ${row.createdAt}, - | ${row.updatedAt}, - | ${seedDigest} - | ) - """.stripMargin.update - - val row = WalletRow.from(wallet) - cxnIO(row).run + WalletSql + .insert(db.Wallet.from(wallet, seedDigest)) .transactWithoutContext(xa) - .as(wallet) .orDie + .map(_.toModel) } override def findWalletById(walletId: WalletId): UIO[Option[Wallet]] = { - val cxnIO = - sql""" - | SELECT - | wallet_id, - | name, - | created_at, - | updated_at - | FROM public.wallet - | WHERE wallet_id = $walletId - """.stripMargin - .query[WalletRow] - .option - - cxnIO + WalletSql + .findByIds(Seq(walletId)) .transactWithoutContext(xa) - .map(_.map(_.toDomain)) .orDie + .map(_.headOption.map(_.toModel)) } override def findWalletBySeed(seedDigest: Array[Byte]): UIO[Option[Wallet]] = { - val cxnIO = - sql""" - | SELECT - | wallet_id, - | name, - | created_at, - | updated_at - | FROM public.wallet - | WHERE seed_digest = $seedDigest - """.stripMargin - .query[WalletRow] - .option - - cxnIO + WalletSql + .findBySeed(seedDigest) .transactWithoutContext(xa) - .map(_.map(_.toDomain)) .orDie + .map(_.headOption.map(_.toModel)) } override def getWallets(walletIds: Seq[WalletId]): UIO[Seq[Wallet]] = { walletIds match case Nil => ZIO.succeed(Nil) - case head +: tail => - val nel = NonEmptyList.of(head, tail*) - val conditionFragment = Fragments.in(fr"wallet_id", nel) - val cxnIO = - sql""" - | SELECT - | wallet_id, - | name, - | created_at, - | updated_at - | FROM public.wallet - | WHERE $conditionFragment - """.stripMargin - .query[WalletRow] - .to[List] - - cxnIO + case ids => + WalletSql + .findByIds(ids) .transactWithoutContext(xa) - .map(_.map(_.toDomain)) .orDie + .map(_.map(_.toModel)) } override def listWallet( offset: Option[Int], limit: Option[Int] ): UIO[(Seq[Wallet], RuntimeFlags)] = { - val countCxnIO = - sql""" - | SELECT COUNT(*) - | FROM public.wallet - """.stripMargin - .query[Int] - .unique - - val baseFr = - sql""" - | SELECT - | wallet_id, - | name, - | created_at, - | updated_at - | FROM public.wallet - | ORDER BY created_at - """.stripMargin - val withOffsetFr = offset.fold(baseFr)(offsetValue => baseFr ++ fr"OFFSET $offsetValue") - val withOffsetAndLimitFr = limit.fold(withOffsetFr)(limitValue => withOffsetFr ++ fr"LIMIT $limitValue") - val walletsCxnIO = withOffsetAndLimitFr.query[WalletRow].to[List] + val countCxnIO = WalletSql.lookupCount() + + val walletsCxnIO = WalletSql.lookup( + offset = offset.getOrElse(0), + limit = limit.getOrElse(1000) + ) val effect = for { totalCount <- countCxnIO - rows <- walletsCxnIO.map(_.map(_.toDomain)) - } yield (rows, totalCount) + rows <- walletsCxnIO.map(_.map(_.toModel)) + } yield (rows, totalCount.toInt) effect .transactWithoutContext(xa) @@ -146,72 +73,33 @@ class JdbcWalletNonSecretStorage(xa: Transactor[ContextAwareTask]) extends Walle } override def countWalletNotification: URIO[WalletAccessContext, Int] = { - val countIO = sql""" - | SELECT COUNT(*) - | FROM public.wallet_notification - """.stripMargin - .query[Int] - .unique - - countIO + WalletNotificationSql + .lookupCount() .transactWallet(xa) + .map(_.toInt) .orDie } override def createWalletNotification( config: EventNotificationConfig ): URIO[WalletAccessContext, Unit] = { - val insertIO = (row: WalletNofiticationRow) => sql""" - | INSERT INTO public.wallet_notification ( - | id, - | wallet_id, - | url, - | custom_headers, - | created_at - | ) VALUES ( - | ${row.id}, - | ${row.walletId}, - | ${row.url}, - | ${row.customHeaders}, - | ${row.createdAt} - | ) - """.stripMargin.update - - val row = WalletNofiticationRow.from(config) - - insertIO(row).run + WalletNotificationSql + .insert(db.WalletNotification.from(config)) .transactWallet(xa) .ensureOneAffectedRowOrDie } override def walletNotification: URIO[WalletAccessContext, Seq[EventNotificationConfig]] = { - val cxn = - sql""" - | SELECT - | id, - | wallet_id, - | url, - | custom_headers, - | created_at - | FROM public.wallet_notification - """.stripMargin - .query[WalletNofiticationRow] - .to[List] - - cxn + WalletNotificationSql + .lookup() .transactWallet(xa) - .flatMap(rows => ZIO.foreach(rows) { row => ZIO.fromTry(row.toDomain) }) + .map(_.map(_.toModel)) .orDie } override def deleteWalletNotification(id: UUID): URIO[WalletAccessContext, Unit] = { - val cxn = - sql""" - | DELETE FROM public.wallet_notification - | WHERE id = $id - """.stripMargin.update - - cxn.run + WalletNotificationSql + .delete(id) .transactWallet(xa) .ensureOneAffectedRowOrDie } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/Wallet.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/Wallet.scala new file mode 100644 index 0000000000..b552d27599 --- /dev/null +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/Wallet.scala @@ -0,0 +1,118 @@ +package org.hyperledger.identus.agent.walletapi.sql.model + +import io.getquill.* +import io.getquill.context.json.PostgresJsonExtensions +import io.getquill.doobie.DoobieContext +import org.hyperledger.identus.agent.walletapi.model +import org.hyperledger.identus.event.notification.EventNotificationConfig +import org.hyperledger.identus.shared.models.WalletId + +import java.net.URL +import java.time.Instant +import java.util.UUID + +final case class Wallet( + walletId: WalletId, + name: String, + createdAt: Instant, + updatedAt: Instant, + seedDigest: Array[Byte] +) + +object Wallet { + def from(wallet: model.Wallet, seedDigest: Array[Byte]): Wallet = { + Wallet( + walletId = wallet.id, + name = wallet.name, + createdAt = wallet.createdAt, + updatedAt = wallet.updatedAt, + seedDigest = seedDigest + ) + } + + extension (wallet: Wallet) { + def toModel: model.Wallet = + model.Wallet( + id = wallet.walletId, + name = wallet.name, + createdAt = wallet.createdAt, + updatedAt = wallet.updatedAt + ) + } +} + +object WalletSql extends DoobieContext.Postgres(SnakeCase) { + + def insert(wallet: Wallet) = run { + quote( + query[Wallet] + .insertValue(lift(wallet)) + ).returning(w => w) + } + + def findByIds(walletIds: Seq[WalletId]) = run { + quote(query[Wallet].filter(p => liftQuery(walletIds.map(_.toUUID)).contains(p.walletId))) + } + + def findBySeed(seedDigest: Array[Byte]) = run { + quote(query[Wallet].filter(_.seedDigest == lift(seedDigest)).take(1)) + } + + def lookupCount() = run { quote(query[Wallet].size) } + + def lookup(offset: Int, limit: Int) = run { + quote(query[Wallet].drop(lift(offset)).take(lift(limit))) + } +} + +final case class WalletNotification( + id: UUID, + walletId: WalletId, + url: URL, + customHeaders: JsonValue[Map[String, String]], + createdAt: Instant, +) + +object WalletNotification { + def from(notification: EventNotificationConfig): WalletNotification = { + WalletNotification( + id = notification.id, + walletId = notification.walletId, + url = notification.url, + customHeaders = JsonValue(notification.customHeaders), + createdAt = notification.createdAt, + ) + } + + extension (notification: WalletNotification) { + def toModel: EventNotificationConfig = + EventNotificationConfig( + id = notification.id, + walletId = notification.walletId, + url = notification.url, + customHeaders = notification.customHeaders.value, + createdAt = notification.createdAt, + ) + } +} + +object WalletNotificationSql extends DoobieContext.Postgres(SnakeCase), PostgresJsonExtensions { + def insert(notification: WalletNotification) = run { + quote( + query[WalletNotification] + .insertValue(lift(notification)) + ) + } + + def lookupCount() = run { + quote(query[WalletNotification].size) + } + + def lookup() = run { + quote(query[WalletNotification]) + } + + def delete(id: UUID) = run { + quote(query[WalletNotification].filter(_.id == lift(id)).delete) + } +} diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/package.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/package.scala new file mode 100644 index 0000000000..464d924a26 --- /dev/null +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/package.scala @@ -0,0 +1,15 @@ +package org.hyperledger.identus.agent.walletapi.sql + +import io.getquill.MappedEncoding +import org.hyperledger.identus.shared.models.WalletId + +import java.net.{URI, URL} +import java.util.UUID + +package object model { + given MappedEncoding[WalletId, UUID] = MappedEncoding[WalletId, UUID](_.toUUID) + given MappedEncoding[UUID, WalletId] = MappedEncoding[UUID, WalletId](WalletId.fromUUID) + + given MappedEncoding[URL, String] = MappedEncoding[URL, String](_.toString) + given MappedEncoding[String, URL] = MappedEncoding[String, URL](URI(_).toURL) +} diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/package.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/package.scala index 300b262a83..c955c640cf 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/package.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/package.scala @@ -4,11 +4,8 @@ import com.nimbusds.jose.jwk.OctetKeyPair import doobie.* import doobie.postgres.implicits.* import doobie.util.invariant.InvalidEnum -import io.circe.* -import io.circe.parser.* -import io.circe.syntax.* import io.iohk.atala.prism.protos.node_models -import org.hyperledger.identus.agent.walletapi.model.{KeyManagementMode, ManagedDIDState, PublicationState, Wallet} +import org.hyperledger.identus.agent.walletapi.model.{KeyManagementMode, ManagedDIDState, PublicationState} import org.hyperledger.identus.castor.core.model.did.{ EllipticCurve, InternalKeyPurpose, @@ -18,7 +15,6 @@ import org.hyperledger.identus.castor.core.model.did.{ VerificationRelationship } import org.hyperledger.identus.castor.core.model.ProtoModelHelper.* -import org.hyperledger.identus.event.notification.EventNotificationConfig import org.hyperledger.identus.shared.crypto.jwk.JWK import org.hyperledger.identus.shared.models.WalletId import zio.json.* @@ -27,7 +23,6 @@ import zio.json.ast.Json.* import java.net.{URI, URL} import java.time.Instant -import java.util.UUID import scala.collection.immutable.ArraySeq import scala.util.Try @@ -213,64 +208,4 @@ package object sql { } } - final case class WalletRow( - id: WalletId, - name: String, - createdAt: Instant, - updatedAt: Instant - ) { - def toDomain: Wallet = { - Wallet( - id: WalletId, - name: String, - createdAt: Instant, - updatedAt: Instant - ) - } - } - - object WalletRow { - def from(wallet: Wallet): WalletRow = { - WalletRow( - id = wallet.id, - name = wallet.name, - createdAt = wallet.createdAt, - updatedAt = wallet.updatedAt - ) - } - } - - final case class WalletNofiticationRow( - id: UUID, - walletId: WalletId, - url: URL, - customHeaders: String, - createdAt: Instant, - ) { - def toDomain: Try[EventNotificationConfig] = { - decode[Map[String, String]](customHeaders).toTry - .map { headers => - EventNotificationConfig( - id = id, - walletId = walletId, - url = url, - customHeaders = headers, - createdAt = createdAt, - ) - } - } - } - - object WalletNofiticationRow { - def from(config: EventNotificationConfig): WalletNofiticationRow = { - WalletNofiticationRow( - id = config.id, - walletId = config.walletId, - url = config.url, - customHeaders = config.customHeaders.asJson.noSpacesSortKeys, - createdAt = config.createdAt, - ) - } - } - } diff --git a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala index 1418eb3410..905438b6d9 100644 --- a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala +++ b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala @@ -1,7 +1,6 @@ package org.hyperledger.identus.mercury.protocol.presentproof -import org.hyperledger.identus.mercury.model.AttachmentDescriptor -import org.hyperledger.identus.mercury.model.DidId +import org.hyperledger.identus.mercury.model.{AttachmentDescriptor, DidId} import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation object PresentProofInvitation { def makeInvitation( diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala index 0a781ae851..5981338168 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala @@ -6,7 +6,6 @@ import org.hyperledger.identus.pollux.anoncreds.AnoncredPresentation import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.error.PresentationError import org.hyperledger.identus.pollux.core.model.presentation.* -import org.hyperledger.identus.pollux.core.model.presentation.Options import org.hyperledger.identus.pollux.core.service.serdes.{AnoncredCredentialProofsV1, AnoncredPresentationRequestV1} import org.hyperledger.identus.pollux.sdjwt.{HolderPrivateKey, PresentationCompact} import org.hyperledger.identus.pollux.vc.jwt.* @@ -16,7 +15,6 @@ import zio.json.ast import java.time.Instant import java.util.UUID -import java.util as ju trait PresentationService { def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID]