Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nft와 wallet 중복 키 발생이슈 #11

Merged
merged 1 commit into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/kotlin/com/api/wallet/domain/nft/Nft.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Nft,String>, NftRepositorySupport {
fun findByTokenAddressAndNetworkType(address: String, networkType: String): Mono<Nft>
interface NftRepository : ReactiveCrudRepository<Nft,Long>, NftRepositorySupport {

fun findByTokenAddressAndNetworkTypeAndTokenId(address: String, networkType: String,tokenId: String): Mono<Nft>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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?, // 외래키
){
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ import reactor.core.publisher.Flux

interface TransactionRepository: ReactiveCrudRepository<Transaction,Long> {

fun findAllByWalletIdOrderByBlockTimestampDesc(address: String,pageable: Pageable?): Flux<Transaction>
fun findAllByWalletIdOrderByBlockTimestampDesc(walletId: Long,pageable: Pageable?): Flux<Transaction>

}
3 changes: 2 additions & 1 deletion src/main/kotlin/com/api/wallet/domain/wallet/Wallet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.springframework.data.repository.reactive.ReactiveCrudRepository
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

interface WalletRepository : ReactiveCrudRepository<Wallet,String>, WalletRepositorySupport {
interface WalletRepository : ReactiveCrudRepository<Wallet,Long>, WalletRepositorySupport {
fun findByAddressAndNetworkType(address: String, networkType: String): Mono<Wallet>
fun findAllByAddress(address: String): Flux<Wallet>
}
5 changes: 3 additions & 2 deletions src/main/kotlin/com/api/wallet/domain/walletNft/WalletNft.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {
}
Original file line number Diff line number Diff line change
@@ -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<WalletNft,Long> {
interface WalletNftRepository : ReactiveCrudRepository<WalletNft,Long>, WalletNftRepositorySupport {

fun findByWalletId(address: String): Flux<WalletNft>
fun findByWalletId(walletId: Long): Flux<WalletNft>

fun deleteByNftIdAndWalletId(tokenAddress: String,walletAddress: String): Mono<Void>
fun deleteByNftIdAndWalletId(nftId: Long,walletId: Long): Mono<Void>
}
Original file line number Diff line number Diff line change
@@ -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<WalletNftWithNft>

}
Original file line number Diff line number Diff line change
@@ -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<WalletNftWithNft> {
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()
}

}
Original file line number Diff line number Diff line change
@@ -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,
)
47 changes: 27 additions & 20 deletions src/main/kotlin/com/api/wallet/service/api/NftService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,14 +27,16 @@ class NftService(
private val walletNftRepository: WalletNftRepository,
) {

fun findByTokenAddress(tokenAddress: String,networkType: String): Mono<Nft> {
return nftRepository.findByTokenAddressAndNetworkType(tokenAddress,networkType)
fun findByTokenAddress(tokenAddress: String,networkType: String,tokenId:String): Mono<Nft> {
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<Page<WalletNft>> {
Expand All @@ -57,49 +60,53 @@ class NftService(
}

private fun getNftByWallet(wallet: Wallet): Flux<WalletNft> {
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<String,NFTResult>,
getNftsMap: Map<String,WalletNft>,
responseNftsMap: Map<Pair<String,String>,NFTResult>,
getNftsMap: Map<Pair<String,String>,WalletNftWithNft>,
wallet: Wallet,
): Flux<WalletNft> {
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()
)
)
}
}
}

private fun deleteToWalletNft(
responseNftsMap: Map<String,NFTResult>,
getNftsMap: Map<String,WalletNft>,
responseNftsMap: Map<Pair<String,String>,NFTResult>,
getNftsMap: Map<Pair<String,String>,WalletNftWithNft>,
wallet: Wallet,
): Flux<Void>
{
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)
}
}

Expand Down
11 changes: 1 addition & 10 deletions src/main/kotlin/com/api/wallet/service/api/WalletService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -56,7 +55,7 @@ class WalletService(
private fun createUserAndWallet(address: String, network: Network): Mono<Wallet> {
return userRepository.save(Users(nickName = "Unknown"))
.flatMap { user ->
walletRepository.insert(Wallet(
walletRepository.save(Wallet(
address = address,
userId = user.id!!,
networkType = network.type!!,
Expand All @@ -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

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class WalletTransactionService(
}

private fun getTransactions(wallet: Wallet, pageable: Pageable): Flux<Transaction> {
val updateFlux = transactionRepository.findAllByWalletIdOrderByBlockTimestampDesc(wallet.address,pageable)
val updateFlux = transactionRepository.findAllByWalletIdOrderByBlockTimestampDesc(wallet.id!!,pageable)
.collectList()
.flatMapMany {
val lastBlockTimestamp = it.firstOrNull()?.blockTimestamp?.plus(10000)
Expand All @@ -63,30 +63,30 @@ class WalletTransactionService(
}

return updateFlux.thenMany(
transactionRepository.findAllByWalletIdOrderByBlockTimestampDesc(wallet.address, pageable)
transactionRepository.findAllByWalletIdOrderByBlockTimestampDesc(wallet.id!!, pageable)
)
}



private fun saveOrUpdate(results: Flux<TransferResult>,wallet: Wallet): Flux<Transaction> {
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) }
}

}
18 changes: 11 additions & 7 deletions src/main/resources/db/postgresql/migration/V1__Initial_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
);

Loading