diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/otc/CurrencyExchangeRatesResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/otc/CurrencyExchangeRatesResponse.kt index 5dfe7fb8e..f21c1760c 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/otc/CurrencyExchangeRatesResponse.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/otc/CurrencyExchangeRatesResponse.kt @@ -5,7 +5,9 @@ import java.math.BigDecimal data class CurrencyExchangeRate( val sourceSymbol: String, val destSymbol: String, - val rate: BigDecimal + val rate: BigDecimal, + val isSwappable: Boolean + ) data class CurrencyExchangeRatesResponse(val rates: List) diff --git a/auth-gateway/auth-gateway-app/src/main/resources/application.yml b/auth-gateway/auth-gateway-app/src/main/resources/application.yml index 798e9dad6..3948349e6 100644 --- a/auth-gateway/auth-gateway-app/src/main/resources/application.yml +++ b/auth-gateway/auth-gateway-app/src/main/resources/application.yml @@ -71,7 +71,7 @@ app: url: http://opex-otp/v1 captcha: url: http://opex-captcha - enabled: false + enabled: ${CAPTCHA_ENABLED:true} device-management: url: http://opex-device-management custom-message: diff --git a/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt b/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt index a93fffd30..b87761b06 100644 --- a/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt +++ b/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt @@ -114,6 +114,8 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus WithdrawCannotBeRequested(6049, "Withdraw cannot be requested", HttpStatus.BAD_REQUEST), OTPCannotBeRequested(6050, "OTP cannot be requested", HttpStatus.BAD_REQUEST), WithdrawRequestExpired(6051,"Withdraw request expired", HttpStatus.BAD_REQUEST), + ForbiddenSwapPair(6052, null, HttpStatus.BAD_REQUEST), + // code 7000: api OrderNotFound(7001, "No order found", HttpStatus.NOT_FOUND), diff --git a/docker-compose.yml b/docker-compose.yml index d1119e80b..8b5ec8b7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -406,6 +406,7 @@ services: - ADMIN_CLIENT_SECRET=${KC_ADMIN_CLIENT_SECRET} - PRE_AUTH_CLIENT_SECRET=${KC_PRE_AUTH_CLIENT_SECRET} - TOKEN_ISSUER_URL=${KC_ISSUER_URL} + - CAPTCHA_ENABLED=${CAPTCHA_ENABLED} volumes: - auth-gateway-keys:/app/keys depends_on: diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/CurrencyRatesController.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/CurrencyRatesController.kt index b40a4222d..5c4729daa 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/CurrencyRatesController.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/CurrencyRatesController.kt @@ -1,14 +1,11 @@ package co.nilin.opex.wallet.app.controller -import co.nilin.opex.wallet.app.dto.CurrencyExchangeRate import co.nilin.opex.wallet.app.dto.CurrencyExchangeRatesResponse import co.nilin.opex.wallet.app.dto.CurrencyPair import co.nilin.opex.wallet.app.dto.SetCurrencyExchangeRateRequest import co.nilin.opex.wallet.app.service.otc.GraphService import co.nilin.opex.wallet.core.inout.CurrencyPrice - import co.nilin.opex.wallet.core.model.otc.* - import io.swagger.annotations.ApiResponse import io.swagger.annotations.Example import io.swagger.annotations.ExampleProperty @@ -127,6 +124,44 @@ class CurrencyRatesController( return rateService.getForbiddenPairs() } + @PostMapping("/forbidden-swap-pairs") + @ApiResponse( + message = "OK", + code = 200, + ) + suspend fun addForbiddenSwapPair(@RequestBody request: CurrencyPair) { + request.validate() + rateService.addForbiddenSwapPair(ForbiddenSwapPair(request.sourceSymbol, request.destSymbol)) + } + + @DeleteMapping("/forbidden-swap-pairs/{sourceSymbol}/{destSymbol}") + @ApiResponse( + message = "OK", + code = 200, + ) + suspend fun deleteForbiddenSwapPair( + @PathVariable sourceSymbol: String, + @PathVariable destSymbol: String + ): ForbiddenSwapPairs { + return rateService.deleteForbiddenSwapPair(ForbiddenSwapPair(sourceSymbol, destSymbol)) + } + + @GetMapping("/forbidden-swap-pairs") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "[{\"sourceSymbol\": \"BTC\",\n" + + " \"destSymbol\": \"ETH\" }]", + mediaType = "application/json" + ) + ) + ) + suspend fun fetchForbiddenSwapPairs(): ForbiddenSwapPairs { + return rateService.getForbiddenSwapPairs() + } + @PostMapping("/transitive-symbols") @ApiResponse( message = "OK", @@ -175,10 +210,7 @@ class CurrencyRatesController( @RequestParam("sourceSymbol") sourceSymbol: String? = null, @RequestParam("destSymbol") destSymbol: String? = null ): CurrencyExchangeRatesResponse { - return CurrencyExchangeRatesResponse( - graphService.buildRoutes(sourceSymbol, destSymbol) - .map { CurrencyExchangeRate(it.getSourceSymbol(), it.getDestSymbol(), it.getRate()) } - ) + return CurrencyExchangeRatesResponse(graphService.getCurrencyExchangeRates(sourceSymbol, destSymbol)) } @GetMapping("/currency/price") diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/CurrencyExchangeRatesResponse.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/CurrencyExchangeRatesResponse.kt index d9c58eff6..f398d9954 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/CurrencyExchangeRatesResponse.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/CurrencyExchangeRatesResponse.kt @@ -6,7 +6,8 @@ import java.math.BigDecimal data class CurrencyExchangeRate( val sourceSymbol: String, val destSymbol: String, - val rate: BigDecimal + val rate: BigDecimal, + val isSwappable: Boolean =true ) data class CurrencyExchangeRatesResponse(val rates: List) \ No newline at end of file diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/otc/GraphService.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/otc/GraphService.kt index 4db86ffe2..eab436728 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/otc/GraphService.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/service/otc/GraphService.kt @@ -1,9 +1,11 @@ package co.nilin.opex.wallet.app.service.otc import co.nilin.opex.common.OpexError +import co.nilin.opex.wallet.app.dto.CurrencyExchangeRate import co.nilin.opex.wallet.core.inout.CurrencyCommand import co.nilin.opex.wallet.core.inout.CurrencyPrice import co.nilin.opex.wallet.core.model.otc.ForbiddenPair +import co.nilin.opex.wallet.core.model.otc.ForbiddenSwapPair import co.nilin.opex.wallet.core.model.otc.Rate import co.nilin.opex.wallet.core.service.otc.RateService import co.nilin.opex.wallet.core.spi.CurrencyServiceManager @@ -33,7 +35,27 @@ class GraphService( accumulator.multiply(element) } } + } + suspend fun getCurrencyExchangeRates(source: String? = null, dest: String? = null): List { + val forbiddenSet = rateService + .getForbiddenSwapPairs() + .forbiddenSwapPairs + ?.toSet() + ?: emptySet() + + val routes = buildRoutes(source, dest) + + val exchangeRates = routes.map { route -> + val isSwappable = ForbiddenSwapPair(route.getSourceSymbol(), route.getDestSymbol()) !in forbiddenSet + CurrencyExchangeRate( + sourceSymbol = route.getSourceSymbol(), + destSymbol = route.getDestSymbol(), + rate = route.getRate(), + isSwappable = isSwappable + ) + } + return exchangeRates } suspend fun buildRoutes(source: String? = null, dest: String? = null): MutableList { @@ -57,6 +79,7 @@ class GraphService( } } return routesWithMax2StepV2 +// .applyForbiddenSwapPairs() } diff --git a/wallet/wallet-app/src/test/kotlin/co/nilin/opex/wallet/app/controller/CurrencyRatesControllerIT.kt b/wallet/wallet-app/src/test/kotlin/co/nilin/opex/wallet/app/controller/CurrencyRatesControllerIT.kt index 6b3dec923..8b98db03f 100644 --- a/wallet/wallet-app/src/test/kotlin/co/nilin/opex/wallet/app/controller/CurrencyRatesControllerIT.kt +++ b/wallet/wallet-app/src/test/kotlin/co/nilin/opex/wallet/app/controller/CurrencyRatesControllerIT.kt @@ -91,7 +91,8 @@ class CurrencyRatesControllerIT : KafkaEnabledTest() { CurrencyExchangeRate( "E", "U", - BigDecimal.TEN + BigDecimal.TEN, + true ) ) ), routes @@ -121,7 +122,8 @@ class CurrencyRatesControllerIT : KafkaEnabledTest() { Assertions.assertEquals( CurrencyExchangeRatesResponse( listOf( - CurrencyExchangeRate("E", "U", BigDecimal.TEN), CurrencyExchangeRate("B", "U", BigDecimal.TEN) + CurrencyExchangeRate("E", "U", BigDecimal.TEN, true), + CurrencyExchangeRate("B", "U", BigDecimal.TEN, true) ) ), allRates ) diff --git a/wallet/wallet-app/src/test/resources/application.yml b/wallet/wallet-app/src/test/resources/application.yml index 452b68c28..066434332 100644 --- a/wallet/wallet-app/src/test/resources/application.yml +++ b/wallet/wallet-app/src/test/resources/application.yml @@ -81,7 +81,7 @@ testcontainers: db: username: ${dbusername:opex} password: ${dbpassword:hiopex} - name: wallet + name: opex version: "postgres:14.9" logging: level: diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/otc/ForbiddenSwapPair.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/otc/ForbiddenSwapPair.kt new file mode 100644 index 000000000..fa13ae8bf --- /dev/null +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/otc/ForbiddenSwapPair.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.wallet.core.model.otc + +data class ForbiddenSwapPair( + val sourceSymbol: String, val destinationSymbol: String +) + +data class ForbiddenSwapPairs( + var forbiddenSwapPairs: List? +) \ No newline at end of file diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/otc/RateService.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/otc/RateService.kt index 1ea9f80fc..a98152956 100644 --- a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/otc/RateService.kt +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/otc/RateService.kt @@ -20,6 +20,11 @@ interface RateService { suspend fun getForbiddenPairs(): ForbiddenPairs + suspend fun addForbiddenSwapPair(forbiddenPair: ForbiddenSwapPair) + + suspend fun deleteForbiddenSwapPair(forbiddenPair: ForbiddenSwapPair): ForbiddenSwapPairs + + suspend fun getForbiddenSwapPairs(): ForbiddenSwapPairs suspend fun addTransitiveSymbols(symbols: Symbols) diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/ForbiddenSwapPairRepository.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/ForbiddenSwapPairRepository.kt new file mode 100644 index 000000000..454c622ba --- /dev/null +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/ForbiddenSwapPairRepository.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.wallet.ports.postgres.dao + +import co.nilin.opex.wallet.ports.postgres.model.ForbiddenSwapPairModel +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono + +@Repository +interface ForbiddenSwapPairRepository : ReactiveCrudRepository { + + fun findAllBy(): Flux? + + fun findBySourceSymbolAndDestinationSymbol( + sourceSymbol: String, + destinationSymbol: String + ): Mono? + + fun deleteBySourceSymbolAndDestinationSymbol(sourceSymbol: String, destinationSymbol: String): Mono? + +} \ No newline at end of file diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/RateServiceImpl.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/RateServiceImpl.kt index 732928fab..bec0d14f4 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/RateServiceImpl.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/RateServiceImpl.kt @@ -5,9 +5,11 @@ import co.nilin.opex.wallet.core.model.otc.* import co.nilin.opex.wallet.core.service.otc.RateService import co.nilin.opex.wallet.ports.postgres.dao.CurrencyRepositoryV2 import co.nilin.opex.wallet.ports.postgres.dao.ForbiddenPairRepository +import co.nilin.opex.wallet.ports.postgres.dao.ForbiddenSwapPairRepository import co.nilin.opex.wallet.ports.postgres.dao.RatesRepository import co.nilin.opex.wallet.ports.postgres.model.CurrencyModel import co.nilin.opex.wallet.ports.postgres.model.ForbiddenPairModel +import co.nilin.opex.wallet.ports.postgres.model.ForbiddenSwapPairModel import co.nilin.opex.wallet.ports.postgres.model.RateModel import kotlinx.coroutines.reactive.awaitFirstOrNull import org.slf4j.LoggerFactory @@ -19,6 +21,7 @@ import java.util.stream.Collectors class RateServiceImpl( private val ratesRepository: RatesRepository, private val forbiddenPairRepository: ForbiddenPairRepository, + private val forbiddenSwapPairRepository: ForbiddenSwapPairRepository, private val currencyRepository: CurrencyRepositoryV2 ) : RateService { @@ -52,36 +55,39 @@ class RateServiceImpl( } override suspend fun deleteRate(rate: Rate): Rates { - return Rates(ratesRepository - .findBySourceSymbolAndDestinationSymbol(rate.sourceSymbol, rate.destSymbol)?.awaitFirstOrNull()?.let { - ratesRepository.deleteBySourceSymbolAndDestinationSymbol(rate.sourceSymbol, rate.destSymbol) - ?.awaitFirstOrNull().let { - ratesRepository.findAll().map { it.toDto() }.collect(Collectors.toList()).awaitFirstOrNull() - } - } ?: throw OpexError.PairNotFound.exception()) + return Rates( + ratesRepository + .findBySourceSymbolAndDestinationSymbol(rate.sourceSymbol, rate.destSymbol)?.awaitFirstOrNull()?.let { + ratesRepository.deleteBySourceSymbolAndDestinationSymbol(rate.sourceSymbol, rate.destSymbol) + ?.awaitFirstOrNull().let { + ratesRepository.findAll().map { it.toDto() }.collect(Collectors.toList()).awaitFirstOrNull() + } + } ?: throw OpexError.PairNotFound.exception()) } override suspend fun updateRate(rate: Rate): Rates { - return Rates(ratesRepository - .findBySourceSymbolAndDestinationSymbol(rate.sourceSymbol, rate.destSymbol)?.awaitFirstOrNull()?.let { it -> - ratesRepository.save( - RateModel( - it.id, - rate.sourceSymbol, - rate.destSymbol, - rate.rate, - LocalDateTime.now(), - it.createDate - ) - )?.awaitFirstOrNull().let { - ratesRepository.findAll().map { it.toDto() }.collect(Collectors.toList()).awaitFirstOrNull() + return Rates( + ratesRepository + .findBySourceSymbolAndDestinationSymbol(rate.sourceSymbol, rate.destSymbol)?.awaitFirstOrNull() + ?.let { it -> + ratesRepository.save( + RateModel( + it.id, + rate.sourceSymbol, + rate.destSymbol, + rate.rate, + LocalDateTime.now(), + it.createDate + ) + )?.awaitFirstOrNull().let { + ratesRepository.findAll().map { it.toDto() }.collect(Collectors.toList()).awaitFirstOrNull() + } } - } - ?: throw OpexError.PairNotFound.exception()) + ?: throw OpexError.PairNotFound.exception()) } override suspend fun addForbiddenPair(forbiddenPair: ForbiddenPair) { - forbiddenPair.isValid() + isPairValid(forbiddenPair.sourceSymbol, forbiddenPair.destinationSymbol) forbiddenPairRepository.findBySourceSymbolAndDestinationSymbol( forbiddenPair.sourceSymbol, forbiddenPair.destinationSymbol @@ -91,17 +97,18 @@ class RateServiceImpl( } override suspend fun deleteForbiddenPair(forbiddenPair: ForbiddenPair): ForbiddenPairs { - return ForbiddenPairs(forbiddenPairRepository - .findBySourceSymbolAndDestinationSymbol(forbiddenPair.sourceSymbol, forbiddenPair.destinationSymbol) - ?.awaitFirstOrNull()?.let { - forbiddenPairRepository.deleteBySourceSymbolAndDestinationSymbol( - forbiddenPair.sourceSymbol, - forbiddenPair.destinationSymbol - )?.awaitFirstOrNull().let { - forbiddenPairRepository.findAllBy()?.map { it.toDto() }?.collect(Collectors.toList()) - ?.awaitFirstOrNull() - } - } ?: throw OpexError.PairNotFound.exception()) + return ForbiddenPairs( + forbiddenPairRepository + .findBySourceSymbolAndDestinationSymbol(forbiddenPair.sourceSymbol, forbiddenPair.destinationSymbol) + ?.awaitFirstOrNull()?.let { + forbiddenPairRepository.deleteBySourceSymbolAndDestinationSymbol( + forbiddenPair.sourceSymbol, + forbiddenPair.destinationSymbol + )?.awaitFirstOrNull().let { + forbiddenPairRepository.findAllBy()?.map { it.toDto() }?.collect(Collectors.toList()) + ?.awaitFirstOrNull() + } + } ?: throw OpexError.PairNotFound.exception()) } @@ -111,6 +118,41 @@ class RateServiceImpl( ) } + override suspend fun addForbiddenSwapPair(forbiddenSwapPair: ForbiddenSwapPair) { + isPairValid(forbiddenSwapPair.sourceSymbol, forbiddenSwapPair.destinationSymbol) + forbiddenSwapPairRepository.findBySourceSymbolAndDestinationSymbol( + forbiddenSwapPair.sourceSymbol, + forbiddenSwapPair.destinationSymbol + )?.awaitFirstOrNull()?.let { + throw OpexError.PairIsExist.exception() + } ?: forbiddenSwapPairRepository.save(forbiddenSwapPair.toModel()).awaitFirstOrNull() + } + + override suspend fun deleteForbiddenSwapPair(forbiddenSwapPair: ForbiddenSwapPair): ForbiddenSwapPairs { + return ForbiddenSwapPairs( + forbiddenSwapPairRepository + .findBySourceSymbolAndDestinationSymbol( + forbiddenSwapPair.sourceSymbol, + forbiddenSwapPair.destinationSymbol + ) + ?.awaitFirstOrNull()?.let { + forbiddenSwapPairRepository.deleteBySourceSymbolAndDestinationSymbol( + forbiddenSwapPair.sourceSymbol, + forbiddenSwapPair.destinationSymbol + )?.awaitFirstOrNull().let { + forbiddenSwapPairRepository.findAllBy()?.map { it.toDto() }?.collect(Collectors.toList()) + ?.awaitFirstOrNull() + } + } ?: throw OpexError.PairNotFound.exception()) + } + + override suspend fun getForbiddenSwapPairs(): ForbiddenSwapPairs { + return ForbiddenSwapPairs( + forbiddenSwapPairRepository.findAllBy()?.map { it.toDto() }?.collect(Collectors.toList()) + ?.awaitFirstOrNull() + ) + } + override suspend fun addTransitiveSymbols(symbols: Symbols) { symbols.symbols?.forEach { currencyRepository.fetchCurrency(symbol = it)?.awaitFirstOrNull()?.let { @@ -175,6 +217,21 @@ class RateServiceImpl( return ForbiddenPair(sourceSymbol, destinationSymbol) } + private fun ForbiddenSwapPair.toModel(): ForbiddenSwapPairModel { + return ForbiddenSwapPairModel( + null, + sourceSymbol, + destinationSymbol, + LocalDateTime.now(), + LocalDateTime.now() + ) + + } + + private fun ForbiddenSwapPairModel.toDto(): ForbiddenSwapPair { + return ForbiddenSwapPair(sourceSymbol, destinationSymbol) + } + private suspend fun Rate.isValid() { /* val transitives = getTransitiveSymbols().symbols //TODO it's not a valid assumption, it's possible to add direct rate between two non transitive symbol, in this case this rate has priority over indirect rate @@ -191,9 +248,9 @@ class RateServiceImpl( } ?: throw OpexError.CurrencyNotFound.exception() } - private suspend fun ForbiddenPair.isValid() { - currencyRepository.fetchCurrency(symbol = this.sourceSymbol)?.awaitFirstOrNull()?.let { - currencyRepository.fetchCurrency(symbol = this.destinationSymbol)?.awaitFirstOrNull()?.let { + private suspend fun isPairValid(sourceSymbol: String, destinationSymbol: String) { + currencyRepository.fetchCurrency(symbol = sourceSymbol)?.awaitFirstOrNull()?.let { + currencyRepository.fetchCurrency(symbol = destinationSymbol)?.awaitFirstOrNull()?.let { } ?: throw OpexError.CurrencyNotFound.exception() } ?: throw OpexError.CurrencyNotFound.exception() } diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/ReservedTransferImpl.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/ReservedTransferImpl.kt index c0450bf4a..a17598db8 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/ReservedTransferImpl.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/ReservedTransferImpl.kt @@ -1,21 +1,27 @@ package co.nilin.opex.wallet.ports.postgres.impl +import co.nilin.opex.common.OpexError import co.nilin.opex.wallet.core.inout.AdminSwapResponse import co.nilin.opex.wallet.core.inout.SwapResponse import co.nilin.opex.wallet.core.model.otc.ReservedStatus import co.nilin.opex.wallet.core.model.otc.ReservedTransfer import co.nilin.opex.wallet.core.spi.ReservedTransferManager +import co.nilin.opex.wallet.ports.postgres.dao.ForbiddenSwapPairRepository import co.nilin.opex.wallet.ports.postgres.dao.ReservedTransferRepository import co.nilin.opex.wallet.ports.postgres.model.ReservedTransferModel import kotlinx.coroutines.flow.toList import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitFirstOrNull import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component import java.time.LocalDateTime @Component -class ReservedTransferImpl(private val reservedTransferRepository: ReservedTransferRepository) : +class ReservedTransferImpl( + private val reservedTransferRepository: ReservedTransferRepository, + private val forbiddenSwapPairRepository: ForbiddenSwapPairRepository +) : ReservedTransferManager { @Value("\${app.reserved-transfer.life-time}") private var reservedTransferLifeTime: Long? = null @@ -33,6 +39,10 @@ class ReservedTransferImpl(private val reservedTransferRepository: ReservedTrans } override suspend fun reserve(request: ReservedTransfer): ReservedTransfer { + forbiddenSwapPairRepository.findBySourceSymbolAndDestinationSymbol(request.sourceSymbol, request.destSymbol) + ?.awaitFirstOrNull()?.let { + throw OpexError.ForbiddenSwapPair.exception() + } request.apply { reserveDate = LocalDateTime.now() status = ReservedStatus.Created diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/ForbiddenSwapPairModel.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/ForbiddenSwapPairModel.kt new file mode 100644 index 000000000..982895664 --- /dev/null +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/ForbiddenSwapPairModel.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.wallet.ports.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table("forbidden_swap_pair") +data class ForbiddenSwapPairModel( + @Id var id: Long?, + var sourceSymbol: String, + @Column("dest_symbol") + var destinationSymbol: String, + var lastUpdateDate: LocalDateTime = LocalDateTime.now(), + var createDate: LocalDateTime +) \ No newline at end of file diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/resources/db/migration/V7__add_forbidden_swap_pair.sql b/wallet/wallet-ports/wallet-persister-postgres/src/main/resources/db/migration/V7__add_forbidden_swap_pair.sql new file mode 100644 index 000000000..c3cfd20a9 --- /dev/null +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/resources/db/migration/V7__add_forbidden_swap_pair.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS forbidden_swap_pair +( + id SERIAL PRIMARY KEY, + source_symbol VARCHAR(25) NOT NULL REFERENCES currency (symbol), + dest_symbol VARCHAR(25) NOT NULL REFERENCES currency (symbol), + last_update_date TIMESTAMP, + create_date TIMESTAMP +); \ No newline at end of file