From a980a979254c0848e5273b823ab7bca1cd55ad87 Mon Sep 17 00:00:00 2001 From: mikeplotean Date: Tue, 17 Oct 2023 13:13:44 +0300 Subject: [PATCH] feat: sqlite db-migration refactor: db related services --- k8s/deployment-portal.yaml | 13 +- src/main/kotlin/id/walt/Application.kt | 1 - .../id/walt/config/DatabaseConfiguration.kt | 3 +- src/main/kotlin/id/walt/db/Db.kt | 91 ++++---- .../id/walt/db/models/AccountCredentials.kt | 8 + .../kotlin/id/walt/db/models/AccountDids.kt | 10 + .../kotlin/id/walt/db/models/AccountKeys.kt | 8 + .../kotlin/id/walt/db/models/Credentials.kt | 8 + src/main/kotlin/id/walt/db/models/Dids.kt | 9 + src/main/kotlin/id/walt/db/models/Keys.kt | 8 + .../id/walt/db/models/WalletCredentials.kt | 11 - .../kotlin/id/walt/db/models/WalletDids.kt | 14 -- .../kotlin/id/walt/db/models/WalletKeys.kt | 11 - .../db/models/WalletOperationHistories.kt | 24 +- .../AccountCredentialsRepository.kt | 28 +++ .../db/repositories/AccountDidsRepository.kt | 37 ++++ .../db/repositories/AccountKeysRepository.kt | 28 +++ .../repositories/AccountWalletsRepository.kt | 31 +++ .../db/repositories/AccountsRepository.kt | 28 +++ .../db/repositories/CredentialsRepository.kt | 28 +++ .../id/walt/db/repositories/DidsRepository.kt | 30 +++ .../walt/db/repositories/EmailsRepository.kt | 28 +++ .../id/walt/db/repositories/KeysRepository.kt | 27 +++ .../id/walt/db/repositories/Repository.kt | 55 +++++ .../WalletOperationHistoriesRepository.kt | 35 +++ .../walt/db/repositories/WalletsRepository.kt | 28 +++ src/main/kotlin/id/walt/service/Did.kt | 5 +- .../id/walt/service/SSIKit2WalletService.kt | 206 +++++++----------- .../id/walt/service/WalletKitWalletService.kt | 12 +- .../kotlin/id/walt/service/WalletService.kt | 4 +- .../service/credentials/CredentialsService.kt | 83 +++++++ .../walt/service/dids/DidInsertDataObject.kt | 9 + .../kotlin/id/walt/service/dids/DidService.kt | 97 +++++++++ .../walt/service/dids/DidUpdateDataObject.kt | 15 ++ .../service/dto/WalletOperationHistory.kt | 22 ++ .../id/walt/service/keys/KeysService.kt | 79 +++++-- .../web/controllers/ExchangeController.kt | 2 +- ...untdids_accountkeys_accountcredentials.sql | 70 ++++++ 38 files changed, 945 insertions(+), 261 deletions(-) create mode 100644 src/main/kotlin/id/walt/db/models/AccountCredentials.kt create mode 100644 src/main/kotlin/id/walt/db/models/AccountDids.kt create mode 100644 src/main/kotlin/id/walt/db/models/AccountKeys.kt create mode 100644 src/main/kotlin/id/walt/db/models/Credentials.kt create mode 100644 src/main/kotlin/id/walt/db/models/Dids.kt create mode 100644 src/main/kotlin/id/walt/db/models/Keys.kt delete mode 100644 src/main/kotlin/id/walt/db/models/WalletCredentials.kt delete mode 100644 src/main/kotlin/id/walt/db/models/WalletDids.kt delete mode 100644 src/main/kotlin/id/walt/db/models/WalletKeys.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/AccountCredentialsRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/AccountDidsRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/AccountKeysRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/AccountWalletsRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/AccountsRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/CredentialsRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/DidsRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/EmailsRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/KeysRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/Repository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/WalletOperationHistoriesRepository.kt create mode 100644 src/main/kotlin/id/walt/db/repositories/WalletsRepository.kt create mode 100644 src/main/kotlin/id/walt/service/credentials/CredentialsService.kt create mode 100644 src/main/kotlin/id/walt/service/dids/DidInsertDataObject.kt create mode 100644 src/main/kotlin/id/walt/service/dids/DidService.kt create mode 100644 src/main/kotlin/id/walt/service/dids/DidUpdateDataObject.kt create mode 100644 src/main/kotlin/id/walt/service/dto/WalletOperationHistory.kt create mode 100644 src/main/resources/db/sqlite/V4__create_tables_keys_dids_credentials_accountdids_accountkeys_accountcredentials.sql diff --git a/k8s/deployment-portal.yaml b/k8s/deployment-portal.yaml index 61a1f01..28df41a 100644 --- a/k8s/deployment-portal.yaml +++ b/k8s/deployment-portal.yaml @@ -204,7 +204,7 @@ spec: imagePullPolicy: Always env: - name: NUXT_PUBLIC_ISSUER_URL - value: "https://issuer.walt.id" + value: "https://portal.walt.id" ports: - containerPort: 3000 name: http-api @@ -244,11 +244,18 @@ spec: service: name: wallet-backend port: - number: 80 + name: http + - path: /swagger + pathType: Prefix + backend: + service: + name: wallet-backend + port: + name: http - path: / pathType: Prefix backend: service: name: wallet-frontend port: - number: 80 \ No newline at end of file + name: http \ No newline at end of file diff --git a/src/main/kotlin/id/walt/Application.kt b/src/main/kotlin/id/walt/Application.kt index 8b38788..fdd29df 100644 --- a/src/main/kotlin/id/walt/Application.kt +++ b/src/main/kotlin/id/walt/Application.kt @@ -32,7 +32,6 @@ suspend fun main(args: Array) { ConfigManager.loadConfigs(args) Db.start() - Db.init() // val webConfig = ConfigManager.getConfig() // log.info { "Starting web server (binding to ${webConfig.webHost}, listening on port ${webConfig.webPort})..." } diff --git a/src/main/kotlin/id/walt/config/DatabaseConfiguration.kt b/src/main/kotlin/id/walt/config/DatabaseConfiguration.kt index 3eca5b3..b8aa2a6 100644 --- a/src/main/kotlin/id/walt/config/DatabaseConfiguration.kt +++ b/src/main/kotlin/id/walt/config/DatabaseConfiguration.kt @@ -3,8 +3,7 @@ package id.walt.config import com.zaxxer.hikari.HikariDataSource data class DatabaseConfiguration( - val database: String, - val recreate_schema: Boolean = false + val database: String ) : WalletConfig data class DatasourceConfiguration( diff --git a/src/main/kotlin/id/walt/db/Db.kt b/src/main/kotlin/id/walt/db/Db.kt index f04535d..f38b235 100644 --- a/src/main/kotlin/id/walt/db/Db.kt +++ b/src/main/kotlin/id/walt/db/Db.kt @@ -4,9 +4,18 @@ import id.walt.config.ConfigManager import id.walt.config.DatabaseConfiguration import id.walt.config.DatasourceConfiguration import id.walt.db.models.* +import id.walt.db.repositories.DbCredential +import id.walt.db.repositories.DbDid +import id.walt.db.repositories.DbKey +import id.walt.service.Did import id.walt.service.account.AccountsService +import id.walt.service.credentials.CredentialsService +import id.walt.service.dids.DidInsertDataObject +import id.walt.service.dids.DidsService +import id.walt.service.keys.KeysService import id.walt.web.model.EmailLoginRequest import io.github.oshai.kotlinlogging.KotlinLogging +import org.flywaydb.core.Flyway import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.StdOutSqlLogger @@ -15,6 +24,7 @@ import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.bridge.SLF4JBridgeHandler import java.sql.Connection +import java.util.* object Db { @@ -25,11 +35,11 @@ object Db { val databaseConfig = ConfigManager.getConfig() //migrate - /*Flyway.configure() + Flyway.configure() .locations(databaseConfig.database.replace(".", "/")) .dataSource(datasourceConfig.hikariDataSource) .load() - .migrate()*/ + .migrate() // connect log.info { "Connecting to database at \"${datasourceConfig.hikariDataSource.jdbcUrl}\"..." } @@ -57,42 +67,43 @@ object Db { "TRANSACTION_SERIALIZABLE" -> Connection.TRANSACTION_SERIALIZABLE else -> Connection.TRANSACTION_SERIALIZABLE } - - suspend fun init() { - val databaseConfig = ConfigManager.getConfig() - transaction { - if (databaseConfig.recreate_schema) { - println("DROP SCHEMA") - SchemaUtils.drop( - WalletOperationHistories, - WalletKeys, - WalletDids, - WalletCredentials, - AccountWallets, - Accounts, - Emails, - Wallets - ) - } - println("CREATE SCHEMA IF NOT EXISTING") - SchemaUtils.create( - Wallets, - Emails, - Accounts, - AccountWallets, - WalletCredentials, - WalletDids, - WalletKeys, - WalletOperationHistories - ) - } - - if (databaseConfig.recreate_schema) { - val accountId = AccountsService.register(EmailLoginRequest("user@email.com", "password")).getOrThrow().id - println("CREATED ACCOUNT: $accountId") - } - /** moved to [AccountsService.register] **/ -// val did = WalletServiceManager.getWalletService(accountId).createDid("key") -// println("CREATED DID: $did") - } } +fun main(){ + ConfigManager.loadConfigs(emptyArray()) + Db.start() +// val account = AccountsService.register(EmailLoginRequest("username", "password")).getOrThrow().id + val account = UUID.fromString("04e595ac-7c48-4482-9ea6-8ae0981251c8") + println(account) + val key = KeysService.add(account, DbKey(keyId = "keyId", document = "document")) + println(key) + val did = DidsService.add( + account, DidInsertDataObject( + key = key, + did = Did(did = "did", document = "document") + ) + ) + println(did) + val cid = CredentialsService.add(account, DbCredential( + credentialId = "credentialId", + document = "document" + )) + println(cid) +// ConfigManager.loadConfigs(emptyArray()) +// val datasourceConfig = ConfigManager.getConfig() +// Database.connect(datasourceConfig.hikariDataSource) +// transaction { +// SchemaUtils.create( +// Accounts, +// Emails, +// Wallets, +// AccountWallets, +// WalletOperationHistories, +// Keys, +// Dids, +// Credentials, +// AccountKeys, +// AccountDids, +// AccountCredentials, +// ) +// } +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/models/AccountCredentials.kt b/src/main/kotlin/id/walt/db/models/AccountCredentials.kt new file mode 100644 index 0000000..556f3c4 --- /dev/null +++ b/src/main/kotlin/id/walt/db/models/AccountCredentials.kt @@ -0,0 +1,8 @@ +package id.walt.db.models + +import org.jetbrains.exposed.dao.id.UUIDTable + +object AccountCredentials : UUIDTable("account_credentials") { + val account = reference("account", Accounts) + val credential = reference("credential", Credentials) +} diff --git a/src/main/kotlin/id/walt/db/models/AccountDids.kt b/src/main/kotlin/id/walt/db/models/AccountDids.kt new file mode 100644 index 0000000..c1b3737 --- /dev/null +++ b/src/main/kotlin/id/walt/db/models/AccountDids.kt @@ -0,0 +1,10 @@ +package id.walt.db.models + +import org.jetbrains.exposed.dao.id.UUIDTable + +object AccountDids: UUIDTable("account_dids") { + val account = reference("account", Accounts) + val did = reference("did", Dids) + val alias = varchar("alias", 1024) + val default = bool("default").default(false) +} diff --git a/src/main/kotlin/id/walt/db/models/AccountKeys.kt b/src/main/kotlin/id/walt/db/models/AccountKeys.kt new file mode 100644 index 0000000..3fe6234 --- /dev/null +++ b/src/main/kotlin/id/walt/db/models/AccountKeys.kt @@ -0,0 +1,8 @@ +package id.walt.db.models + +import org.jetbrains.exposed.dao.id.UUIDTable + +object AccountKeys : UUIDTable("account_keys") { + val account = reference("account", Accounts) + val key = reference("key", Keys) +} diff --git a/src/main/kotlin/id/walt/db/models/Credentials.kt b/src/main/kotlin/id/walt/db/models/Credentials.kt new file mode 100644 index 0000000..56e14a1 --- /dev/null +++ b/src/main/kotlin/id/walt/db/models/Credentials.kt @@ -0,0 +1,8 @@ +package id.walt.db.models + +import org.jetbrains.exposed.dao.id.UUIDTable + +object Credentials : UUIDTable() { + val credentialId = varchar("cid", 256).uniqueIndex() + val document = text("document") +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/models/Dids.kt b/src/main/kotlin/id/walt/db/models/Dids.kt new file mode 100644 index 0000000..8bf0916 --- /dev/null +++ b/src/main/kotlin/id/walt/db/models/Dids.kt @@ -0,0 +1,9 @@ +package id.walt.db.models + +import org.jetbrains.exposed.dao.id.UUIDTable + +object Dids : UUIDTable() { + val did = varchar("did", 1024).uniqueIndex() + val document = text("document") + val key = reference("key", Keys) +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/models/Keys.kt b/src/main/kotlin/id/walt/db/models/Keys.kt new file mode 100644 index 0000000..c4c130f --- /dev/null +++ b/src/main/kotlin/id/walt/db/models/Keys.kt @@ -0,0 +1,8 @@ +package id.walt.db.models + +import org.jetbrains.exposed.dao.id.UUIDTable + +object Keys : UUIDTable() { + val keyId = varchar("kid", 512).uniqueIndex() + val document = text("document") +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/models/WalletCredentials.kt b/src/main/kotlin/id/walt/db/models/WalletCredentials.kt deleted file mode 100644 index 00b98d8..0000000 --- a/src/main/kotlin/id/walt/db/models/WalletCredentials.kt +++ /dev/null @@ -1,11 +0,0 @@ -package id.walt.db.models - -import org.jetbrains.exposed.sql.Table - -object WalletCredentials : Table() { - val account = reference("account", Accounts.id) - val credentialId = varchar("id", 256) - val credential = text("credential") - - override val primaryKey = PrimaryKey(account, credentialId) -} diff --git a/src/main/kotlin/id/walt/db/models/WalletDids.kt b/src/main/kotlin/id/walt/db/models/WalletDids.kt deleted file mode 100644 index 0338e0a..0000000 --- a/src/main/kotlin/id/walt/db/models/WalletDids.kt +++ /dev/null @@ -1,14 +0,0 @@ -package id.walt.db.models - -import org.jetbrains.exposed.sql.Table - -object WalletDids: Table() { - val account = reference("account", Accounts.id) - val did = varchar("did", 1024) - val keyId = reference("keyId", WalletKeys.keyId) - val document = text("document") - val alias = varchar("alias", 1024) - val default = bool("default").default(false) - - override val primaryKey = PrimaryKey(account, did) -} diff --git a/src/main/kotlin/id/walt/db/models/WalletKeys.kt b/src/main/kotlin/id/walt/db/models/WalletKeys.kt deleted file mode 100644 index ac6ece0..0000000 --- a/src/main/kotlin/id/walt/db/models/WalletKeys.kt +++ /dev/null @@ -1,11 +0,0 @@ -package id.walt.db.models - -import org.jetbrains.exposed.sql.Table - -object WalletKeys : Table() { - val account = reference("account", Accounts.id) - val keyId = varchar("kid", 512) - val document = text("key") - - override val primaryKey = PrimaryKey(account, keyId) -} diff --git a/src/main/kotlin/id/walt/db/models/WalletOperationHistories.kt b/src/main/kotlin/id/walt/db/models/WalletOperationHistories.kt index 00b17ac..d4a3c94 100644 --- a/src/main/kotlin/id/walt/db/models/WalletOperationHistories.kt +++ b/src/main/kotlin/id/walt/db/models/WalletOperationHistories.kt @@ -1,31 +1,11 @@ package id.walt.db.models -import id.walt.service.WalletService -import id.walt.utils.JsonUtils.toJsonPrimitives -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.javatime.timestamp object WalletOperationHistories : UUIDTable() { - val account = reference("account", Accounts.id) + val account = reference("account", Accounts) val timestamp = timestamp("timestamp") val operation = varchar("operation", 48) val data = text("data") -} - -@Serializable -data class WalletOperationHistory( - val accountId: String, - //val walletId: String, - val timestamp: Instant, - val operation: String, - val data: Map -) { - companion object { - fun new(wallet: WalletService, operation: String, data: Map) = - WalletOperationHistory(wallet.accountId.toString(), Clock.System.now(), operation, data.toJsonPrimitives()) - } -} +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/AccountCredentialsRepository.kt b/src/main/kotlin/id/walt/db/repositories/AccountCredentialsRepository.kt new file mode 100644 index 0000000..da78ff4 --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/AccountCredentialsRepository.kt @@ -0,0 +1,28 @@ +package id.walt.db.repositories + +import id.walt.db.models.AccountCredentials +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object AccountCredentialsRepository : RepositoryBase(AccountCredentials) { + override fun ResultRow.fromRow(): DbAccountCredentials = DbAccountCredentials( + id = this[AccountCredentials.id].value, + account = this[AccountCredentials.account].value, + credential = this[AccountCredentials.credential].value, + ) + + override fun DbAccountCredentials.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[AccountCredentials.account] = it.account + insertStatement[AccountCredentials.credential] = it.credential + insertStatement + } +} + +data class DbAccountCredentials( + override val id: UUID? = null, + val account: UUID, + val credential: UUID, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/AccountDidsRepository.kt b/src/main/kotlin/id/walt/db/repositories/AccountDidsRepository.kt new file mode 100644 index 0000000..87caf18 --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/AccountDidsRepository.kt @@ -0,0 +1,37 @@ +package id.walt.db.repositories + +import id.walt.db.models.AccountDids +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.statements.InsertStatement +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +object AccountDidsRepository : RepositoryBase(AccountDids) { + override fun ResultRow.fromRow(): DbAccountDids = DbAccountDids( + id = this[AccountDids.id].value, + account = this[AccountDids.account].value, + did = this[AccountDids.did].value, + alias = this[AccountDids.alias], + default = this[AccountDids.default], + ) + + override fun DbAccountDids.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[AccountDids.account] = it.account + insertStatement[AccountDids.did] = it.did + insertStatement[AccountDids.alias] = it.alias + insertStatement[AccountDids.default] = it.default + insertStatement + } +} + +data class DbAccountDids( + override val id: UUID? = null, + val account: UUID, + val did: UUID, + val alias: String, + val default: Boolean, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/AccountKeysRepository.kt b/src/main/kotlin/id/walt/db/repositories/AccountKeysRepository.kt new file mode 100644 index 0000000..c6cdcfb --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/AccountKeysRepository.kt @@ -0,0 +1,28 @@ +package id.walt.db.repositories + +import id.walt.db.models.AccountKeys +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object AccountKeysRepository : RepositoryBase(AccountKeys) { + override fun ResultRow.fromRow(): DbAccountKeys = DbAccountKeys( + id = this[AccountKeys.id].value, + account = this[AccountKeys.account].value, + key = this[AccountKeys.key].value, + ) + + override fun DbAccountKeys.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[AccountKeys.account] = it.account + insertStatement[AccountKeys.key] = it.key + insertStatement + } +} + +data class DbAccountKeys( + override val id: UUID? = null, + val account: UUID, + val key: UUID, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/AccountWalletsRepository.kt b/src/main/kotlin/id/walt/db/repositories/AccountWalletsRepository.kt new file mode 100644 index 0000000..a63f4b7 --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/AccountWalletsRepository.kt @@ -0,0 +1,31 @@ +package id.walt.db.repositories + +import id.walt.db.models.AccountWallets +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object AccountWalletsRepository : RepositoryBase(AccountWallets) { + override fun ResultRow.fromRow(): DbAccountWallets = DbAccountWallets( + id = this[AccountWallets.id].value, + account = this[AccountWallets.account].value, + wallet = this[AccountWallets.wallet].value, + owner = this[AccountWallets.owner], + ) + + override fun DbAccountWallets.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[AccountWallets.account] = it.account + insertStatement[AccountWallets.wallet] = it.wallet + insertStatement[AccountWallets.owner] = it.owner + insertStatement + } +} + +data class DbAccountWallets( + override val id: UUID? = null, + val account: UUID, + val wallet: UUID, + val owner: Boolean, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/AccountsRepository.kt b/src/main/kotlin/id/walt/db/repositories/AccountsRepository.kt new file mode 100644 index 0000000..34bf2e7 --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/AccountsRepository.kt @@ -0,0 +1,28 @@ +package id.walt.db.repositories + +import id.walt.db.models.Accounts +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object AccountsRepository : RepositoryBase(Accounts) { + override fun ResultRow.fromRow(): DbAccount = DbAccount( + id = this[Accounts.id].value, + email = this[Accounts.email]?.value, + wallet = this[Accounts.wallet]?.value, + ) + + override fun DbAccount.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[Accounts.email] = it.email + insertStatement[Accounts.wallet] = it.wallet + insertStatement + } +} + +data class DbAccount( + override val id: UUID? = null, + val email: UUID?, + val wallet: UUID?, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/CredentialsRepository.kt b/src/main/kotlin/id/walt/db/repositories/CredentialsRepository.kt new file mode 100644 index 0000000..81cc9da --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/CredentialsRepository.kt @@ -0,0 +1,28 @@ +package id.walt.db.repositories + +import id.walt.db.models.Credentials +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object CredentialsRepository : RepositoryBase(Credentials) { + override fun ResultRow.fromRow(): DbCredential = DbCredential( + id = this[Credentials.id].value, + credentialId = this[Credentials.credentialId], + document = this[Credentials.document] + ) + + override fun DbCredential.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[Credentials.credentialId] = it.credentialId + insertStatement[Credentials.document] = it.document + insertStatement + } +} + +data class DbCredential( + override val id: UUID? = null, + val credentialId: String, + val document: String, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/DidsRepository.kt b/src/main/kotlin/id/walt/db/repositories/DidsRepository.kt new file mode 100644 index 0000000..cd1585c --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/DidsRepository.kt @@ -0,0 +1,30 @@ +package id.walt.db.repositories + +import id.walt.db.models.Dids +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object DidsRepository : RepositoryBase(Dids) { + override fun ResultRow.fromRow(): DbDid = DbDid( + id = this[Dids.id].value, + key = this[Dids.key].value, + did = this[Dids.did], + document = this[Dids.document] + ) + + override fun DbDid.toRow(insertStatement: InsertStatement>): InsertStatement> = let { + insertStatement[Dids.did] = it.did + insertStatement[Dids.document] = it.document + insertStatement[Dids.key] = it.key + insertStatement + } +} + +data class DbDid( + override val id: UUID? = null, + val key: UUID, + val did: String, + val document: String, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/EmailsRepository.kt b/src/main/kotlin/id/walt/db/repositories/EmailsRepository.kt new file mode 100644 index 0000000..fb15a4c --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/EmailsRepository.kt @@ -0,0 +1,28 @@ +package id.walt.db.repositories + +import id.walt.db.models.Emails +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object EmailsRepository : RepositoryBase(Emails) { + override fun ResultRow.fromRow(): DbEmail = DbEmail( + id = this[Emails.id].value, + email = this[Emails.email], + password = this[Emails.password], + ) + + override fun DbEmail.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[Emails.email] = it.email + insertStatement[Emails.password] = it.password + insertStatement + } +} + +data class DbEmail( + override val id: UUID? = null, + val email: String, + val password: String, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/KeysRepository.kt b/src/main/kotlin/id/walt/db/repositories/KeysRepository.kt new file mode 100644 index 0000000..047abeb --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/KeysRepository.kt @@ -0,0 +1,27 @@ +package id.walt.db.repositories + +import id.walt.db.models.Keys +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object KeysRepository : RepositoryBase(Keys) { + override fun ResultRow.fromRow(): DbKey = DbKey( + id = this[Keys.id].value, + keyId = this[Keys.keyId], + document = this[Keys.document], + ) + + override fun DbKey.toRow(insertStatement: InsertStatement>): InsertStatement> = let { + insertStatement[Keys.keyId] = it.keyId + insertStatement[Keys.document] = it.document + insertStatement + } +} + +data class DbKey( + override val id: UUID? = null, + val keyId: String, + val document: String, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/Repository.kt b/src/main/kotlin/id/walt/db/repositories/Repository.kt new file mode 100644 index 0000000..d1eb984 --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/Repository.kt @@ -0,0 +1,55 @@ +package id.walt.db.repositories + +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.statements.InsertStatement +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +interface Repository { + fun insert(model: T): UUID + fun get(id: UUID): T + fun delete(id: UUID): Int + fun query(query: Query, distinct: Boolean = true, transform: (ResultRow) -> K): List +// fun find(column: Column, value: K): List +} + +interface Transformer { + fun ResultRow.fromRow(): T + fun T.toRow(insertStatement: InsertStatement>): InsertStatement> +} + +abstract class RepositoryBase( + private val table: UUIDTable +) : Repository, Transformer { + + override fun insert(model: T): UUID = transaction { + table.insertAndGetId { model.toRow(it) }.value + } + +// override fun find(column: Column, value: K): List = transaction { +// table.select { column eq value } +// }.map { +// it.fromRow() +// } + + override fun delete(id: UUID): Int = transaction { + table.deleteWhere { table.id eq id } + } + + override fun get(id: UUID): T = transaction { + table.select { table.id eq id }.single() + }.fromRow() + + override fun query(query: Query, distinct: Boolean, transform: (ResultRow) -> K): List = transaction { + query.let { q -> + distinct.takeIf { it }?.let { q.distinct() } ?: q + }.map(transform) + } +} + +abstract class DbEntity { + abstract val id: UUID? +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/WalletOperationHistoriesRepository.kt b/src/main/kotlin/id/walt/db/repositories/WalletOperationHistoriesRepository.kt new file mode 100644 index 0000000..77e9c5a --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/WalletOperationHistoriesRepository.kt @@ -0,0 +1,35 @@ +package id.walt.db.repositories + +import id.walt.db.models.WalletOperationHistories +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.time.Instant +import java.util.* + +object WalletOperationHistoriesRepository : RepositoryBase(WalletOperationHistories) { + override fun ResultRow.fromRow(): DbWalletOperationHistory = DbWalletOperationHistory( + id = this[WalletOperationHistories.id].value, + accountId = this[WalletOperationHistories.account].value, + timestamp = this[WalletOperationHistories.timestamp], + operation = this[WalletOperationHistories.operation], + data = this[WalletOperationHistories.data], + ) + + override fun DbWalletOperationHistory.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[WalletOperationHistories.account] = it.accountId + insertStatement[WalletOperationHistories.timestamp] = it.timestamp + insertStatement[WalletOperationHistories.operation] = it.operation + insertStatement[WalletOperationHistories.data] = it.data + insertStatement + } +} + +data class DbWalletOperationHistory( + override val id: UUID? = null, + val accountId: UUID, + val timestamp: Instant, + val operation: String, + val data: String, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/db/repositories/WalletsRepository.kt b/src/main/kotlin/id/walt/db/repositories/WalletsRepository.kt new file mode 100644 index 0000000..ef93bd7 --- /dev/null +++ b/src/main/kotlin/id/walt/db/repositories/WalletsRepository.kt @@ -0,0 +1,28 @@ +package id.walt.db.repositories + +import id.walt.db.models.Wallets +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.statements.InsertStatement +import java.util.* + +object WalletsRepository : RepositoryBase(Wallets) { + override fun ResultRow.fromRow(): DbWallet = DbWallet( + id = this[Wallets.id].value, + address = this[Wallets.address], + ecosystem = this[Wallets.ecosystem], + ) + + override fun DbWallet.toRow(insertStatement: InsertStatement>): InsertStatement> = + let { + insertStatement[Wallets.address] = it.address + insertStatement[Wallets.ecosystem] = it.ecosystem + insertStatement + } +} + +data class DbWallet( + override val id: UUID? = null, + val address: String, + val ecosystem: String, +) : DbEntity() \ No newline at end of file diff --git a/src/main/kotlin/id/walt/service/Did.kt b/src/main/kotlin/id/walt/service/Did.kt index cf7902c..50f024e 100644 --- a/src/main/kotlin/id/walt/service/Did.kt +++ b/src/main/kotlin/id/walt/service/Did.kt @@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable @Serializable data class Did( val did : String, - val alias : String, - val default : Boolean + val alias : String = "n/a", + val default : Boolean = false, + val document: String, ) diff --git a/src/main/kotlin/id/walt/service/SSIKit2WalletService.kt b/src/main/kotlin/id/walt/service/SSIKit2WalletService.kt index 6273623..cf0fe99 100644 --- a/src/main/kotlin/id/walt/service/SSIKit2WalletService.kt +++ b/src/main/kotlin/id/walt/service/SSIKit2WalletService.kt @@ -5,6 +5,7 @@ import id.walt.crypto.keys.KeySerialization import id.walt.crypto.keys.KeyType import id.walt.crypto.keys.LocalKey import id.walt.db.models.* +import id.walt.db.repositories.* import id.walt.did.dids.DidService import id.walt.did.dids.registrar.dids.DidCheqdCreateOptions import id.walt.did.dids.registrar.dids.DidJwkCreateOptions @@ -23,8 +24,13 @@ import id.walt.oid4vc.requests.TokenRequest import id.walt.oid4vc.responses.CredentialResponse import id.walt.oid4vc.responses.TokenResponse import id.walt.oid4vc.util.randomUUID +import id.walt.service.credentials.CredentialsService +import id.walt.service.dids.DidDefaultUpdateDataObject +import id.walt.service.dids.DidInsertDataObject +import id.walt.service.dids.DidsService import id.walt.service.dto.LinkedWalletDataTransferObject import id.walt.service.dto.WalletDataTransferObject +import id.walt.service.dto.WalletOperationHistory import id.walt.service.keys.KeysService import id.walt.service.oidc4vc.TestCredentialWallet import io.ktor.client.* @@ -36,6 +42,7 @@ import io.ktor.client.request.forms.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.plugins.* import io.ktor.util.* import kotlinx.coroutines.runBlocking import kotlinx.datetime.toJavaInstant @@ -44,7 +51,6 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction import java.net.URLDecoder import java.util.* @@ -74,48 +80,32 @@ class SSIKit2WalletService(accountId: UUID) : WalletService(accountId) { /* Credentials */ @OptIn(ExperimentalEncodingApi::class) - override suspend fun listCredentials(): List = - transaction { - WalletCredentials.select { WalletCredentials.account eq accountId }.map { - it - } - }.mapNotNull { resultRow -> - val credentialId = resultRow[WalletCredentials.credentialId] - runCatching { - val cred = resultRow[WalletCredentials.credential] - val parsedCred = if (cred.startsWith("{")) - Json.parseToJsonElement(cred).jsonObject - else if (cred.startsWith("ey")) - Json.parseToJsonElement( - Base64.decode(cred.split(".")[1]).decodeToString() - ).jsonObject["vc"]!!.jsonObject - else throw IllegalArgumentException("Unknown credential format") - Credential(parsedCred, cred) - }.onFailure { it.printStackTrace() }.getOrNull()?.let { cred -> - Credential(JsonObject(cred.parsedCredential.toMutableMap().also { - it.putIfAbsent("id", JsonPrimitive(credentialId)) - }), cred.rawCredential) - } + override suspend fun listCredentials(): List = CredentialsService.list(accountId).mapNotNull { + val credentialId = it.credentialId + runCatching { + val cred = it.document + val parsedCred = if (cred.startsWith("{")) Json.parseToJsonElement(cred).jsonObject + else if (cred.startsWith("ey")) Json.parseToJsonElement( + Base64.decode(cred.split(".")[1]).decodeToString() + ).jsonObject["vc"]!!.jsonObject + else throw IllegalArgumentException("Unknown credential format") + Credential(parsedCred, cred) + }.onFailure { it.printStackTrace() }.getOrNull()?.let { cred -> + Credential(JsonObject(cred.parsedCredential.toMutableMap().also { + it.putIfAbsent("id", JsonPrimitive(credentialId)) + }), cred.rawCredential) } + } - override suspend fun listRawCredentials(): List { - return transaction { - WalletCredentials.select { WalletCredentials.account eq accountId }.map { - it[WalletCredentials.credential] - } - } + override suspend fun listRawCredentials(): List = CredentialsService.list(accountId).map { + it.document } - override suspend fun deleteCredential(id: String) = transaction { - WalletCredentials.deleteWhere { (account eq accountId) and (credentialId eq id) } - } > 0 + override suspend fun deleteCredential(id: String) = CredentialsService.delete(accountId, id) - override suspend fun getCredential(credentialId: String): String { - return transaction { - WalletCredentials.select { (WalletCredentials.account eq accountId) and (WalletCredentials.credentialId eq credentialId) } - .single()[WalletCredentials.credential] - } - } + override suspend fun getCredential(credentialId: String): String = + CredentialsService.get(accountId, credentialId)?.document + ?: throw IllegalArgumentException("Credential not found for credentialId: $credentialId") private fun getQueryParams(url: String): Map> { val params: MutableMap> = HashMap() @@ -293,17 +283,13 @@ class SSIKit2WalletService(accountId: UUID) : WalletService(accountId) { } val credential = credentialResp.credential!!.jsonPrimitive.content - println(">>> CREDENTIAL IS: " + credential) + println(">>> CREDENTIAL IS: $credential") - val credentialId = Json.parseToJsonElement(Base64.decode(credential.split(".")[1]).decodeToString()).jsonObject["vc"]!!.jsonObject["id"]?.jsonPrimitive?.content ?: randomUUID() + val credentialId = Json.parseToJsonElement( + Base64.decode(credential.split(".")[1]).decodeToString() + ).jsonObject["vc"]!!.jsonObject["id"]?.jsonPrimitive?.content ?: randomUUID() - transaction { - WalletCredentials.insert { - it[WalletCredentials.account] = accountId - it[WalletCredentials.credentialId] = credentialId - it[WalletCredentials.credential] = credential - } - } + CredentialsService.add(accountId, DbCredential(credentialId = credentialId, document = credential)) println("Credential stored with Id: $credentialId") @@ -312,77 +298,50 @@ class SSIKit2WalletService(accountId: UUID) : WalletService(accountId) { /* DIDs */ override suspend fun createDid(method: String, args: Map): String { - // TODO: other DIDs - val key = args["keyId"]?.content?.let { getKey(it) }?.getOrNull() ?: LocalKey.generate(KeyType.Ed25519) - val keyId = key.getKeyId() + val key = args["keyId"]?.content?.let { getKey(it) } ?: LocalKey.generate(KeyType.Ed25519) val options = getDidOptions(method, args) - val result = DidService.registerByKey(method, key, options) - KeysService.insert(accountId, keyId, KeySerialization.serializeKey(key)) - - transaction { - - WalletDids.insert { - it[account] = accountId - it[did] = result.did - it[alias] = args["alias"]?.content ?: "" - it[WalletDids.keyId] = keyId - it[document] = Json.encodeToString(result.didDocument) - } - } - + val keyRef = KeysService.add(accountId, + DbKey( + keyId = key.getKeyId(), + document = KeySerialization.serializeKey(key) + ) + ) + DidsService.add(accountId, + DidInsertDataObject( + key = keyRef, + did = Did(did = result.did, document = Json.encodeToString(result.didDocument)) + ) + ) return result.did } - override suspend fun listDids(): List = - transaction { - WalletDids.select { WalletDids.account eq accountId }.map {resultRow -> - Did( - did = resultRow[WalletDids.did], - alias = resultRow[WalletDids.alias], - default = resultRow[WalletDids.default] - ) - } - } + override suspend fun listDids(): List = DidsService.list(accountId) - override suspend fun loadDid(did: String): JsonObject = Json.parseToJsonElement(transaction { - WalletDids.select { (WalletDids.account eq accountId) and (WalletDids.did eq did) } - .single()[WalletDids.document] - }).jsonObject + override suspend fun loadDid(did: String): JsonObject = DidsService.get(accountId, did)?.let { + Json.parseToJsonElement(it.document).jsonObject + } ?: throw IllegalArgumentException("Did not found: $did for account: $accountId") - override suspend fun deleteDid(did: String): Boolean = transaction { - WalletDids.deleteWhere { (account eq account) and (WalletDids.did eq did) } - } > 0 + override suspend fun deleteDid(did: String): Boolean = DidsService.delete(accountId, did) - override suspend fun setDefault(did: String) = transaction{ - WalletDids.update({ WalletDids.default eq true }) { - it[default] = false - } - WalletDids.update( {WalletDids.did eq did}){ - it[default] = true - } - } > 0 + override suspend fun setDefault(did: String) = DidsService.update(accountId, DidDefaultUpdateDataObject(did, true)) /* Keys */ - private fun getKey(keyId: String) = runCatching { - KeysService.getById(accountId, keyId)?.let { - KeySerialization.deserializeKey(it).getOrThrow() - } ?: throw IllegalArgumentException("Key not found: $keyId") - } + private fun getKey(keyId: String) = KeysService.get(accountId, keyId)?.let { + KeySerialization.deserializeKey(it.document) + .getOrElse { throw IllegalArgumentException("Could not deserialize resolved key: ${it.message}", it) } + } ?: throw IllegalArgumentException("Key not found: $keyId") - suspend fun getKeyByDid(did: String): Key = DidService.resolveToKey(did).fold( - onSuccess = { - getKey(it.getKeyId()).getOrElse { throw IllegalArgumentException("Could not deserialize resolved key: ${it.message}", it) } - }, - onFailure = { - throw it - } - ) + suspend fun getKeyByDid(did: String): Key = DidService.resolveToKey(did).fold(onSuccess = { + getKey(it.getKeyId()) + }, onFailure = { + throw it + }) override suspend fun exportKey(alias: String, format: String, private: Boolean): String = - getKey(alias).fold(onSuccess = { + runCatching { getKey(alias) }.fold(onSuccess = { when (format.lowercase()) { "jwk" -> it.exportJWK() "pem" -> it.exportPEM() @@ -392,23 +351,23 @@ class SSIKit2WalletService(accountId: UUID) : WalletService(accountId) { throw it }) - override suspend fun loadKey(alias: String): JsonObject = getKey(alias).getOrThrow().exportJWKObject() + override suspend fun loadKey(alias: String): JsonObject = getKey(alias).exportJWKObject() - override suspend fun listKeys(): List = - KeysService.getAllForAccount(accountId).map { (id, keyJson) -> - val key = KeySerialization.deserializeKey(keyJson).getOrThrow() + override suspend fun listKeys(): List = KeysService.list(accountId).map { + val key = KeySerialization.deserializeKey(it.document).getOrThrow() - SingleKeyResponse( - keyId = SingleKeyResponse.KeyId(id), - algorithm = key.keyType.name, - cryptoProvider = key.toString(), - keyPair = JsonObject(emptyMap()), - keysetHandle = JsonNull - ) - } + SingleKeyResponse( + keyId = SingleKeyResponse.KeyId(it.keyId), + algorithm = key.keyType.name, + cryptoProvider = key.toString(), + keyPair = JsonObject(emptyMap()), + keysetHandle = JsonNull + ) + } override suspend fun generateKey(type: String): String = LocalKey.generate(KeyType.valueOf(type)).let { - KeysService.insert(accountId, it.getKeyId(), KeySerialization.serializeKey(it)) + val kid = KeysRepository.insert(DbKey(keyId = it.getKeyId(), document = KeySerialization.serializeKey(it))) + AccountKeysRepository.insert(DbAccountKeys(account = accountId, key = kid)) it.getKeyId() } @@ -432,20 +391,13 @@ class SSIKit2WalletService(accountId: UUID) : WalletService(accountId) { val keyId = key.getKeyId() val keyJson = KeySerialization.serializeKey(key) - transaction { - WalletKeys.insert { - it[account] = accountId - it[WalletKeys.keyId] = keyId - it[document] = keyJson - } - } + val kid = KeysRepository.insert(DbKey(keyId = keyId, document = keyJson)) + AccountKeysRepository.insert(DbAccountKeys(account = accountId, key = kid)) return keyId } - override suspend fun deleteKey(alias: String): Boolean = transaction { - WalletKeys.deleteWhere { (account eq accountId) and (keyId eq alias) } - } > 0 + override suspend fun deleteKey(alias: String): Boolean = KeysService.delete(accountId, alias) fun addToHistory() { // data from @@ -504,6 +456,6 @@ class SSIKit2WalletService(accountId: UUID) : WalletService(accountId) { network = args["network"]?.content ?: "test", document = Json.decodeFromString(args["document"]?.content ?: "") ) - else -> DidJwkCreateOptions() + else -> throw IllegalArgumentException("Did method not supported: $method") } } diff --git a/src/main/kotlin/id/walt/service/WalletKitWalletService.kt b/src/main/kotlin/id/walt/service/WalletKitWalletService.kt index 709e8b6..2c4ee20 100644 --- a/src/main/kotlin/id/walt/service/WalletKitWalletService.kt +++ b/src/main/kotlin/id/walt/service/WalletKitWalletService.kt @@ -3,8 +3,11 @@ package id.walt.service import id.walt.config.ConfigManager import id.walt.config.RemoteWalletConfig import id.walt.db.models.* +import id.walt.service.dids.DidDefaultUpdateDataObject +import id.walt.service.dids.DidsService import id.walt.service.dto.LinkedWalletDataTransferObject import id.walt.service.dto.WalletDataTransferObject +import id.walt.service.dto.WalletOperationHistory import id.walt.utils.JsonUtils.toJsonPrimitive import io.ktor.client.* import io.ktor.client.call.* @@ -279,14 +282,7 @@ class WalletKitWalletService(accountId: UUID) : WalletService(accountId) { override suspend fun deleteDid(did: String)=authenticatedJsonDelete("/api/wallet/did/delete/$did").status.isSuccess() - override suspend fun setDefault(did: String)= transaction{ - WalletDids.update({ WalletDids.default eq true }) { - it[default] = false - } - WalletDids.update( { WalletDids.did eq did}){ - it[default] = true - } - } > 0 + override suspend fun setDefault(did: String) = DidsService.update(accountId, DidDefaultUpdateDataObject(did, true)) /* Keys */ diff --git a/src/main/kotlin/id/walt/service/WalletService.kt b/src/main/kotlin/id/walt/service/WalletService.kt index aaeba25..7810b63 100644 --- a/src/main/kotlin/id/walt/service/WalletService.kt +++ b/src/main/kotlin/id/walt/service/WalletService.kt @@ -1,10 +1,8 @@ package id.walt.service -import id.walt.db.models.WalletDids -import id.walt.db.models.WalletOperationHistory import id.walt.service.dto.LinkedWalletDataTransferObject import id.walt.service.dto.WalletDataTransferObject -import id.walt.utils.JsonUtils.toJsonPrimitives +import id.walt.service.dto.WalletOperationHistory import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import java.util.* diff --git a/src/main/kotlin/id/walt/service/credentials/CredentialsService.kt b/src/main/kotlin/id/walt/service/credentials/CredentialsService.kt new file mode 100644 index 0000000..1a1e191 --- /dev/null +++ b/src/main/kotlin/id/walt/service/credentials/CredentialsService.kt @@ -0,0 +1,83 @@ +package id.walt.service.credentials + +import id.walt.db.models.AccountCredentials +import id.walt.db.models.Accounts +import id.walt.db.models.Credentials +import id.walt.db.repositories.AccountCredentialsRepository +import id.walt.db.repositories.CredentialsRepository +import id.walt.db.repositories.DbAccountCredentials +import id.walt.db.repositories.DbCredential +import id.walt.service.dids.DidUpdateDataObject +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.innerJoin +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.selectAll +import java.util.* + +//TODO: replace DbCredential with a dto +object CredentialsService { + fun get(account: UUID, credentialId: String): DbCredential? = + list(account).singleOrNull { it.credentialId == credentialId } + + fun list(account: UUID): List = join(account).let { + AccountCredentialsRepository.query(it) { + DbCredential( + id = it[Credentials.id].value, + credentialId = it[Credentials.credentialId], + document = it[Credentials.document] + ) + } + } + + fun add(account: UUID, credential: DbCredential): UUID = + getOrInsert(credential.credentialId, credential.document).let { cid -> + join(account, credential.credentialId).let { + AccountCredentialsRepository.query(it) { + it[AccountCredentials.id] + } + }.takeIf { it.isNotEmpty() }?.single()?.value ?: let { + AccountCredentialsRepository.insert(DbAccountCredentials( + account = account, + credential = cid, + )) + } + } + + fun delete(account: UUID, credentialId: String): Boolean = join(account, credentialId).let { + AccountCredentialsRepository.query(it) { + it[AccountCredentials.id] + }.singleOrNull()?.value + }?.let { + AccountCredentialsRepository.delete(it) + }?.let { it > 0 } ?: false + + fun update(account: UUID, did: DidUpdateDataObject): Boolean { + TODO() + } + + private fun join(account: UUID, credentialId: String? = null) = + Accounts.innerJoin(AccountCredentials, onColumn = { Accounts.id }, otherColumn = { AccountCredentials.account }) + .innerJoin(Credentials, + onColumn = { Credentials.id }, + otherColumn = { AccountCredentials.credential }, + additionalConstraint = credentialId?.let { + { + Credentials.credentialId eq credentialId and (Accounts.id eq account) + } + }).selectAll() + + private fun find(credentialId: String) = Credentials.select { Credentials.credentialId eq credentialId } + + private fun getOrInsert(credentialId: String, document: String) = find(credentialId).let { + CredentialsRepository.query(it) { + it[Credentials.id] + }.singleOrNull()?.value + } ?: let { + CredentialsRepository.insert( + DbCredential( + credentialId = credentialId, + document = document, + ) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/service/dids/DidInsertDataObject.kt b/src/main/kotlin/id/walt/service/dids/DidInsertDataObject.kt new file mode 100644 index 0000000..830fb63 --- /dev/null +++ b/src/main/kotlin/id/walt/service/dids/DidInsertDataObject.kt @@ -0,0 +1,9 @@ +package id.walt.service.dids + +import id.walt.service.Did +import java.util.* + +data class DidInsertDataObject( + val key: UUID, + val did: Did +) diff --git a/src/main/kotlin/id/walt/service/dids/DidService.kt b/src/main/kotlin/id/walt/service/dids/DidService.kt new file mode 100644 index 0000000..2b8826a --- /dev/null +++ b/src/main/kotlin/id/walt/service/dids/DidService.kt @@ -0,0 +1,97 @@ +package id.walt.service.dids + +import id.walt.db.models.AccountDids +import id.walt.db.models.Accounts +import id.walt.db.models.Dids +import id.walt.db.repositories.AccountDidsRepository +import id.walt.db.repositories.DbAccountDids +import id.walt.db.repositories.DbDid +import id.walt.db.repositories.DidsRepository +import id.walt.service.Did +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + +object DidsService { + fun get(account: UUID, did: String): Did? = list(account).singleOrNull { it.did == did } + + fun list(account: UUID): List = join(account).let { + AccountDidsRepository.query(it) { + Did( + did = it[Dids.did], + alias = it[AccountDids.alias], + default = it[AccountDids.default], + document = it[Dids.document], + ) + } + } + + fun add(account: UUID, data: DidInsertDataObject): UUID = + getOrInsert(data.key, data.did.did, data.did.document).let { did -> + join(account, data.did.did).let { + AccountDidsRepository.query(it) { + it[AccountDids.id] + } + }.takeIf { it.isNotEmpty() }?.single()?.value ?: let { + AccountDidsRepository.insert( + DbAccountDids( + account = account, did = did, alias = data.did.alias, default = data.did.default + ) + ) + } + } + + fun delete(account: UUID, did: String): Boolean = join(account, did).let { + AccountDidsRepository.query(it) { + it[AccountDids.id] + }.single().value + }.let { + AccountDidsRepository.delete(it) + }.let { it > 0 } + + fun update(account: UUID, did: DidUpdateDataObject): Boolean = join(account, did.did).let { + AccountDidsRepository.query(it) { + it[AccountDids.id] + }.singleOrNull()?.value + }?.let { + when (did) { + is DidAliasUpdateDataObject -> updateQuery(it, alias = did.alias) + is DidDefaultUpdateDataObject -> updateQuery(it, isDefault = did.isDefault) + else -> throw IllegalArgumentException("Unsupported update field: ${did.javaClass.name}") + } + }?.let { it > 0 } ?: false + + private fun join(account: UUID, did: String? = null) = + Accounts.innerJoin(AccountDids, onColumn = { Accounts.id }, otherColumn = { AccountDids.account }).innerJoin( + Dids, + onColumn = { Dids.id }, + otherColumn = { AccountDids.did }, + additionalConstraint = did?.let { + { + Dids.did eq did and (Accounts.id eq account) + } + }).selectAll() + + private fun find(did: String) = Dids.select { Dids.did eq did } + private fun getOrInsert(key: UUID, did: String, document: String) = find(did).let { + DidsRepository.query(it) { + it[Dids.id] + }.singleOrNull()?.value + } ?: let { + DidsRepository.insert( + DbDid( + key = key, + did = did, + document = document, + ) + ) + } + + // TODO: implement in repository + private fun updateQuery(id: UUID, alias: String? = null, isDefault: Boolean? = null) = transaction { + AccountDids.update({ AccountDids.id eq id }) { statement -> + alias?.let { statement[AccountDids.alias] = it } + isDefault?.let { statement[AccountDids.default] = it } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/service/dids/DidUpdateDataObject.kt b/src/main/kotlin/id/walt/service/dids/DidUpdateDataObject.kt new file mode 100644 index 0000000..46c71fe --- /dev/null +++ b/src/main/kotlin/id/walt/service/dids/DidUpdateDataObject.kt @@ -0,0 +1,15 @@ +package id.walt.service.dids + +abstract class DidUpdateDataObject( + open val did: String, +) + +data class DidDefaultUpdateDataObject( + override val did: String, + val isDefault: Boolean, +) : DidUpdateDataObject(did) + +data class DidAliasUpdateDataObject( + override val did: String, + val alias: String, +) : DidUpdateDataObject(did) \ No newline at end of file diff --git a/src/main/kotlin/id/walt/service/dto/WalletOperationHistory.kt b/src/main/kotlin/id/walt/service/dto/WalletOperationHistory.kt new file mode 100644 index 0000000..e861a4f --- /dev/null +++ b/src/main/kotlin/id/walt/service/dto/WalletOperationHistory.kt @@ -0,0 +1,22 @@ +package id.walt.service.dto + +import id.walt.service.WalletService +import id.walt.utils.JsonUtils.toJsonPrimitives +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement + +@Serializable +data class WalletOperationHistory( + val accountId: String, + //val walletId: String, + val timestamp: Instant, + val operation: String, + val data: Map +) { + companion object { + fun new(wallet: WalletService, operation: String, data: Map) = + WalletOperationHistory(wallet.accountId.toString(), Clock.System.now(), operation, data.toJsonPrimitives()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/service/keys/KeysService.kt b/src/main/kotlin/id/walt/service/keys/KeysService.kt index de99dc8..931ce41 100644 --- a/src/main/kotlin/id/walt/service/keys/KeysService.kt +++ b/src/main/kotlin/id/walt/service/keys/KeysService.kt @@ -1,32 +1,79 @@ package id.walt.service.keys -import id.walt.db.models.WalletKeys +import id.walt.db.models.AccountKeys +import id.walt.db.models.Accounts +import id.walt.db.models.Credentials +import id.walt.db.models.Keys +import id.walt.db.repositories.AccountKeysRepository +import id.walt.db.repositories.DbAccountKeys +import id.walt.db.repositories.DbKey +import id.walt.db.repositories.KeysRepository +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.innerJoin import org.jetbrains.exposed.sql.select -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.selectAll import java.util.* -//TODO: introduce and implement db-entities object KeysService { + fun get(account: UUID, keyId: String): DbKey? = list(account).singleOrNull { it.keyId == keyId } - fun getById(account: UUID, id: String): String? = transaction { - WalletKeys.select { WalletKeys.account eq account and (WalletKeys.keyId eq id) }.firstOrNull()?.let { - it[WalletKeys.document] + fun list(account: UUID): List = join(account).let { + AccountKeysRepository.query(it) { + DbKey(it[Keys.id].value, it[Keys.keyId], it[Keys.document]) } } - fun getAllForAccount(account: UUID) = transaction { - WalletKeys.select { (WalletKeys.account eq account) }.map { - Pair(it[WalletKeys.keyId], it[WalletKeys.document]) + //TODO: remove db entity reference + fun add(account: UUID, key: DbKey): UUID = getOrInsert(key.keyId, key.document).let { kid -> + join(account, key.keyId).let { + AccountKeysRepository.query(it) { + it[AccountKeys.id] + }.takeIf { + it.isNotEmpty() + // account has the key already associated + }?.single()?.value + // otherwise, associate the key to account + ?: let { + AccountKeysRepository.insert(DbAccountKeys(account = account, key = kid)) + } } } - fun insert(account: UUID, keyId: String, document: String) = transaction { - WalletKeys.insert { - it[WalletKeys.account] = account - it[WalletKeys.keyId] = keyId - it[WalletKeys.document] = document - }.insertedCount == 1 + fun delete(account: UUID, keyId: String): Boolean = join(account, keyId).let { + AccountKeysRepository.query(it) { + it[AccountKeys.id] + }.single().value + }.let { + AccountKeysRepository.delete(it) + }.let { it > 0 } + + fun update(account: UUID, key: DbKey): Boolean { + TODO() + } + + private fun join(account: UUID, keyId: String? = null) = + Accounts.innerJoin(AccountKeys, onColumn = { Accounts.id }, otherColumn = { AccountKeys.account }).innerJoin( + Keys, + onColumn = { Keys.id }, + otherColumn = { AccountKeys.key }, + additionalConstraint = keyId?.let { + { + Keys.keyId eq keyId and (Accounts.id eq account) + } + }).selectAll() + + private fun find(keyId: String) = Keys.select { Keys.keyId eq keyId } + private fun getOrInsert(keyId: String, document: String) = find(keyId).let { + KeysRepository.query(it) { + it[Keys.id] + }.singleOrNull()?.value + } ?: let { + KeysRepository.insert( + DbKey( + keyId = keyId, + document = document, + ) + ) } } \ No newline at end of file diff --git a/src/main/kotlin/id/walt/web/controllers/ExchangeController.kt b/src/main/kotlin/id/walt/web/controllers/ExchangeController.kt index 150a9ec..043ffa4 100644 --- a/src/main/kotlin/id/walt/web/controllers/ExchangeController.kt +++ b/src/main/kotlin/id/walt/web/controllers/ExchangeController.kt @@ -1,6 +1,6 @@ package id.walt.web.controllers -import id.walt.db.models.WalletOperationHistory +import id.walt.service.dto.WalletOperationHistory import id.walt.web.getWalletService import io.github.smiley4.ktorswaggerui.dsl.post import io.github.smiley4.ktorswaggerui.dsl.route diff --git a/src/main/resources/db/sqlite/V4__create_tables_keys_dids_credentials_accountdids_accountkeys_accountcredentials.sql b/src/main/resources/db/sqlite/V4__create_tables_keys_dids_credentials_accountdids_accountkeys_accountcredentials.sql new file mode 100644 index 0000000..ce2446d --- /dev/null +++ b/src/main/resources/db/sqlite/V4__create_tables_keys_dids_credentials_accountdids_accountkeys_accountcredentials.sql @@ -0,0 +1,70 @@ +-- ---------------------------------- +-- Keys table +-- ---------------------------------- +CREATE TABLE "keys" ( + "id" BINARY(16) NOT NULL PRIMARY KEY, + "kid" VARCHAR(512) NOT NULL, + "document" TEXT NOT NULL +); +-- ---------------------------------- +-- Dids table +-- ---------------------------------- +CREATE TABLE "dids" ( + "id" BINARY(16) NOT NULL PRIMARY KEY, + "did" VARCHAR(1024) NOT NULL, + "document" TEXT NOT NULL, + "key" BINARY(16) NOT NULL, + CONSTRAINT "fk_dids_key__id" FOREIGN KEY ("key") REFERENCES "keys"("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +-- ---------------------------------- +-- Credentials table +-- ---------------------------------- +CREATE TABLE "credentials" ( + "id" BINARY(16) NOT NULL PRIMARY KEY, + "cid" VARCHAR(256) NOT NULL, + "document" TEXT NOT NULL +); +-- ---------------------------------- +-- AccountKeys table +-- ---------------------------------- +CREATE TABLE "account_keys" ( + "id" BINARY(16) NOT NULL PRIMARY KEY, + "account" BINARY(16) NOT NULL, + "key" BINARY(16) NOT NULL, + CONSTRAINT "fk_account_keys_account__id" FOREIGN KEY ("account") REFERENCES "accounts"("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "fk_account_keys_key__id" FOREIGN KEY ("key") REFERENCES "keys"("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +-- ---------------------------------- +-- AccountDids table +-- ---------------------------------- +CREATE TABLE "account_dids" ( + "id" BINARY(16) NOT NULL PRIMARY KEY, + "account" BINARY(16) NOT NULL, + "did" BINARY(16) NOT NULL, + "alias" VARCHAR(1024) NOT NULL, + "default" BOOLEAN DEFAULT 0 NOT NULL, + CONSTRAINT "fk_account_dids_account__id" FOREIGN KEY ("account") REFERENCES "accounts"("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "fk_account_dids_did__id" FOREIGN KEY ("did") REFERENCES "dids"("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +-- ---------------------------------- +-- AccountCredentials table +-- ---------------------------------- +CREATE TABLE "account_credentials" ( + "id" BINARY(16) NOT NULL PRIMARY KEY, + "account" BINARY(16) NOT NULL, + "credential" BINARY(16) NOT NULL, + CONSTRAINT "fk_account_credentials_account__id" FOREIGN KEY ("account") REFERENCES "accounts"("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "fk_account_credentials_credential__id" FOREIGN KEY ("credential") REFERENCES "credentials"("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +-- ---------------------------------- +-- Keys index +-- ---------------------------------- +CREATE UNIQUE INDEX "keys_kid" ON "keys" ("kid"); +-- ---------------------------------- +-- Dids index +-- ---------------------------------- +CREATE UNIQUE INDEX "dids_did" ON "dids" ("did"); +-- ---------------------------------- +-- Credentials index +-- ---------------------------------- +CREATE UNIQUE INDEX "credentials_cid" ON "credentials" ("cid") \ No newline at end of file