Skip to content

Commit

Permalink
fix: migrate wallet nonsecret storage to quill (#1290)
Browse files Browse the repository at this point in the history
Signed-off-by: Pat Losoponkul <[email protected]>
  • Loading branch information
patlo-iog authored Aug 19, 2024
1 parent 2f09550 commit 525b3bc
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 217 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,217 +1,105 @@
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)
.orDie
}

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
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 525b3bc

Please sign in to comment.