diff --git a/src/main/kotlin/com/api/wallet/domain/nft/Nft.kt b/src/main/kotlin/com/api/wallet/domain/nft/Nft.kt index 4b19514..18b0e55 100644 --- a/src/main/kotlin/com/api/wallet/domain/nft/Nft.kt +++ b/src/main/kotlin/com/api/wallet/domain/nft/Nft.kt @@ -5,7 +5,9 @@ import org.springframework.data.relational.core.mapping.Table @Table("nft") class Nft( - @Id val tokenAddress: String, - val networkType: String + @Id val id: Long? = null, + val tokenId: String, + val tokenAddress: String, + val networkType: String, ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/domain/nft/repository/NftRepository.kt b/src/main/kotlin/com/api/wallet/domain/nft/repository/NftRepository.kt index 6a0220d..1bbf0e5 100644 --- a/src/main/kotlin/com/api/wallet/domain/nft/repository/NftRepository.kt +++ b/src/main/kotlin/com/api/wallet/domain/nft/repository/NftRepository.kt @@ -4,6 +4,7 @@ import com.api.wallet.domain.nft.Nft import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Mono -interface NftRepository : ReactiveCrudRepository, NftRepositorySupport { - fun findByTokenAddressAndNetworkType(address: String, networkType: String): Mono +interface NftRepository : ReactiveCrudRepository, NftRepositorySupport { + + fun findByTokenAddressAndNetworkTypeAndTokenId(address: String, networkType: String,tokenId: String): Mono } \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/domain/transaction/Transaction.kt b/src/main/kotlin/com/api/wallet/domain/transaction/Transaction.kt index 595e94f..47b1002 100644 --- a/src/main/kotlin/com/api/wallet/domain/transaction/Transaction.kt +++ b/src/main/kotlin/com/api/wallet/domain/transaction/Transaction.kt @@ -7,13 +7,13 @@ import java.math.BigDecimal @Table("transaction") class Transaction( @Id val id: Long? = null, - val nftId: String, // 외래키 + val nftId: Long, // 외래키 val toAddress: String, val fromAddress: String, val amount: Int, val value: BigDecimal, val hash: String?, val blockTimestamp: Long?, - val walletId: String?, // 외래키 + val walletId: Long?, // 외래키 ){ } \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/domain/transaction/repository/TransactionRepository.kt b/src/main/kotlin/com/api/wallet/domain/transaction/repository/TransactionRepository.kt index 65103bf..3270a86 100644 --- a/src/main/kotlin/com/api/wallet/domain/transaction/repository/TransactionRepository.kt +++ b/src/main/kotlin/com/api/wallet/domain/transaction/repository/TransactionRepository.kt @@ -7,6 +7,6 @@ import reactor.core.publisher.Flux interface TransactionRepository: ReactiveCrudRepository { - fun findAllByWalletIdOrderByBlockTimestampDesc(address: String,pageable: Pageable?): Flux + fun findAllByWalletIdOrderByBlockTimestampDesc(walletId: Long,pageable: Pageable?): Flux } \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/domain/wallet/Wallet.kt b/src/main/kotlin/com/api/wallet/domain/wallet/Wallet.kt index 84a9624..ded141c 100644 --- a/src/main/kotlin/com/api/wallet/domain/wallet/Wallet.kt +++ b/src/main/kotlin/com/api/wallet/domain/wallet/Wallet.kt @@ -6,7 +6,8 @@ import java.math.BigDecimal @Table("wallet") data class Wallet( - @Id val address: String, + @Id val id: Long? = null, + val address: String, val userId: Long, val networkType: String, var balance: BigDecimal, diff --git a/src/main/kotlin/com/api/wallet/domain/wallet/repository/WalletRepository.kt b/src/main/kotlin/com/api/wallet/domain/wallet/repository/WalletRepository.kt index b151e8a..922aea7 100644 --- a/src/main/kotlin/com/api/wallet/domain/wallet/repository/WalletRepository.kt +++ b/src/main/kotlin/com/api/wallet/domain/wallet/repository/WalletRepository.kt @@ -6,7 +6,7 @@ import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Flux import reactor.core.publisher.Mono -interface WalletRepository : ReactiveCrudRepository, WalletRepositorySupport { +interface WalletRepository : ReactiveCrudRepository, WalletRepositorySupport { fun findByAddressAndNetworkType(address: String, networkType: String): Mono fun findAllByAddress(address: String): Flux } \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/domain/walletNft/WalletNft.kt b/src/main/kotlin/com/api/wallet/domain/walletNft/WalletNft.kt index 6c5517f..8ada726 100644 --- a/src/main/kotlin/com/api/wallet/domain/walletNft/WalletNft.kt +++ b/src/main/kotlin/com/api/wallet/domain/walletNft/WalletNft.kt @@ -6,7 +6,8 @@ import org.springframework.data.relational.core.mapping.Table @Table("wallet_nft") class WalletNft( @Id val id: Long? = null, - val walletId: String, - val nftId: String + val walletId: Long, + val nftId: Long, + val amount: Int, ) { } \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepository.kt b/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepository.kt index ac28341..08b6502 100644 --- a/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepository.kt +++ b/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepository.kt @@ -1,14 +1,16 @@ package com.api.wallet.domain.walletNft.repository import com.api.wallet.domain.walletNft.WalletNft +import com.api.wallet.enums.NetworkType +import org.springframework.data.r2dbc.repository.Query import org.springframework.data.domain.Pageable import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Flux import reactor.core.publisher.Mono -interface WalletNftRepository : ReactiveCrudRepository { +interface WalletNftRepository : ReactiveCrudRepository, WalletNftRepositorySupport { - fun findByWalletId(address: String): Flux + fun findByWalletId(walletId: Long): Flux - fun deleteByNftIdAndWalletId(tokenAddress: String,walletAddress: String): Mono + fun deleteByNftIdAndWalletId(nftId: Long,walletId: Long): Mono } \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepositorySupport.kt b/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepositorySupport.kt new file mode 100644 index 0000000..083d773 --- /dev/null +++ b/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepositorySupport.kt @@ -0,0 +1,10 @@ +package com.api.wallet.domain.walletNft.repository + +import com.api.wallet.enums.NetworkType +import reactor.core.publisher.Flux + +interface WalletNftRepositorySupport { + + fun findByWalletIdJoinNft(address: String, networkType: String) : Flux + +} diff --git a/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepositorySupportImpl.kt b/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepositorySupportImpl.kt new file mode 100644 index 0000000..4a4d8a7 --- /dev/null +++ b/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftRepositorySupportImpl.kt @@ -0,0 +1,40 @@ +package com.api.wallet.domain.walletNft.repository + +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate +import reactor.core.publisher.Flux + +class WalletNftRepositorySupportImpl( + private val r2dbcEntityTemplate: R2dbcEntityTemplate +): WalletNftRepositorySupport { + + + override fun findByWalletIdJoinNft(address: String, networkType: String): Flux { + val query = """ + SELECT + wn.id AS wn_id, + wn.wallet_id AS wallet_address, + n.id AS nft_id, + n.token_address AS nft_token_address, + n.token_id AS nft_token_id + FROM wallet_nft wn + JOIN wallet w ON wn.wallet_id = w.id + JOIN nft n ON wn.nft_id = n.id + WHERE w.address = $1 AND w.network_type = $2 + """ + + return r2dbcEntityTemplate.databaseClient.sql(query) + .bind(0, address) + .bind(1, networkType) + .map { row, metadata -> + WalletNftWithNft( + id = (row.get("wn_id") as Number).toLong(), + walletId = (row.get("wallet_id") as Number).toLong(), + nftId = (row.get("nft_id") as Number).toLong(), + nftTokenAddress = row.get("nft_token_address", String::class.java)!!, + nftTokenId = row.get("nft_token_id", String::class.java)!! + ) + } + .all() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftWithNft.kt b/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftWithNft.kt new file mode 100644 index 0000000..bac52fd --- /dev/null +++ b/src/main/kotlin/com/api/wallet/domain/walletNft/repository/WalletNftWithNft.kt @@ -0,0 +1,10 @@ +package com.api.wallet.domain.walletNft.repository + + +data class WalletNftWithNft( + val id: Long, + val walletId: Long, + val nftId: Long, + val nftTokenAddress: String, + val nftTokenId: String, +) diff --git a/src/main/kotlin/com/api/wallet/service/api/NftService.kt b/src/main/kotlin/com/api/wallet/service/api/NftService.kt index dc7ca4c..c6f6b79 100644 --- a/src/main/kotlin/com/api/wallet/service/api/NftService.kt +++ b/src/main/kotlin/com/api/wallet/service/api/NftService.kt @@ -6,6 +6,7 @@ import com.api.wallet.domain.wallet.Wallet import com.api.wallet.domain.wallet.repository.WalletRepository import com.api.wallet.domain.walletNft.WalletNft import com.api.wallet.domain.walletNft.repository.WalletNftRepository +import com.api.wallet.domain.walletNft.repository.WalletNftWithNft import com.api.wallet.enums.NetworkType import com.api.wallet.service.moralis.MoralisService import com.api.wallet.service.moralis.dto.response.NFTResult @@ -26,14 +27,16 @@ class NftService( private val walletNftRepository: WalletNftRepository, ) { - fun findByTokenAddress(tokenAddress: String,networkType: String): Mono { - return nftRepository.findByTokenAddressAndNetworkType(tokenAddress,networkType) + fun findByTokenAddress(tokenAddress: String,networkType: String,tokenId:String): Mono { + return nftRepository.findByTokenAddressAndNetworkTypeAndTokenId(tokenAddress,networkType,tokenId) .switchIfEmpty( - nftRepository.insert(Nft(tokenAddress,networkType) - )) + nftRepository.insert( + Nft( + tokenId = tokenId, tokenAddress = tokenAddress, networkType = networkType) + ) + ) } - //TODO("반환값 재정의 : nft: 메타데이터 필요") @Transactional fun readAllNftByWallet(address: String, networkType:NetworkType?, pageable: Pageable): Mono> { @@ -57,33 +60,36 @@ class NftService( } private fun getNftByWallet(wallet: Wallet): Flux { - val response = moralisService.getNFTsByAddress(wallet.address, wallet.networkType.convertNetworkTypeToChainType()) //11 - val getNftsByWallet = walletNftRepository.findByWalletId(wallet.address) + val response = moralisService.getNFTsByAddress(wallet.address, wallet.networkType.convertNetworkTypeToChainType()) + val getNftsByWallet = walletNftRepository.findByWalletIdJoinNft(wallet.address,wallet.networkType) return Mono.zip(response, getNftsByWallet.collectList()) .flatMapMany { tuple -> - val responseNfts = tuple.t1.result.associateBy { it.tokenAddress } - val getNfts = tuple.t2.associateBy { it.nftId } // 0 + val responseNfts = tuple.t1.result.associateBy { Pair(it.tokenAddress,it.tokenId) } + val getNfts = tuple.t2.associateBy { Pair(it.nftTokenAddress,it.nftTokenId) } + deleteToWalletNft(responseNfts, getNfts, wallet) .thenMany(addToWalletNft(responseNfts, getNfts, wallet)) - .thenMany(walletNftRepository.findByWalletId(wallet.address)) + .thenMany(walletNftRepository.findByWalletId(wallet.id!!)) } } private fun addToWalletNft( - responseNftsMap: Map, - getNftsMap: Map, + responseNftsMap: Map,NFTResult>, + getNftsMap: Map,WalletNftWithNft>, wallet: Wallet, ): Flux { return Flux.fromIterable(responseNftsMap.keys).filter{ !getNftsMap.containsKey(it) } - .flatMap { tokenAddress -> - findByTokenAddress(tokenAddress,wallet.networkType).flatMap { nft-> + .flatMap { + val data = responseNftsMap[Pair(it.first,it.second)] + findByTokenAddress(data!!.tokenAddress,wallet.networkType,data!!.tokenId).flatMap { nft-> walletNftRepository.save( WalletNft( - walletId = wallet.address, - nftId = nft.tokenAddress + walletId = wallet.id!!, + nftId = nft.id!!, + amount = data.amount!!.toInt() ) ) } @@ -91,15 +97,16 @@ class NftService( } private fun deleteToWalletNft( - responseNftsMap: Map, - getNftsMap: Map, + responseNftsMap: Map,NFTResult>, + getNftsMap: Map,WalletNftWithNft>, wallet: Wallet, ): Flux { return Flux.fromIterable(getNftsMap.keys) .filter { !responseNftsMap.containsKey(it) } - .flatMap { tokenAddress -> - walletNftRepository.deleteByNftIdAndWalletId(tokenAddress,wallet.address) + .flatMap { + val data = getNftsMap[Pair(it.first,it.second)] + walletNftRepository.deleteByNftIdAndWalletId(data!!.nftId,data.walletId) } } diff --git a/src/main/kotlin/com/api/wallet/service/api/WalletService.kt b/src/main/kotlin/com/api/wallet/service/api/WalletService.kt index b62a5a7..d991252 100644 --- a/src/main/kotlin/com/api/wallet/service/api/WalletService.kt +++ b/src/main/kotlin/com/api/wallet/service/api/WalletService.kt @@ -6,7 +6,6 @@ import com.api.wallet.domain.user.Users import com.api.wallet.domain.user.repository.UserRepository import com.api.wallet.domain.wallet.Wallet import com.api.wallet.domain.wallet.repository.WalletRepository -import com.api.wallet.enums.ChainType import com.api.wallet.enums.NetworkType import com.api.wallet.service.infura.InfuraApiService import com.api.wallet.util.Util.convertNetworkTypeToChainType @@ -56,7 +55,7 @@ class WalletService( private fun createUserAndWallet(address: String, network: Network): Mono { return userRepository.save(Users(nickName = "Unknown")) .flatMap { user -> - walletRepository.insert(Wallet( + walletRepository.save(Wallet( address = address, userId = user.id!!, networkType = network.type!!, @@ -77,12 +76,4 @@ class WalletService( .map { wallet.updateBalance(it) } .flatMap { walletRepository.save(it) } } - - private fun convertNetworkTypeToChainType(networkType: NetworkType): ChainType { - return when (networkType) { - NetworkType.ETHEREUM -> ChainType.ETHEREUM_MAINNET - NetworkType.POLYGON -> ChainType.POLYGON_MAINNET - - } - } } \ No newline at end of file diff --git a/src/main/kotlin/com/api/wallet/service/api/WalletTransactionService.kt b/src/main/kotlin/com/api/wallet/service/api/WalletTransactionService.kt index 5d19116..e3c740d 100644 --- a/src/main/kotlin/com/api/wallet/service/api/WalletTransactionService.kt +++ b/src/main/kotlin/com/api/wallet/service/api/WalletTransactionService.kt @@ -45,7 +45,7 @@ class WalletTransactionService( } private fun getTransactions(wallet: Wallet, pageable: Pageable): Flux { - val updateFlux = transactionRepository.findAllByWalletIdOrderByBlockTimestampDesc(wallet.address,pageable) + val updateFlux = transactionRepository.findAllByWalletIdOrderByBlockTimestampDesc(wallet.id!!,pageable) .collectList() .flatMapMany { val lastBlockTimestamp = it.firstOrNull()?.blockTimestamp?.plus(10000) @@ -63,7 +63,7 @@ class WalletTransactionService( } return updateFlux.thenMany( - transactionRepository.findAllByWalletIdOrderByBlockTimestampDesc(wallet.address, pageable) + transactionRepository.findAllByWalletIdOrderByBlockTimestampDesc(wallet.id!!, pageable) ) } @@ -71,22 +71,22 @@ class WalletTransactionService( private fun saveOrUpdate(results: Flux,wallet: Wallet): Flux { return results.flatMap {result -> - nftService.findByTokenAddress(result.tokenAddress,wallet.networkType).flatMap { nft-> + nftService.findByTokenAddress(result.tokenAddress,wallet.networkType,result.tokenId).flatMap { nft-> transactionRepository.save( Transaction( - nftId = nft.tokenAddress, + nftId = nft.id!!, toAddress = result.toAddress, fromAddress = result.fromAddress, amount = result.amount.toInt(), value = result.value.toBigDecimal(), hash = result.blockHash, blockTimestamp = result.blockTimestamp.toTimestamp(), - walletId = wallet.address + walletId = wallet.id!! ) ) } - - } + }.collectList() + .flatMapMany { Flux.fromIterable(it) } } } \ No newline at end of file diff --git a/src/main/resources/db/postgresql/migration/V1__Initial_schema.sql b/src/main/resources/db/postgresql/migration/V1__Initial_schema.sql index 8538764..fedba40 100644 --- a/src/main/resources/db/postgresql/migration/V1__Initial_schema.sql +++ b/src/main/resources/db/postgresql/migration/V1__Initial_schema.sql @@ -13,7 +13,8 @@ CREATE TABLE IF NOT EXISTS network ( ); CREATE TABLE IF NOT EXISTS wallet ( - address VARCHAR(255) PRIMARY KEY, + id SERIAL PRIMARY KEY, + address VARCHAR(255) NOT NULL, balance DECIMAL(19, 4) NOT NULL, created_At BIGINT, updated_At BIGINT, @@ -28,25 +29,28 @@ CREATE TABLE IF NOT EXISTS wallet ( ); CREATE TABLE IF NOT EXISTS nft ( - token_address VARCHAR(255) PRIMARY KEY, + id SERIAL PRIMARY KEY, + token_id VARCHAR(255) NOT NULL, + token_address VARCHAR(255) NOT NULL, network_type varchar(100) REFERENCES network(type) ); CREATE TABLE IF NOT EXISTS transaction ( id SERIAL PRIMARY KEY, - nft_id VARCHAR(255) REFERENCES nft(token_address), + nft_id BIGINT REFERENCES nft(id), to_address VARCHAR(255) NOT NULL, from_address VARCHAR(255) NOT NULL, amount INT, value NUMERIC, hash VARCHAR(255), block_timestamp BIGINT, - wallet_id VARCHAR(255) REFERENCES wallet(address) + wallet_id BIGINT REFERENCES wallet(id) ); CREATE TABLE IF NOT EXISTS wallet_nft ( id SERIAL PRIMARY KEY, - wallet_id VARCHAR(255) REFERENCES wallet(address), - nft_id VARCHAR(255) REFERENCES nft(token_address) -) + wallet_id BIGINT REFERENCES wallet(id), + nft_id BIGINT REFERENCES nft(id), + amount INT +); diff --git a/src/test/kotlin/com/api/wallet/ValidatorTest.kt b/src/test/kotlin/com/api/wallet/ValidatorTest.kt index 5dd7309..3f4747e 100644 --- a/src/test/kotlin/com/api/wallet/ValidatorTest.kt +++ b/src/test/kotlin/com/api/wallet/ValidatorTest.kt @@ -119,7 +119,7 @@ class ValidatorTest( @Test fun tansferasdas() { val address = "0x01b72b4aa3f66f213d62d53e829bc172a6a72867" - val pagebale = PageRequest.of(2,4) + val pagebale = PageRequest.of(0,20) val networkType = NetworkType.POLYGON val transaction: Page? = walletTransactionService.readAllTransactions(address,networkType,pagebale).block() @@ -139,7 +139,7 @@ class ValidatorTest( @Test fun readAllNfts() { val address = "0x01b72b4aa3f66f213d62d53e829bc172a6a72867" - val pagebale = PageRequest.of(0,3) + val pagebale = PageRequest.of(0,10) val nftList= nftService.readAllNftByWallet(address,null,pagebale).block() println("total element : "+nftList?.totalElements)