From cd1c3c3036db7f7c42702fed6d212949a39690f3 Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Tue, 24 Feb 2026 21:09:16 +0330 Subject: [PATCH 01/11] Fixed a bug related to receiving messages when starting the service. --- .../translation/TranslationCacheService.kt | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/common/src/main/kotlin/co/nilin/opex/common/translation/TranslationCacheService.kt b/common/src/main/kotlin/co/nilin/opex/common/translation/TranslationCacheService.kt index a75ced806..8658bfc2e 100644 --- a/common/src/main/kotlin/co/nilin/opex/common/translation/TranslationCacheService.kt +++ b/common/src/main/kotlin/co/nilin/opex/common/translation/TranslationCacheService.kt @@ -13,30 +13,34 @@ import javax.annotation.PreDestroy @Service class TranslationCacheService( - private val configClient: ConfigClient?, - private val messageSource: MessageSource? + private val configClient: ConfigClient?, + private val messageSource: MessageSource? ) { private val cache: MutableMap, MessageTranslation> = ConcurrentHashMap() - private var lastUpdate: Long = System.currentTimeMillis() + private var lastUpdate: Long? = null private var job: Job? = null private val logger = LoggerFactory.getLogger(TranslationCacheService::class.java) - + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) @PostConstruct fun start() { - job = CoroutineScope(Dispatchers.IO).launch { + job = scope.launch { logger.info("Going to get messages which are updated after {}", lastUpdate) while (isActive) { - if (configClient != null) { - val newMessages = configClient.getMessagesUpdatedAfter(cache?.let { lastUpdate }) - newMessages?.forEach { msg -> - cache[Pair(msg.key, msg.language)] = + try { + if (configClient != null) { + val newMessages = configClient.getMessagesUpdatedAfter(lastUpdate) + newMessages?.forEach { msg -> + cache[Pair(msg.key, msg.language)] = MessageTranslation(msg.key, msg.message, msg.language) + } } + lastUpdate = System.currentTimeMillis() + delay(30_000) + } catch (e: Exception) { + logger.error("Error fetching messages", e) } - lastUpdate = System.currentTimeMillis() - delay(30_000) } } } From 1f6699da480f3114c9d2a932df88b95a602d5d49 Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Tue, 24 Feb 2026 21:11:50 +0330 Subject: [PATCH 02/11] Inhance the log message --- .../co/nilin/opex/common/translation/TranslationCacheService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/co/nilin/opex/common/translation/TranslationCacheService.kt b/common/src/main/kotlin/co/nilin/opex/common/translation/TranslationCacheService.kt index 8658bfc2e..c8b2c2b6c 100644 --- a/common/src/main/kotlin/co/nilin/opex/common/translation/TranslationCacheService.kt +++ b/common/src/main/kotlin/co/nilin/opex/common/translation/TranslationCacheService.kt @@ -26,7 +26,7 @@ class TranslationCacheService( @PostConstruct fun start() { job = scope.launch { - logger.info("Going to get messages which are updated after {}", lastUpdate) + logger.info("Going to get messages which are updated after: {}", lastUpdate) while (isActive) { try { if (configClient != null) { From 69eb2f77e23f50f16dc0e875a0b0a551306a79f1 Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Sun, 26 Apr 2026 19:22:38 +0330 Subject: [PATCH 03/11] Develop new services to search on trades and orders --- .../core/inout/AdminOrdersSearchRequest.kt | 17 +++++ .../core/inout/AdminTradesHistoryRequest.kt | 14 ++++ .../opex/api/core/inout/OrderAdminItem.kt | 26 +++++++ .../api/core/inout/ResolveUsersRequest.kt | 5 ++ .../opex/api/core/inout/TradeAdminItem.kt | 28 ++++++++ .../opex/api/core/inout/TradeAdminResponse.kt | 2 +- .../opex/api/core/spi/MarketDataProxy.kt | 2 + .../nilin/opex/api/core/spi/ProfileProxy.kt | 1 + .../controller/TransactionAdminController.kt | 67 ++++++++++++++++- .../ports/opex/service/OwnerNameResolver.kt | 51 +++++++++++++ .../proxy/data/RecentTradesProxyRequest.kt | 13 ++++ .../ports/proxy/impl/MarketDataProxyImpl.kt | 31 ++++++++ .../api/ports/proxy/impl/ProfileProxyImpl.kt | 16 +++++ .../market/app/controller/AdminController.kt | 19 +++++ .../app/data/AdminTradesHistoryRequest.kt | 14 ++++ .../nilin/opex/market/core/inout/TradeData.kt | 8 +++ .../market/core/spi/MarketQueryHandler.kt | 13 ++++ .../ports/postgres/dao/TradeRepository.kt | 54 ++++++++++++++ .../postgres/impl/MarketQueryHandlerImpl.kt | 71 +++++++++++++++++++ 19 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersSearchRequest.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminTradesHistoryRequest.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderAdminItem.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/ResolveUsersRequest.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminItem.kt create mode 100644 api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/service/OwnerNameResolver.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/RecentTradesProxyRequest.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminTradesHistoryRequest.kt diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersSearchRequest.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersSearchRequest.kt new file mode 100644 index 000000000..675799ebc --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersSearchRequest.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.api.core.inout + +import co.nilin.opex.api.core.inout.MatchingOrderType +import co.nilin.opex.api.core.inout.OrderDirection + +// Admin orders search request (API-only wrapper) +data class AdminOrdersSearchRequest( + val creatorUuid: String, + val symbol: String?, + val startTime: Long?, + val endTime: Long?, + val orderType: MatchingOrderType?, + val direction: OrderDirection?, + val limit: Int? = 100, + val offset: Int? = 0, + val includeNames: Boolean = false, +) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminTradesHistoryRequest.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminTradesHistoryRequest.kt new file mode 100644 index 000000000..70ce71644 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminTradesHistoryRequest.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.api.core.inout + +data class AdminTradesHistoryRequest( + val baseAsset: String?, + val quoteAsset: String?, + val makerUuid: String?, + val takerUuid: String?, + val fromDate: Long?, + val toDate: Long?, + val excludeSelfTrade: Boolean = true, + val limit: Int, + val offset: Int, + val includeNames: Boolean = false, +) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderAdminItem.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderAdminItem.kt new file mode 100644 index 000000000..b72b693ff --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderAdminItem.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal +import java.time.LocalDateTime + +// Admin-facing order item returned by API wrapper around Market order history +// Adds creatorUuid and optional creatorOwnerName for human readability. +data class OrderAdminItem( + val symbol: String, + val orderId: Long, + val orderType: MatchingOrderType, + val side: OrderDirection, + val price: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, + val executedQuantity: BigDecimal, + val takerFee: BigDecimal, + val makerFee: BigDecimal, + val status: Int, + val appearance: Int, + val createDate: LocalDateTime, + val updateDate: LocalDateTime, + // Enrichment fields (API only) + val creatorUuid: String, + val creatorOwnerName: String? = null, +) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/ResolveUsersRequest.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/ResolveUsersRequest.kt new file mode 100644 index 000000000..66d781fcc --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/ResolveUsersRequest.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.api.core.inout + +data class ResolveUsersRequest( + val uuids: List +) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminItem.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminItem.kt new file mode 100644 index 000000000..a5f748f2b --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminItem.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal +import java.time.LocalDateTime + + +data class TradeAdminItem( + val tradeId: Long, + val symbol: String, + val baseAsset: String?, + val quoteAsset: String?, + val matchedPrice: BigDecimal, + val matchedQuantity: BigDecimal, + val takerPrice: BigDecimal, + val makerPrice: BigDecimal, + val tradeDate: LocalDateTime, + val makerUuid: String, + val takerUuid: String, + val makerOuid: String?, + val takerOuid: String?, + val makerCommission: BigDecimal?, + val takerCommission: BigDecimal?, + val makerCommissionAsset: String?, + val takerCommissionAsset: String?, + // Enriched in API when includeNames=true + val makerOwnerName: String? = null, + val takerOwnerName: String? = null, +) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminResponse.kt index 45594248c..9164db6f5 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminResponse.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminResponse.kt @@ -2,7 +2,7 @@ package co.nilin.opex.api.core.inout import java.math.BigDecimal import java.time.LocalDateTime - +@Deprecated("Use TradeResponseItem") data class TradeAdminResponse( val id: Long, val currency: String, diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt index f58daca30..41663ae11 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt @@ -17,6 +17,8 @@ interface MarketDataProxy { suspend fun recentTrades(symbol: String, limit: Int): List + suspend fun recentTrades(token: String, request: AdminTradesHistoryRequest): List + suspend fun lastPrice(symbol: String?): List suspend fun getBestPriceForSymbols(symbols: List): List diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/ProfileProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/ProfileProxy.kt index 85b1bec2e..d5d08aef0 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/ProfileProxy.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/ProfileProxy.kt @@ -28,6 +28,7 @@ interface ProfileProxy { request: UpdateApprovalRequestBody ): ProfileApprovalAdminResponse + suspend fun resolveUsers(token: String, request: ResolveUsersRequest): Map // Address Book suspend fun addAddressBook(token: String, request: AddAddressBookItemRequest): AddressBookResponse diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/TransactionAdminController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/TransactionAdminController.kt index c55f72079..8798e1e2f 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/TransactionAdminController.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/TransactionAdminController.kt @@ -2,6 +2,9 @@ package co.nilin.opex.api.ports.opex.controller import co.nilin.opex.api.core.inout.* import co.nilin.opex.api.core.spi.WalletProxy +import co.nilin.opex.api.core.spi.MarketDataProxy +import co.nilin.opex.api.core.spi.MarketUserDataProxy +import co.nilin.opex.api.ports.opex.service.OwnerNameResolver import co.nilin.opex.api.ports.opex.util.jwtAuthentication import co.nilin.opex.api.ports.opex.util.tokenValue import org.springframework.security.core.annotation.CurrentSecurityContext @@ -16,6 +19,9 @@ import org.springframework.web.bind.annotation.RestController @RequestMapping("/opex/v1/admin/transactions") class TransactionAdminController( private val walletProxy: WalletProxy, + private val marketDataProxy: MarketDataProxy, + private val marketUserDataProxy: MarketUserDataProxy, + private val ownerNameResolver: OwnerNameResolver, ) { @PostMapping("/summary") @@ -59,6 +65,7 @@ class TransactionAdminController( ) } + @Deprecated("Use /v2/trades instead") // This part is temporary and the structure of fetching trades needs to be fixed. @PostMapping("/trades") suspend fun getTransactionHistory( @@ -67,4 +74,62 @@ class TransactionAdminController( ): List { return walletProxy.getTradeHistoryForAdmin(securityContext.jwtAuthentication().tokenValue(), request) } -} \ No newline at end of file + + @PostMapping("/market-trades/search") + suspend fun searchMarketTrades( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestBody request: AdminTradesHistoryRequest, + ): List { + val token = securityContext.jwtAuthentication().tokenValue() + val items = marketDataProxy.recentTrades(token, request) + if (!request.includeNames) return items + val uuids = items.flatMap { listOfNotNull(it.makerUuid, it.takerUuid) }.toSet() + val nameMap = ownerNameResolver.resolve(token, uuids) + return items.map { it.copy( + makerOwnerName = nameMap[it.makerUuid], + takerOwnerName = nameMap[it.takerUuid] + ) } + } + + @PostMapping("/market-orders/search") + suspend fun searchMarketOrders( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestBody request: AdminOrdersSearchRequest, + ): List { + val token = securityContext.jwtAuthentication().tokenValue() + val orders = marketUserDataProxy.getOrderHistory( + uuid = request.creatorUuid, + symbol = request.symbol, + startTime = request.startTime, + endTime = request.endTime, + orderType = request.orderType, + direction = request.direction, + limit = request.limit, + offset = request.offset, + ) + val items = orders.map { od -> + OrderAdminItem( + symbol = od.symbol, + orderId = od.orderId, + orderType = od.orderType, + side = od.side, + price = od.price, + quantity = od.quantity, + quoteQuantity = od.quoteQuantity, + executedQuantity = od.executedQuantity, + takerFee = od.takerFee, + makerFee = od.makerFee, + status = od.status, + appearance = od.appearance, + createDate = od.createDate, + updateDate = od.updateDate, + creatorUuid = request.creatorUuid, + creatorOwnerName = null, + ) + } + if (!request.includeNames) return items + val nameMap = ownerNameResolver.resolve(token, setOf(request.creatorUuid)) + val name = nameMap[request.creatorUuid] + return items.map { it.copy(creatorOwnerName = name) } + } +} diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/service/OwnerNameResolver.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/service/OwnerNameResolver.kt new file mode 100644 index 000000000..7b0bb6cd7 --- /dev/null +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/service/OwnerNameResolver.kt @@ -0,0 +1,51 @@ +package co.nilin.opex.api.ports.opex.service + +import co.nilin.opex.api.core.inout.ResolveUsersRequest +import co.nilin.opex.api.core.spi.ProfileProxy +import org.springframework.stereotype.Component +import java.time.Duration +import java.time.Instant +import java.util.concurrent.ConcurrentHashMap + +@Component +class OwnerNameResolver( + private val profileProxy: ProfileProxy, +) { + private data class CacheEntry(val name: String?, val expiresAt: Long) + + private val cache = ConcurrentHashMap() + private val ttl: Duration = Duration.ofDays(1) + + suspend fun resolve(token: String, uuids: Set): Map { + if (uuids.isEmpty()) return emptyMap() + + val now = Instant.now().toEpochMilli() + val cached = mutableMapOf() + val missing = mutableListOf() + + uuids.forEach { uuid -> + val entry = cache[uuid] + if (entry != null && entry.expiresAt > now) { + cached[uuid] = entry.name + } else { + missing.add(uuid) + } + } + + if (missing.isNotEmpty()) { + try { + val result = profileProxy.resolveUsers(token, ResolveUsersRequest(missing)) + val expiry = Instant.now().plus(ttl).toEpochMilli() + result.forEach { (uuid, name) -> + cache[uuid] = CacheEntry(name, expiry) + } + val notReturned = missing.filterNot { result.containsKey(it) } + notReturned.forEach { uuid -> cache[uuid] = CacheEntry(null, expiry) } + cached.putAll(result) + } catch (t: Throwable) { + } + } + + return cached + } +} diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/RecentTradesProxyRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/RecentTradesProxyRequest.kt new file mode 100644 index 000000000..b64ff0480 --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/RecentTradesProxyRequest.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.api.ports.proxy.data + +data class RecentTradesProxyRequest( + val baseAsset: String?, + val quoteAsset: String?, + val makerUuid: String?, + val takerUuid: String?, + val fromDate: Long?, + val toDate: Long?, + val excludeSelfTrade: Boolean = true, + val limit: Int, + val offset: Int, +) \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt index 43e1c49ec..e7e4b1093 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt @@ -3,6 +3,7 @@ package co.nilin.opex.api.ports.proxy.impl import co.nilin.opex.api.core.inout.* import co.nilin.opex.api.core.spi.MarketDataProxy import co.nilin.opex.api.ports.proxy.config.ProxyDispatchers +import co.nilin.opex.api.ports.proxy.data.RecentTradesProxyRequest import co.nilin.opex.common.utils.Interval import co.nilin.opex.common.utils.LoggerDelegate import kotlinx.coroutines.reactive.awaitFirstOrElse @@ -14,8 +15,10 @@ import org.springframework.http.HttpHeaders import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.body import org.springframework.web.reactive.function.client.bodyToFlux import org.springframework.web.reactive.function.client.bodyToMono +import reactor.core.publisher.Mono import java.util.* @Component @@ -257,4 +260,32 @@ class MarketDataProxyImpl(@Qualifier("generalWebClient") private val webClient: ?.value ?: 0 } } + + override suspend fun recentTrades(token: String, request: AdminTradesHistoryRequest): List { + logger.info("admin recent trades wrapper for symbol=${request.baseAsset}-${request.quoteAsset}") + return withContext(ProxyDispatchers.market) { + webClient.post() + .uri("$baseUrl/v1/admin/trades/history") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .body(Mono.just( + RecentTradesProxyRequest( + baseAsset = request.baseAsset, + quoteAsset=request.quoteAsset, + makerUuid = request.makerUuid, + takerUuid = request.takerUuid, + fromDate = request.fromDate, + toDate = request.toDate, + excludeSelfTrade = request.excludeSelfTrade, + limit = request.limit, + offset = request.offset, + ) + )) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + } } \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ProfileProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ProfileProxyImpl.kt index 68dd031c8..1cb139840 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ProfileProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ProfileProxyImpl.kt @@ -114,6 +114,22 @@ class ProfileProxyImpl(@Qualifier("generalWebClient") private val webClient: Web .awaitFirstOrElse { throw OpexError.BadRequest.exception("Failed to update profile approval request ${request.id}") } } + override suspend fun resolveUsers(token: String, request: ResolveUsersRequest): Map { + return try { + webClient.post() + .uri("$baseUrl/admin/users/resolve") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .body(Mono.just(request)) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono(object : org.springframework.core.ParameterizedTypeReference>() {}) + .awaitFirstOrElse { emptyMap() } + } catch (t: Throwable) { + emptyMap() + } + } + override suspend fun addAddressBook( token: String, request: AddAddressBookItemRequest diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/AdminController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/AdminController.kt index a0e5af6d4..7be0e4984 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/AdminController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/AdminController.kt @@ -1,6 +1,7 @@ package co.nilin.opex.market.app.controller import co.nilin.opex.market.app.data.RecentTradesRequest +import co.nilin.opex.market.app.data.AdminTradesHistoryRequest import co.nilin.opex.market.app.utils.asLocalDateTime import co.nilin.opex.market.core.inout.TradeData import co.nilin.opex.market.core.spi.MarketQueryHandler @@ -28,4 +29,22 @@ class AdminController(private val marketQueryHandler: MarketQueryHandler) { request.offset ) } + + @PostMapping("/trades/history") + suspend fun searchTradesAdmin( + @RequestBody request: AdminTradesHistoryRequest, + ): List { + return marketQueryHandler.recentTradesAdmin( + baseAsset = request.baseAsset, + quoteAsset = request.quoteAsset, + makerUuid = request.makerUuid, + takerUuid = request.takerUuid, + fromDate = request.fromDate?.asLocalDateTime(), + toDate = request.toDate?.asLocalDateTime(), + excludeSelfTrade = request.excludeSelfTrade, + ascendingByTime = request.ascendingByTime, + limit = request.limit, + offset = request.offset + ) + } } \ No newline at end of file diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminTradesHistoryRequest.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminTradesHistoryRequest.kt new file mode 100644 index 000000000..ecf655e43 --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminTradesHistoryRequest.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.market.app.data + +data class AdminTradesHistoryRequest( + val baseAsset: String?, + val quoteAsset: String?, + val makerUuid: String?, + val takerUuid: String?, + val fromDate: Long?, + val toDate: Long?, + val excludeSelfTrade: Boolean = true, + val ascendingByTime: Boolean = false, + val limit: Int, + val offset: Int, +) diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeData.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeData.kt index be190bfd4..c170d9f6c 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeData.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeData.kt @@ -7,6 +7,8 @@ import java.time.LocalDateTime data class TradeData( val tradeId: Long, val symbol: String, + val baseAsset: String?, + val quoteAsset: String?, val matchedPrice: BigDecimal, val matchedQuantity: BigDecimal, val takerPrice: BigDecimal, @@ -14,4 +16,10 @@ data class TradeData( val tradeDate: LocalDateTime, val makerUuid: String, val takerUuid: String, + val makerOuid: String?, + val takerOuid: String?, + val makerCommission: BigDecimal?, + val takerCommission: BigDecimal?, + val makerCommissionAsset: String?, + val takerCommissionAsset: String?, ) diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt index 6e31d84fc..364d93fcf 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt @@ -29,6 +29,19 @@ interface MarketQueryHandler { offset: Int, ): List + suspend fun recentTradesAdmin( + baseAsset: String?, + quoteAsset: String?, + makerUuid: String?, + takerUuid: String?, + fromDate: LocalDateTime?, + toDate: LocalDateTime?, + excludeSelfTrade: Boolean, + ascendingByTime: Boolean, + limit: Int, + offset: Int, + ): List + suspend fun lastPrice(symbol: String?): List suspend fun getBestPriceForSymbols(symbols: List): List diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt index 5b97f7dff..2949f8cb5 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt @@ -564,6 +564,60 @@ interface TradeRepository : ReactiveCrudRepository { offset: Int, ): Flow + @Query( + """ + select * from trades where + (:baseAsset is null or base_asset = :baseAsset) + and (:quoteAsset is null or quote_asset = :quoteAsset) + and (:makerUuid is null or maker_uuid = :makerUuid) + and (:takerUuid is null or taker_uuid = :takerUuid) + and (:fromDate is null or trade_date >= :fromDate) + and (:toDate is null or trade_date <= :toDate) + and (:excludeSelfTrade is false or maker_uuid != taker_uuid) + order by trade_date ASC + limit :limit + offset :offset + """ + ) + suspend fun findByCriteriaByBaseQuoteAsc( + baseAsset: String?, + quoteAsset: String?, + makerUuid: String?, + takerUuid: String?, + fromDate: LocalDateTime?, + toDate: LocalDateTime?, + excludeSelfTrade: Boolean, + limit: Int, + offset: Int, + ): Flow + + @Query( + """ + select * from trades where + (:baseAsset is null or base_asset = :baseAsset) + and (:quoteAsset is null or quote_asset = :quoteAsset) + and (:makerUuid is null or maker_uuid = :makerUuid) + and (:takerUuid is null or taker_uuid = :takerUuid) + and (:fromDate is null or trade_date >= :fromDate) + and (:toDate is null or trade_date <= :toDate) + and (:excludeSelfTrade is false or maker_uuid != taker_uuid) + order by trade_date DESC + limit :limit + offset :offset + """ + ) + suspend fun findByCriteriaByBaseQuoteDesc( + baseAsset: String?, + quoteAsset: String?, + makerUuid: String?, + takerUuid: String?, + fromDate: LocalDateTime?, + toDate: LocalDateTime?, + excludeSelfTrade: Boolean, + limit: Int, + offset: Int, + ): Flow + @Query( """ diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt index 7947f6ae4..028feb32f 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt @@ -131,7 +131,10 @@ class MarketQueryHandlerImpl( .map { TradeData( tradeId = it.tradeId, + dbTradeId = it.id ?: -1, symbol = it.symbol, + baseAsset = it.baseAsset, + quoteAsset = it.quoteAsset, matchedPrice = it.matchedPrice, matchedQuantity = it.matchedQuantity, takerPrice = it.takerPrice, @@ -139,12 +142,80 @@ class MarketQueryHandlerImpl( tradeDate = it.tradeDate, makerUuid = it.makerUuid, takerUuid = it.takerUuid, + makerOuid = it.makerOuid, + takerOuid = it.takerOuid, + makerCommission = it.makerCommission, + takerCommission = it.takerCommission, + makerCommissionAsset = it.makerCommissionAsset, + takerCommissionAsset = it.takerCommissionAsset, ) } .toList() } + override suspend fun recentTradesAdmin( + baseAsset: String?, + quoteAsset: String?, + makerUuid: String?, + takerUuid: String?, + fromDate: LocalDateTime?, + toDate: LocalDateTime?, + excludeSelfTrade: Boolean, + ascendingByTime: Boolean, + limit: Int, + offset: Int, + ): List { + val flow = if (ascendingByTime) { + tradeRepository.findByCriteriaByBaseQuoteAsc( + baseAsset, + quoteAsset, + makerUuid, + takerUuid, + fromDate, + toDate, + excludeSelfTrade, + limit, + offset + ) + } else { + tradeRepository.findByCriteriaByBaseQuoteDesc( + baseAsset, + quoteAsset, + makerUuid, + takerUuid, + fromDate, + toDate, + excludeSelfTrade, + limit, + offset + ) + } + return flow + .map { + TradeData( + tradeId = it.tradeId, + symbol = it.symbol, + baseAsset = it.baseAsset, + quoteAsset = it.quoteAsset, + matchedPrice = it.matchedPrice, + matchedQuantity = it.matchedQuantity, + takerPrice = it.takerPrice, + makerPrice = it.makerPrice, + tradeDate = it.tradeDate, + makerUuid = it.makerUuid, + takerUuid = it.takerUuid, + makerOuid = it.makerOuid, + takerOuid = it.takerOuid, + makerCommission = it.makerCommission, + takerCommission = it.takerCommission, + makerCommissionAsset = it.makerCommissionAsset, + takerCommissionAsset = it.takerCommissionAsset, + ) + } + .toList() + } + override suspend fun lastPrice(symbol: String?): List { val list = redisCacheHelper.getOrElse("lastPrice", 1.minutes()) { if (symbol.isNullOrEmpty()) From 17d662949b3c14da7e6e0ceeca09aefdbbef252c Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Tue, 9 Jun 2026 21:19:40 +0330 Subject: [PATCH 04/11] Clean the user history services --- .../core/inout/AdminOrdersHistoryRequest.kt | 15 ++ .../core/inout/AdminOrdersSearchRequest.kt | 17 -- .../core/inout/AdminTradesHistoryRequest.kt | 14 +- .../opex/api/core/inout/OrderAdminItem.kt | 24 +- .../co/nilin/opex/api/core/inout/OrderData.kt | 2 +- .../opex/api/core/inout/TradeAdminItem.kt | 2 + .../opex/api/core/spi/MarketDataProxy.kt | 2 + .../opex/api/core/spi/MarketUserDataProxy.kt | 16 +- .../ports/opex/controller/OrderController.kt | 11 +- .../controller/TransactionAdminController.kt | 122 ++++++---- .../opex/controller/UserHistoryController.kt | 8 +- .../ports/opex/service/OwnerNameResolver.kt | 7 +- .../ports/opex/util/ConvertorExtenstions.kt | 2 +- .../proxy/data/RecentTradesProxyRequest.kt | 13 - .../ports/proxy/impl/MarketDataProxyImpl.kt | 31 +-- .../proxy/impl/MarketUserDataProxyImpl.kt | 40 +-- .../api/ports/proxy/impl/ProfileProxyImpl.kt | 2 +- .../opex/auth/service/RegisterService.kt | 2 +- .../common/service/TranslationCacheService.kt | 6 +- .../opex/market/app/config/SecurityConfig.kt | 1 + .../market/app/controller/AdminController.kt | 28 ++- .../market/app/controller/MarketController.kt | 8 +- .../app/controller/MarketStatsController.kt | 4 +- .../app/controller/UserDataController.kt | 70 ++++-- .../app/data/AdminOrdersHistoryRequest.kt | 17 ++ .../app/data/AdminTradesHistoryRequest.kt | 11 +- .../nilin/opex/market/core/inout/OrderData.kt | 20 +- .../co/nilin/opex/market/core/inout/Trade.kt | 2 +- .../core/inout/TradeUserContextProjection.kt | 22 ++ .../market/core/spi/MarketQueryHandler.kt | 40 ++- .../opex/market/core/spi/UserQueryHandler.kt | 12 +- .../ports/postgres/dao/OrderRepository.kt | 87 ++++++- .../ports/postgres/dao/TradeRepository.kt | 227 ++++++++++++++---- .../postgres/impl/MarketQueryHandlerImpl.kt | 140 +++++++---- .../postgres/impl/UserQueryHandlerImpl.kt | 152 ++++++------ .../postgres/impl/MarketQueryHandlerTest.kt | 9 +- .../postgres/impl/UserQueryHandlerTest.kt | 9 +- .../ports/postgres/impl/sample/Samples.kt | 18 ++ .../otp/app/service/message/EmailSender.kt | 64 +++-- .../src/main/resources/application.yml | 2 +- .../otp/app/conttroller/OTPControllerIT.kt | 4 +- .../app/controller/ProfileAdminController.kt | 11 + .../profile/app/service/ProfileManagement.kt | 6 +- .../opex/profile/core/spi/ProfilePersister.kt | 1 + .../postgres/ProfileFullNameProjection.kt | 4 + .../ports/postgres/dao/ProfileRepository.kt | 4 + .../postgres/imp/ProfileManagementImp.kt | 9 + 47 files changed, 904 insertions(+), 414 deletions(-) create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersHistoryRequest.kt delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersSearchRequest.kt delete mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/RecentTradesProxyRequest.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminOrdersHistoryRequest.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt create mode 100644 profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/ProfileFullNameProjection.kt diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersHistoryRequest.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersHistoryRequest.kt new file mode 100644 index 000000000..b8a68298e --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersHistoryRequest.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.api.core.inout + +data class AdminOrdersHistoryRequest( + val uuid: String?, + val symbol: String?, + val ouid: String?, + val startTime: Long?, + val endTime: Long?, + val orderType: MatchingOrderType?, + val direction: OrderDirection?, + val limit: Int? = 10, + val offset: Int? = 0, + val ascendingByTime: Boolean? = false, + val includeNames: Boolean? = false +) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersSearchRequest.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersSearchRequest.kt deleted file mode 100644 index 675799ebc..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminOrdersSearchRequest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package co.nilin.opex.api.core.inout - -import co.nilin.opex.api.core.inout.MatchingOrderType -import co.nilin.opex.api.core.inout.OrderDirection - -// Admin orders search request (API-only wrapper) -data class AdminOrdersSearchRequest( - val creatorUuid: String, - val symbol: String?, - val startTime: Long?, - val endTime: Long?, - val orderType: MatchingOrderType?, - val direction: OrderDirection?, - val limit: Int? = 100, - val offset: Int? = 0, - val includeNames: Boolean = false, -) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminTradesHistoryRequest.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminTradesHistoryRequest.kt index 70ce71644..1065682f7 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminTradesHistoryRequest.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AdminTradesHistoryRequest.kt @@ -1,14 +1,20 @@ package co.nilin.opex.api.core.inout data class AdminTradesHistoryRequest( + val symbol: String?, val baseAsset: String?, val quoteAsset: String?, + val ouid: String?, + val makerOuid: String?, + val takerOuid: String?, + val uuid: String?, val makerUuid: String?, val takerUuid: String?, val fromDate: Long?, val toDate: Long?, - val excludeSelfTrade: Boolean = true, - val limit: Int, - val offset: Int, - val includeNames: Boolean = false, + val ascendingByTime: Boolean? = false, + val excludeSelfTrade: Boolean? = true, + val limit: Int?=10, + val offset: Int?=0, + val includeNames: Boolean? = false, ) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderAdminItem.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderAdminItem.kt index b72b693ff..aec8f5592 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderAdminItem.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderAdminItem.kt @@ -3,24 +3,22 @@ package co.nilin.opex.api.core.inout import java.math.BigDecimal import java.time.LocalDateTime -// Admin-facing order item returned by API wrapper around Market order history -// Adds creatorUuid and optional creatorOwnerName for human readability. + data class OrderAdminItem( val symbol: String, - val orderId: Long, - val orderType: MatchingOrderType, + val ouid: String, + val orderType: MatchingOrderType?, val side: OrderDirection, val price: BigDecimal, val quantity: BigDecimal, - val quoteQuantity: BigDecimal, - val executedQuantity: BigDecimal, - val takerFee: BigDecimal, - val makerFee: BigDecimal, - val status: Int, - val appearance: Int, + val quoteQuantity: BigDecimal?, + val executedQuantity: BigDecimal?, + val takerFee: BigDecimal?, + val makerFee: BigDecimal?, + val status: OrderStatus?, + val appearance: Int?, val createDate: LocalDateTime, val updateDate: LocalDateTime, - // Enrichment fields (API only) - val creatorUuid: String, - val creatorOwnerName: String? = null, + val uuid: String?, + val ownerName: String? = null, ) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderData.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderData.kt index c451aace9..d10688d1b 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderData.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderData.kt @@ -15,7 +15,7 @@ data class OrderData( val executedQuantity: BigDecimal, val takerFee: BigDecimal, val makerFee: BigDecimal, - val status: Int, + val status: OrderStatus, val appearance: Int, val createDate: LocalDateTime, val updateDate: LocalDateTime, diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminItem.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminItem.kt index a5f748f2b..94638023c 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminItem.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeAdminItem.kt @@ -26,3 +26,5 @@ data class TradeAdminItem( val makerOwnerName: String? = null, val takerOwnerName: String? = null, ) + + diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt index 41663ae11..f43b276d6 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt @@ -19,6 +19,8 @@ interface MarketDataProxy { suspend fun recentTrades(token: String, request: AdminTradesHistoryRequest): List + suspend fun recentOrders(token: String, request: AdminOrdersHistoryRequest): List + suspend fun lastPrice(symbol: String?): List suspend fun getBestPriceForSymbols(symbols: List): List diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketUserDataProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketUserDataProxy.kt index a0d94fede..c5780e2b0 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketUserDataProxy.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketUserDataProxy.kt @@ -10,12 +10,12 @@ import java.util.* interface MarketUserDataProxy { - suspend fun queryOrder(principal: Principal, symbol: String, orderId: Long?, origClientOrderId: String?): Order? + suspend fun queryOrder(token: String, symbol: String, orderId: Long?, origClientOrderId: String?): Order? - suspend fun openOrders(principal: Principal, symbol: String?, limit: Int?): List + suspend fun openOrders(token: String, symbol: String?, limit: Int?): List suspend fun allOrders( - principal: Principal, + token: String, symbol: String?, startTime: Date?, endTime: Date?, @@ -23,7 +23,7 @@ interface MarketUserDataProxy { ): List suspend fun allTrades( - principal: Principal, + token: String, symbol: String?, fromTrade: Long?, startTime: Date?, @@ -32,7 +32,7 @@ interface MarketUserDataProxy { ): List suspend fun getOrderHistory( - uuid: String, + token: String, symbol: String?, startTime: Long?, endTime: Long?, @@ -43,7 +43,7 @@ interface MarketUserDataProxy { ): List suspend fun getOrderHistoryCount( - uuid: String, + token: String, symbol: String?, startTime: Long?, endTime: Long?, @@ -52,7 +52,7 @@ interface MarketUserDataProxy { ): Long suspend fun getTradeHistory( - uuid: String, + token: String, symbol: String?, startTime: Long?, endTime: Long?, @@ -62,7 +62,7 @@ interface MarketUserDataProxy { ): List suspend fun getTradeHistoryCount( - uuid: String, + token: String, symbol: String?, startTime: Long?, endTime: Long?, diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/OrderController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/OrderController.kt index fc80f91ed..c5a28eeb3 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/OrderController.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/OrderController.kt @@ -259,7 +259,7 @@ Response body: @CurrentSecurityContext securityContext: SecurityContext ): CancelOrderResponse { if (orderId == null && origClientOrderId == null) throw OpexError.BadRequest.exception("'orderId' or 'origClientOrderId' must be sent") - val order = queryHandler.queryOrder(principal, symbol, orderId, origClientOrderId) + val order = queryHandler.queryOrder(securityContext.jwtAuthentication().tokenValue(), symbol, orderId, origClientOrderId) ?: throw OpexError.OrderNotFound.exception() val response = CancelOrderResponse( symbol, @@ -323,8 +323,9 @@ Response body: ] ) suspend fun queryOrder( + @Parameter(hidden = true) - principal: Principal, + @CurrentSecurityContext securityContext: SecurityContext, @Parameter( name = "symbol", @@ -353,7 +354,7 @@ Response body: ) @RequestParam(required = false) origClientOrderId: String?, ): QueryOrderResponse { - return queryHandler.queryOrder(principal, symbol, orderId, origClientOrderId) + return queryHandler.queryOrder(securityContext.jwtAuthentication().tokenValue(), symbol, orderId, origClientOrderId) ?.asQueryOrderResponse() ?.apply { this.symbol = symbol } ?: throw OpexError.OrderNotFound.exception() @@ -394,7 +395,7 @@ Response body: ) suspend fun fetchOpenOrders( @Parameter(hidden = true) - principal: Principal, + @CurrentSecurityContext securityContext: SecurityContext, @Parameter( name = "symbol", @@ -414,7 +415,7 @@ Response body: ) @RequestParam(required = false) limit: Int? ): List { - return queryHandler.openOrders(principal, symbol, limit).map { + return queryHandler.openOrders(securityContext.jwtAuthentication().tokenValue(), symbol, limit).map { it.asQueryOrderResponse().apply { symbol?.let { s -> this.symbol = s } } } } diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/TransactionAdminController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/TransactionAdminController.kt index a95cfa3d0..d7dc44c66 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/TransactionAdminController.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/TransactionAdminController.kt @@ -1,12 +1,12 @@ package co.nilin.opex.api.ports.opex.controller import co.nilin.opex.api.core.inout.* -import co.nilin.opex.api.core.spi.WalletProxy import co.nilin.opex.api.core.spi.MarketDataProxy -import co.nilin.opex.api.core.spi.MarketUserDataProxy +import co.nilin.opex.api.core.spi.WalletProxy import co.nilin.opex.api.ports.opex.service.OwnerNameResolver import co.nilin.opex.api.ports.opex.util.jwtAuthentication import co.nilin.opex.api.ports.opex.util.tokenValue +import co.nilin.opex.common.utils.LoggerDelegate import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.media.ArraySchema @@ -29,9 +29,9 @@ import org.springframework.web.bind.annotation.RestController class TransactionAdminController( private val walletProxy: WalletProxy, private val marketDataProxy: MarketDataProxy, - private val marketUserDataProxy: MarketUserDataProxy, private val ownerNameResolver: OwnerNameResolver, ) { + private val logger by LoggerDelegate() @PostMapping("/summary") suspend fun getUserTransactionHistory( @@ -201,61 +201,99 @@ Security: Bearer admin-token required. Required authority: ROLE_monitoring or RO return walletProxy.getTradeHistoryForAdmin(securityContext.jwtAuthentication().tokenValue(), request) } - @PostMapping("/market-trades/search") + @PostMapping("/v2/trades") + @Operation( + summary = "Get trades transactions", + description = """POST /opex/v1/admin/transactions/v2/trades. +Security: Bearer admin-token required. Required authority: ROLE_monitoring or ROLE_admin. +""", + security = [SecurityRequirement(name = "bearerAuth")], + responses = [ + ApiResponse( + responseCode = "200", + description = "Successful response.", + content = [Content( + mediaType = "application/json", + array = ArraySchema(schema = Schema(implementation = TradeAdminItem::class)) + )] + ), + ApiResponse( + responseCode = "401", + description = "Unauthorized. Bearer token is missing, invalid, or expired. No response body.", + content = [Content()] + ), + ApiResponse( + responseCode = "403", + description = "Forbidden. Required authority is missing: ROLE_monitoring or ROLE_admin. No response body.", + content = [Content()] + ) + ] + ) suspend fun searchMarketTrades( + @Parameter(hidden = true) @CurrentSecurityContext securityContext: SecurityContext, @RequestBody request: AdminTradesHistoryRequest, ): List { val token = securityContext.jwtAuthentication().tokenValue() val items = marketDataProxy.recentTrades(token, request) - if (!request.includeNames) return items + if (request.includeNames == false) return items val uuids = items.flatMap { listOfNotNull(it.makerUuid, it.takerUuid) }.toSet() val nameMap = ownerNameResolver.resolve(token, uuids) - return items.map { it.copy( - makerOwnerName = nameMap[it.makerUuid], - takerOwnerName = nameMap[it.takerUuid] - ) } + return items.map { + it.copy( + makerOwnerName = nameMap[it.makerUuid], + takerOwnerName = nameMap[it.takerUuid] + ) + } } - @PostMapping("/market-orders/search") + @PostMapping("/v2/orders") + @Operation( + summary = "Get orders history", + description = """POST /opex/v1/admin/transactions/v2/orders. +Security: Bearer admin-token required. Required authority: ROLE_monitoring or ROLE_admin. +Allowed values: +- OrderType: LIMIT_ORDER, MARKET_ORDER +- OrderDirection: ASK, BID +""", + security = [SecurityRequirement(name = "bearerAuth")], + responses = [ + ApiResponse( + responseCode = "200", + description = "Successful response.", + content = [Content( + mediaType = "application/json", + array = ArraySchema(schema = Schema(implementation = OrderAdminItem::class)) + )] + ), + ApiResponse( + responseCode = "401", + description = "Unauthorized. Bearer token is missing, invalid, or expired. No response body.", + content = [Content()] + ), + ApiResponse( + responseCode = "403", + description = "Forbidden. Required authority is missing: ROLE_monitoring or ROLE_admin. No response body.", + content = [Content()] + ) + ] + ) suspend fun searchMarketOrders( + @Parameter(hidden = true) @CurrentSecurityContext securityContext: SecurityContext, - @RequestBody request: AdminOrdersSearchRequest, + @RequestBody request: AdminOrdersHistoryRequest, ): List { val token = securityContext.jwtAuthentication().tokenValue() - val orders = marketUserDataProxy.getOrderHistory( - uuid = request.creatorUuid, - symbol = request.symbol, - startTime = request.startTime, - endTime = request.endTime, - orderType = request.orderType, - direction = request.direction, - limit = request.limit, - offset = request.offset, + val items = marketDataProxy.recentOrders( + token, request ) - val items = orders.map { od -> - OrderAdminItem( - symbol = od.symbol, - orderId = od.orderId, - orderType = od.orderType, - side = od.side, - price = od.price, - quantity = od.quantity, - quoteQuantity = od.quoteQuantity, - executedQuantity = od.executedQuantity, - takerFee = od.takerFee, - makerFee = od.makerFee, - status = od.status, - appearance = od.appearance, - createDate = od.createDate, - updateDate = od.updateDate, - creatorUuid = request.creatorUuid, - creatorOwnerName = null, + if (request.includeNames == false) return items + val uuids = items.flatMap { listOfNotNull(it.uuid) }.toSet() + val nameMap = ownerNameResolver.resolve(token, uuids) + return items.map { + it.copy( + ownerName = nameMap[it.uuid], ) } - if (!request.includeNames) return items - val nameMap = ownerNameResolver.resolve(token, setOf(request.creatorUuid)) - val name = nameMap[request.creatorUuid] - return items.map { it.copy(creatorOwnerName = name) } } } diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/UserHistoryController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/UserHistoryController.kt index 9b59872c5..07fcc2d42 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/UserHistoryController.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/UserHistoryController.kt @@ -94,7 +94,7 @@ Allowed values: @CurrentSecurityContext securityContext: SecurityContext, ): List { return marketUserDataProxy.getOrderHistory( - securityContext.authentication.name, + securityContext.jwtAuthentication().tokenValue(), symbol, startTime, endTime, @@ -159,7 +159,7 @@ Allowed values: @CurrentSecurityContext securityContext: SecurityContext, ): Long { return marketUserDataProxy.getOrderHistoryCount( - securityContext.authentication.name, + securityContext.jwtAuthentication().tokenValue(), symbol, startTime, endTime, @@ -225,7 +225,7 @@ Allowed values: @CurrentSecurityContext securityContext: SecurityContext, ): List { return marketUserDataProxy.getTradeHistory( - securityContext.authentication.name, + securityContext.jwtAuthentication().tokenValue(), symbol, startTime, endTime, @@ -282,7 +282,7 @@ Allowed values: @CurrentSecurityContext securityContext: SecurityContext, ): Long { return marketUserDataProxy.getTradeHistoryCount( - securityContext.authentication.name, + securityContext.jwtAuthentication().tokenValue(), symbol, startTime, endTime, diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/service/OwnerNameResolver.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/service/OwnerNameResolver.kt index 7b0bb6cd7..7b0894c5a 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/service/OwnerNameResolver.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/service/OwnerNameResolver.kt @@ -2,6 +2,7 @@ package co.nilin.opex.api.ports.opex.service import co.nilin.opex.api.core.inout.ResolveUsersRequest import co.nilin.opex.api.core.spi.ProfileProxy +import co.nilin.opex.common.utils.LoggerDelegate import org.springframework.stereotype.Component import java.time.Duration import java.time.Instant @@ -13,6 +14,8 @@ class OwnerNameResolver( ) { private data class CacheEntry(val name: String?, val expiresAt: Long) + private val logger by LoggerDelegate() + private val cache = ConcurrentHashMap() private val ttl: Duration = Duration.ofDays(1) @@ -36,13 +39,15 @@ class OwnerNameResolver( try { val result = profileProxy.resolveUsers(token, ResolveUsersRequest(missing)) val expiry = Instant.now().plus(ttl).toEpochMilli() - result.forEach { (uuid, name) -> + result.filter { (uuid, name) -> name != null }.forEach { (uuid, name) -> cache[uuid] = CacheEntry(name, expiry) } val notReturned = missing.filterNot { result.containsKey(it) } notReturned.forEach { uuid -> cache[uuid] = CacheEntry(null, expiry) } + cached.putAll(result) } catch (t: Throwable) { + logger.debug("Error in fetching users data $t") } } diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/util/ConvertorExtenstions.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/util/ConvertorExtenstions.kt index c24b05f60..d815a9f99 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/util/ConvertorExtenstions.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/util/ConvertorExtenstions.kt @@ -19,7 +19,7 @@ fun OrderData.toResponse(): OrderDataResponse { executedQuantity = this.executedQuantity, takerFee = this.takerFee, makerFee = this.makerFee, - status = OrderStatus.fromCode(this.status) ?: OrderStatus.REJECTED, + status = this.status, createDate = this.createDate, updateDate = this.updateDate, ) diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/RecentTradesProxyRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/RecentTradesProxyRequest.kt deleted file mode 100644 index b64ff0480..000000000 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/RecentTradesProxyRequest.kt +++ /dev/null @@ -1,13 +0,0 @@ -package co.nilin.opex.api.ports.proxy.data - -data class RecentTradesProxyRequest( - val baseAsset: String?, - val quoteAsset: String?, - val makerUuid: String?, - val takerUuid: String?, - val fromDate: Long?, - val toDate: Long?, - val excludeSelfTrade: Boolean = true, - val limit: Int, - val offset: Int, -) \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt index e7e4b1093..41782c441 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt @@ -3,7 +3,6 @@ package co.nilin.opex.api.ports.proxy.impl import co.nilin.opex.api.core.inout.* import co.nilin.opex.api.core.spi.MarketDataProxy import co.nilin.opex.api.ports.proxy.config.ProxyDispatchers -import co.nilin.opex.api.ports.proxy.data.RecentTradesProxyRequest import co.nilin.opex.common.utils.Interval import co.nilin.opex.common.utils.LoggerDelegate import kotlinx.coroutines.reactive.awaitFirstOrElse @@ -268,19 +267,7 @@ class MarketDataProxyImpl(@Qualifier("generalWebClient") private val webClient: .uri("$baseUrl/v1/admin/trades/history") .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.AUTHORIZATION, "Bearer $token") - .body(Mono.just( - RecentTradesProxyRequest( - baseAsset = request.baseAsset, - quoteAsset=request.quoteAsset, - makerUuid = request.makerUuid, - takerUuid = request.takerUuid, - fromDate = request.fromDate, - toDate = request.toDate, - excludeSelfTrade = request.excludeSelfTrade, - limit = request.limit, - offset = request.offset, - ) - )) + .body(Mono.just(request)) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) .bodyToFlux() @@ -288,4 +275,20 @@ class MarketDataProxyImpl(@Qualifier("generalWebClient") private val webClient: .awaitFirstOrElse { emptyList() } } } + + override suspend fun recentOrders(token: String, request: AdminOrdersHistoryRequest): List { + logger.info("admin recent orders wrapper for symbol=${request.symbol}") + return withContext(ProxyDispatchers.market) { + webClient.post() + .uri("$baseUrl/v1/admin/orders/history") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .body(Mono.just(request)) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + } } \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketUserDataProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketUserDataProxyImpl.kt index 5b383ad8a..b40cfdc03 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketUserDataProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketUserDataProxyImpl.kt @@ -44,16 +44,17 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie private val mgLimiter = Semaphore(permits = 16, acquiredPermits = 0) override suspend fun queryOrder( - principal: Principal, + token: String, symbol: String, orderId: Long?, origClientOrderId: String?, ): Order? { return withContext(ProxyDispatchers.market) { webClient.post() - .uri("$baseUrl/v1/user/${principal.name}/order/query") + .uri("$baseUrl/v1/user/order/query") .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token" ) .body(Mono.just(QueryOrderRequest(symbol, orderId, origClientOrderId))) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) @@ -62,16 +63,17 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie } } - override suspend fun openOrders(principal: Principal, symbol: String?, limit: Int?): List { + override suspend fun openOrders(token: String, symbol: String?, limit: Int?): List { return withContext(ProxyDispatchers.market) { mgLimiter.withPermit { retryOnce { webClient.get() - .uri("$baseUrl/v1/user/${principal.name}/orders/$symbol/open") { + .uri("$baseUrl/v1/user/orders/$symbol/open") { it.queryParam("limit", limit ?: 100) it.build() }.accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token" ) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) .bodyToFlux() @@ -83,7 +85,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie } override suspend fun allOrders( - principal: Principal, + token: String, symbol: String?, startTime: Date?, endTime: Date?, @@ -91,9 +93,10 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie ): List { return withContext(ProxyDispatchers.market) { webClient.post() - .uri("$baseUrl/v1/user/${principal.name}/orders") + .uri("$baseUrl/v1/user/orders") .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token" ) .body(Mono.just(AllOrderRequest(symbol, startTime, endTime, limit ?: 500))) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) @@ -104,7 +107,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie } override suspend fun allTrades( - principal: Principal, + token: String, symbol: String?, fromTrade: Long?, startTime: Date?, @@ -113,9 +116,10 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie ): List { return withContext(ProxyDispatchers.market) { webClient.post() - .uri("$baseUrl/v1/user/${principal.name}/trades") + .uri("$baseUrl/v1/user/trades") .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token" ) .body(Mono.just(TradeRequest(symbol, fromTrade, startTime, endTime, limit ?: 500))) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) @@ -126,7 +130,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie } override suspend fun getOrderHistory( - uuid: String, + token: String, symbol: String?, startTime: Long?, endTime: Long?, @@ -137,7 +141,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie ): List { return withContext(ProxyDispatchers.market) { webClient.get() - .uri("$baseUrl/v1/user/order/history/$uuid") { + .uri("$baseUrl/v1/user/order/history") { it.queryParam("symbol", symbol) it.queryParam("startTime", startTime) it.queryParam("endTime", endTime) @@ -148,6 +152,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie it.build() }.accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token" ) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) .bodyToFlux() @@ -157,7 +162,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie } override suspend fun getOrderHistoryCount( - uuid: String, + token: String, symbol: String?, startTime: Long?, endTime: Long?, @@ -166,7 +171,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie ): Long { return withContext(ProxyDispatchers.market) { webClient.get() - .uri("$baseUrl/v1/user/order/history/count/$uuid") { + .uri("$baseUrl/v1/user/order/history/count") { it.queryParam("symbol", symbol) it.queryParam("startTime", startTime) it.queryParam("endTime", endTime) @@ -175,6 +180,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie it.build() }.accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token" ) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) .bodyToMono() @@ -183,7 +189,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie } override suspend fun getTradeHistory( - uuid: String, + token: String, symbol: String?, startTime: Long?, endTime: Long?, @@ -193,7 +199,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie ): List { return withContext(ProxyDispatchers.market) { webClient.get() - .uri("$baseUrl/v1/user/trade/history/$uuid") { + .uri("$baseUrl/v1/user/trade/history") { it.queryParam("symbol", symbol) it.queryParam("startTime", startTime) it.queryParam("endTime", endTime) @@ -203,6 +209,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie it.build() }.accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token" ) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) .bodyToFlux() @@ -212,7 +219,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie } override suspend fun getTradeHistoryCount( - uuid: String, + token: String, symbol: String?, startTime: Long?, endTime: Long?, @@ -220,7 +227,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie ): Long { return withContext(ProxyDispatchers.market) { webClient.get() - .uri("$baseUrl/v1/user/trade/history/count/$uuid") { + .uri("$baseUrl/v1/user/trade/history/count") { it.queryParam("symbol", symbol) it.queryParam("startTime", startTime) it.queryParam("endTime", endTime) @@ -228,6 +235,7 @@ class MarketUserDataProxyImpl(@Qualifier("generalWebClient") private val webClie it.build() }.accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token" ) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) .bodyToMono() diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ProfileProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ProfileProxyImpl.kt index 62caec71e..7f9bff866 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ProfileProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ProfileProxyImpl.kt @@ -117,7 +117,7 @@ class ProfileProxyImpl(@Qualifier("generalWebClient") private val webClient: Web override suspend fun resolveUsers(token: String, request: ResolveUsersRequest): Map { return try { webClient.post() - .uri("$baseUrl/admin/users/resolve") + .uri("$baseUrl/admin/profile/users/resolve") .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.AUTHORIZATION, "Bearer $token") .body(Mono.just(request)) diff --git a/auth-gateway/auth-gateway-app/src/main/kotlin/co/nilin/opex/auth/service/RegisterService.kt b/auth-gateway/auth-gateway-app/src/main/kotlin/co/nilin/opex/auth/service/RegisterService.kt index 34479764b..4f95d9e40 100644 --- a/auth-gateway/auth-gateway-app/src/main/kotlin/co/nilin/opex/auth/service/RegisterService.kt +++ b/auth-gateway/auth-gateway-app/src/main/kotlin/co/nilin/opex/auth/service/RegisterService.kt @@ -36,7 +36,7 @@ class RegisterService( val otpType = username.type.otpType val otpReceiver = OTPReceiver(request.username, otpType) val res = otpProxy.requestOTP(request.username, listOf(otpReceiver)) - +// todo we have to check for duplication usernames after verifying the register otp if (!userStatus) keycloakProxy.createUser( username, diff --git a/common/src/main/kotlin/co/nilin/opex/common/service/TranslationCacheService.kt b/common/src/main/kotlin/co/nilin/opex/common/service/TranslationCacheService.kt index 9d2ba6618..b661a1d28 100644 --- a/common/src/main/kotlin/co/nilin/opex/common/service/TranslationCacheService.kt +++ b/common/src/main/kotlin/co/nilin/opex/common/service/TranslationCacheService.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.common.translation +package co.nilin.opex.common.service import co.nilin.opex.common.data.MessageTranslation import co.nilin.opex.common.data.UserLanguage @@ -30,8 +30,8 @@ class TranslationCacheService( logger.info("Going to get messages which are updated after: {}", lastUpdate) while (isActive) { try { - if (configClient != null) { - val newMessages = configClient.getMessagesUpdatedAfter(lastUpdate) + if (customMessageClient != null) { + val newMessages = customMessageClient.getMessagesUpdatedAfter(lastUpdate) newMessages?.forEach { msg -> cache[Pair(msg.key, msg.language)] = MessageTranslation(msg.key, msg.message, msg.language) diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt index bcb8b2a2f..0f2132e1a 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt @@ -28,6 +28,7 @@ class SecurityConfig(private val webClient: WebClient) { .authorizeExchange() { it.pathMatchers(HttpMethod.GET, "/v1/admin/**").hasAnyAuthority("ROLE_monitoring", "ROLE_admin") .pathMatchers("/actuator/**").permitAll() + .pathMatchers("/v1/user/**").authenticated() .anyExchange().permitAll() } .oauth2ResourceServer { it.jwt { jwt -> jwt.jwtAuthenticationConverter(ReactiveCustomJwtConverter()) } } diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/AdminController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/AdminController.kt index 7be0e4984..98783f1e2 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/AdminController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/AdminController.kt @@ -1,8 +1,10 @@ package co.nilin.opex.market.app.controller +import co.nilin.opex.market.app.data.AdminOrdersHistoryRequest import co.nilin.opex.market.app.data.RecentTradesRequest import co.nilin.opex.market.app.data.AdminTradesHistoryRequest import co.nilin.opex.market.app.utils.asLocalDateTime +import co.nilin.opex.market.core.inout.OrderData import co.nilin.opex.market.core.inout.TradeData import co.nilin.opex.market.core.spi.MarketQueryHandler import org.springframework.web.bind.annotation.PostMapping @@ -17,7 +19,7 @@ class AdminController(private val marketQueryHandler: MarketQueryHandler) { @PostMapping("/recent-trades") suspend fun getRecentTrades( @RequestBody request: RecentTradesRequest, - ): List { + ): List? { return marketQueryHandler.recentTrades( request.symbol, request.makerUuid, @@ -33,12 +35,17 @@ class AdminController(private val marketQueryHandler: MarketQueryHandler) { @PostMapping("/trades/history") suspend fun searchTradesAdmin( @RequestBody request: AdminTradesHistoryRequest, - ): List { + ): List? { return marketQueryHandler.recentTradesAdmin( + symbol=request.symbol, baseAsset = request.baseAsset, quoteAsset = request.quoteAsset, + uuid = request.uuid, makerUuid = request.makerUuid, takerUuid = request.takerUuid, + ouid = request.ouid, + makerOuid = request.makerOuid, + takerOuid = request.takerOuid, fromDate = request.fromDate?.asLocalDateTime(), toDate = request.toDate?.asLocalDateTime(), excludeSelfTrade = request.excludeSelfTrade, @@ -47,4 +54,21 @@ class AdminController(private val marketQueryHandler: MarketQueryHandler) { offset = request.offset ) } + @PostMapping("/orders/history") + suspend fun searchOrdersAdmin( + @RequestBody request: AdminOrdersHistoryRequest, + ): List? { + return marketQueryHandler.recentOrdersAdmin( + request.uuid, + request.symbol, + request.ouid, + request.startTime?.asLocalDateTime(), + request.endTime?.asLocalDateTime(), + request.orderType, + request.direction, + request.ascendingByTime, + request.limit, + request.offset + ) + } } \ No newline at end of file diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt index 529b04b06..a4f121f72 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.* class MarketController(private val marketQueryHandler: MarketQueryHandler) { @GetMapping("/ticker") - suspend fun priceChangeSince(@RequestParam interval: Interval): List { + suspend fun priceChangeSince(@RequestParam interval: Interval): List? { return marketQueryHandler.getTradeTickerData(interval) } @@ -28,7 +28,7 @@ class MarketController(private val marketQueryHandler: MarketQueryHandler) { @PathVariable symbol: String, @RequestParam direction: OrderDirection, @RequestParam(required = false) limit: Int = 500 - ): List { + ): List? { return if (direction == OrderDirection.BID) marketQueryHandler.openBidOrders(symbol, limit) else @@ -39,7 +39,7 @@ class MarketController(private val marketQueryHandler: MarketQueryHandler) { suspend fun getRecentTradesForSymbol( @PathVariable symbol: String, @RequestParam(required = false) limit: Int = 500 - ): List { + ): List? { return marketQueryHandler.recentTrades(symbol, limit) } @@ -54,7 +54,7 @@ class MarketController(private val marketQueryHandler: MarketQueryHandler) { } @GetMapping("/best-prices") - suspend fun getOrderBookForSymbol(@RequestParam symbols: List): List { + suspend fun getOrderBookForSymbol(@RequestParam symbols: List): List? { return marketQueryHandler.getBestPriceForSymbols(symbols) } diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketStatsController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketStatsController.kt index 12a44e8b2..8d59c349a 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketStatsController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketStatsController.kt @@ -15,12 +15,12 @@ import java.util.* class MarketStatsController(private val marketQueryHandler: MarketQueryHandler) { @GetMapping("/price/most-increased") - suspend fun getMostIncreasedPrices(@RequestParam interval: Interval, @RequestParam limit: Int): List { + suspend fun getMostIncreasedPrices(@RequestParam interval: Interval, @RequestParam limit: Int): List? { return marketQueryHandler.mostIncreasePrice(interval, limit) } @GetMapping("/price/most-decreased") - suspend fun getMostDecreasedPrices(@RequestParam interval: Interval, @RequestParam limit: Int): List { + suspend fun getMostDecreasedPrices(@RequestParam interval: Interval, @RequestParam limit: Int): List? { return marketQueryHandler.mostDecreasePrice(interval, limit) } diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt index edd972b6f..62527b2cd 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt @@ -15,17 +15,32 @@ class UserDataController( ) { @GetMapping("/{uuid}/order/{ouid}") - suspend fun getOrder(@PathVariable uuid: String, @PathVariable ouid: String): Order { + suspend fun getOrder( + @PathVariable uuid: String, + @PathVariable ouid: String, + @CurrentSecurityContext securityContext: SecurityContext, + ): Order { + if (securityContext.authentication.name != uuid) + throw OpexError.Forbidden.exception() return userQueryHandler.getOrder(uuid, ouid) ?: throw OpexError.NotFound.exception() } @PostMapping("/{uuid}/order/query") - suspend fun queryUserOrder(@PathVariable uuid: String, @RequestBody request: QueryOrderRequest): Order { + suspend fun queryUserOrder( + @PathVariable uuid: String, @RequestBody request: QueryOrderRequest, + @CurrentSecurityContext securityContext: SecurityContext, + ): Order { + if (securityContext.authentication.name != uuid) + throw OpexError.Forbidden.exception() return userQueryHandler.queryOrder(uuid, request) ?: throw OpexError.NotFound.exception() } @GetMapping("/{uuid}/orders/open") - suspend fun getUserOpenOrders(@PathVariable uuid: String, @RequestParam limit: Int): List { + suspend fun getUserOpenOrders( + @PathVariable uuid: String, @RequestParam limit: Int, @CurrentSecurityContext securityContext: SecurityContext, + ): List { + if (securityContext.authentication.name != uuid) + throw OpexError.Forbidden.exception() return userQueryHandler.openOrders(uuid, limit) } @@ -34,17 +49,33 @@ class UserDataController( @PathVariable uuid: String, @PathVariable symbol: String, @RequestParam limit: Int, - ): List { + @CurrentSecurityContext securityContext: SecurityContext, + + ): List { + if (securityContext.authentication.name != uuid) + throw OpexError.Forbidden.exception() return userQueryHandler.openOrders(uuid, symbol, limit) } @PostMapping("/{uuid}/orders") - suspend fun getUserOrders(@PathVariable uuid: String, @RequestBody request: AllOrderRequest): List { + suspend fun getUserOrders( + @PathVariable uuid: String, + @RequestBody request: AllOrderRequest, + @CurrentSecurityContext securityContext: SecurityContext, + ): List { + if (securityContext.authentication.name != uuid) + throw OpexError.Forbidden.exception() return userQueryHandler.allOrders(uuid, request) } @PostMapping("/{uuid}/trades") - suspend fun getUserTrades(@PathVariable uuid: String, @RequestBody request: TradeRequest): List { + suspend fun getUserTrades( + @PathVariable uuid: String, + @RequestBody request: TradeRequest, + @CurrentSecurityContext securityContext: SecurityContext, + ): List? { + if (securityContext.authentication.name != uuid) + throw OpexError.Forbidden.exception() return userQueryHandler.allTrades(uuid, request) } @@ -59,7 +90,7 @@ class UserDataController( return userQueryHandler.txOfTrades(transactionRequest.apply { owner = user }) } - @GetMapping("/order/history/{uuid}") + @GetMapping("/order/history") suspend fun getOrderHistory( @RequestParam symbol: String?, @RequestParam startTime: Long?, @@ -68,10 +99,10 @@ class UserDataController( @RequestParam direction: OrderDirection?, @RequestParam limit: Int?, @RequestParam offset: Int?, - @PathVariable uuid: String, + @CurrentSecurityContext securityContext: SecurityContext, ): List { return userQueryHandler.getOrderHistory( - uuid, + securityContext.authentication.name, symbol, startTime?.let { startTime.asLocalDateTime() }, endTime?.let { endTime.asLocalDateTime() }, @@ -82,17 +113,17 @@ class UserDataController( ) } - @GetMapping("/order/history/count/{uuid}") + @GetMapping("/order/history/count") suspend fun getOrderHistoryCount( @RequestParam symbol: String?, @RequestParam startTime: Long?, @RequestParam endTime: Long?, @RequestParam orderType: MatchingOrderType?, @RequestParam direction: OrderDirection?, - @PathVariable uuid: String, + @CurrentSecurityContext securityContext: SecurityContext, ): Long { return userQueryHandler.getOrderHistoryCount( - uuid, + securityContext.authentication.name, symbol, startTime?.let { startTime.asLocalDateTime() }, endTime?.let { endTime.asLocalDateTime() }, @@ -101,18 +132,18 @@ class UserDataController( ) } - @GetMapping("/trade/history/{uuid}") + @GetMapping("/trade/history") suspend fun getTradeHistory( - @PathVariable uuid: String, @RequestParam symbol: String?, @RequestParam startTime: Long?, @RequestParam endTime: Long?, @RequestParam direction: OrderDirection?, @RequestParam limit: Int?, @RequestParam offset: Int?, - ): List { + @CurrentSecurityContext securityContext: SecurityContext, + ): List? { return userQueryHandler.getTradeHistory( - uuid, + securityContext.authentication.name, symbol, startTime?.let { startTime.asLocalDateTime() }, endTime?.let { endTime.asLocalDateTime() }, @@ -122,16 +153,17 @@ class UserDataController( ) } - @GetMapping("/trade/history/count/{uuid}") + @GetMapping("/trade/history/count") suspend fun getTradeHistoryCount( - @PathVariable uuid: String, @RequestParam symbol: String?, @RequestParam startTime: Long?, @RequestParam endTime: Long?, @RequestParam direction: OrderDirection?, + @CurrentSecurityContext securityContext: SecurityContext, ): Long { + return userQueryHandler.getTradeHistoryCount( - uuid, + securityContext.authentication.name, symbol, startTime?.let { startTime.asLocalDateTime() }, endTime?.let { endTime.asLocalDateTime() }, diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminOrdersHistoryRequest.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminOrdersHistoryRequest.kt new file mode 100644 index 000000000..33c5e3c5d --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminOrdersHistoryRequest.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.market.app.data + +import co.nilin.opex.market.core.inout.MatchingOrderType +import co.nilin.opex.market.core.inout.OrderDirection + +data class AdminOrdersHistoryRequest( + val uuid: String?, + val symbol: String?, + val ouid: String?, + val startTime: Long?, + val endTime: Long?, + val orderType: MatchingOrderType?, + val direction: OrderDirection?, + val limit: Int? = 100, + val offset: Int? = 0, + val ascendingByTime: Boolean = false, + ) diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminTradesHistoryRequest.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminTradesHistoryRequest.kt index ecf655e43..58d97d6ed 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminTradesHistoryRequest.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/AdminTradesHistoryRequest.kt @@ -1,14 +1,21 @@ package co.nilin.opex.market.app.data +import org.apache.kafka.common.protocol.types.Field + data class AdminTradesHistoryRequest( + val symbol: String?, val baseAsset: String?, val quoteAsset: String?, + val uuid: String?, val makerUuid: String?, val takerUuid: String?, + val ouid: String?, + val makerOuid: String?, + val takerOuid: String?, val fromDate: Long?, val toDate: Long?, val excludeSelfTrade: Boolean = true, val ascendingByTime: Boolean = false, - val limit: Int, - val offset: Int, + val limit: Int? = 100, + val offset: Int? = 0, ) diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderData.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderData.kt index 3a0b3debb..342296680 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderData.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderData.kt @@ -2,21 +2,23 @@ package co.nilin.opex.market.core.inout import java.math.BigDecimal import java.time.LocalDateTime -import java.util.Date data class OrderData( val symbol: String, - val orderId: Long, - val orderType: MatchingOrderType, + val ouid: String, + val orderType: MatchingOrderType?, val side: OrderDirection, val price: BigDecimal, val quantity: BigDecimal, - val quoteQuantity: BigDecimal, - val executedQuantity: BigDecimal, - val takerFee: BigDecimal, - val makerFee: BigDecimal, - val status: Int, - val appearance: Int, + val quoteQuantity: BigDecimal?, + val executedQuantity: BigDecimal?, + val takerFee: BigDecimal?, + val makerFee: BigDecimal?, + val statusCode: Int?, + val status: OrderStatus?, + val appearance: Int?, val createDate: LocalDateTime, val updateDate: LocalDateTime, + val uuid: String? ) + diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt index 9a23460b4..89ab64eef 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt @@ -8,7 +8,7 @@ import java.util.* data class Trade( val symbol: String, val id: Long, - val orderId: Long, + val ouid: String, val price: BigDecimal, val quantity: BigDecimal, val quoteQuantity: BigDecimal, diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt new file mode 100644 index 000000000..740e474cf --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal +import java.time.LocalDateTime + +data class TradeUserContextProjection( + val symbol: String, + val baseAsset: String?, + val quoteAsset: String?, + val id: Long, + val price: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, + val time: LocalDateTime, + val isMakerBuyer: Boolean, + val ouid: String? = null, + val commission: BigDecimal? = null, + val commissionAsset: String? = null, + val isBuyer: Boolean? = null, + val isMaker: Boolean? = null +) + diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt index 364d93fcf..714e13cf4 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt @@ -6,17 +6,17 @@ import java.time.LocalDateTime interface MarketQueryHandler { - suspend fun getTradeTickerData(interval: Interval): List + suspend fun getTradeTickerData(interval: Interval): List? suspend fun getTradeTickerDateBySymbol(symbol: String, interval: Interval): PriceChange? - suspend fun openBidOrders(symbol: String, limit: Int): List + suspend fun openBidOrders(symbol: String, limit: Int): List? - suspend fun openAskOrders(symbol: String, limit: Int): List + suspend fun openAskOrders(symbol: String, limit: Int): List? suspend fun lastOrder(symbol: String): Order? - suspend fun recentTrades(symbol: String, limit: Int): List + suspend fun recentTrades(symbol: String, limit: Int): List? suspend fun recentTrades( symbol: String?, @@ -27,24 +27,42 @@ interface MarketQueryHandler { excludeSelfTrade: Boolean, limit: Int, offset: Int, - ): List + ): List? suspend fun recentTradesAdmin( + symbol: String?, baseAsset: String?, quoteAsset: String?, + uuid: String?, makerUuid: String?, takerUuid: String?, + ouid: String?, + makerOuid: String?, + takerOuid: String?, fromDate: LocalDateTime?, toDate: LocalDateTime?, excludeSelfTrade: Boolean, ascendingByTime: Boolean, - limit: Int, - offset: Int, - ): List + limit: Int?, + offset: Int?, + ): List? + + suspend fun recentOrdersAdmin( + uuid: String?, + symbol: String?, + ouid: String?, + fromDate: LocalDateTime?, + toDate: LocalDateTime?, + orderType: MatchingOrderType?, + direction: OrderDirection?, + ascendingByTime: Boolean, + limit: Int?, + offset: Int?, + ): List? suspend fun lastPrice(symbol: String?): List - suspend fun getBestPriceForSymbols(symbols: List): List + suspend fun getBestPriceForSymbols(symbols: List): List? suspend fun getCandleInfo( symbol: String, @@ -60,9 +78,9 @@ interface MarketQueryHandler { suspend fun numberOfOrders(interval: Interval, pair: String? = null): Long - suspend fun mostIncreasePrice(interval: Interval, limit: Int): List + suspend fun mostIncreasePrice(interval: Interval, limit: Int): List? - suspend fun mostDecreasePrice(interval: Interval, limit: Int): List + suspend fun mostDecreasePrice(interval: Interval, limit: Int): List? suspend fun mostVolume(interval: Interval): TradeVolumeStat? diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/UserQueryHandler.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/UserQueryHandler.kt index 0086ec66e..ef05ce908 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/UserQueryHandler.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/UserQueryHandler.kt @@ -15,12 +15,12 @@ interface UserQueryHandler { suspend fun allOrders(uuid: String, allOrderRequest: AllOrderRequest): List - suspend fun allTrades(uuid: String, request: TradeRequest): List + suspend fun allTrades(uuid: String, request: TradeRequest): List? suspend fun txOfTrades(transactionRequest: TransactionRequest): TransactionResponse? suspend fun getOrderHistory( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, @@ -31,7 +31,7 @@ interface UserQueryHandler { ): List suspend fun getOrderHistoryCount( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, @@ -40,17 +40,17 @@ interface UserQueryHandler { ): Long suspend fun getTradeHistory( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, direction: OrderDirection?, limit: Int?, offset: Int?, - ): List + ): List? suspend fun getTradeHistoryCount( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderRepository.kt index 2b3eb435b..6f7bbd9a3 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderRepository.kt @@ -12,6 +12,7 @@ import org.springframework.data.repository.reactive.ReactiveCrudRepository import org.springframework.stereotype.Repository import reactor.core.publisher.Flux import reactor.core.publisher.Mono + import java.time.LocalDateTime import java.util.* @@ -138,7 +139,7 @@ interface OrderRepository : ReactiveCrudRepository { @Query( """ select o.symbol, - o.order_id, + o.ouid, o.order_type, o.side, o.price, @@ -147,17 +148,18 @@ select o.symbol, os.executed_quantity, o.taker_fee, o.maker_fee, - os.status, + os.status as status_code, os.appearance, o.create_date, - os.date as update_date + os.date as update_date, + o.uuid from orders o left join (select * from order_status os1 where os1.date = (select max(os2.date) from order_status os2 where os2.ouid = os1.ouid)) os on o.ouid = os.ouid - WHERE uuid = :uuid + WHERE (:uuid is null or o.uuid = :uuid) and (:symbol is null or o.symbol = :symbol) and (:startTime is null or o.create_date >= :startTime) and (:endTime is null or o.create_date <= :endTime) @@ -168,7 +170,7 @@ order by create_date desc """ ) fun findByCriteria( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, @@ -182,7 +184,7 @@ order by create_date desc """ select count(*) from orders o - WHERE uuid = :uuid + WHERE (:uuid is null or o.uuid = :uuid) and (:symbol is null or o.symbol = :symbol) and (:startTime is null or o.create_date >= :startTime) and (:endTime is null or o.create_date <= :endTime) @@ -191,7 +193,7 @@ from orders o """ ) fun countByCriteria( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, @@ -199,4 +201,75 @@ from orders o direction: OrderDirection?, ): Mono + @Query(""" +SELECT + o.symbol , + o.ouid , + o.order_type , + o.side , + + o.price AS price, + o.quantity AS quantity, + o.quote_quantity , + + os.executed_quantity, + + o.taker_fee, + o.maker_fee, + + os.status as status_code, + os.appearance, + + o.create_date , + o.update_date, + + o.uuid + +FROM orders o + +LEFT JOIN ( + SELECT DISTINCT ON (ouid) + ouid, + executed_quantity, + status, + appearance, + date + FROM order_status + ORDER BY ouid, date DESC +) os +ON os.ouid = o.ouid + +WHERE + (:uuid IS NULL OR o.uuid = :uuid) + AND (:symbol IS NULL OR o.symbol = :symbol) + AND (:ouid IS NULL OR o.ouid = :ouid) + AND (:fromDate IS NULL OR o.create_date >= :fromDate) + AND (:toDate IS NULL OR o.create_date <= :toDate) + AND (:orderType IS NULL OR o.order_type = :orderType) + AND (:direction IS NULL OR o.side = :direction) + +ORDER BY +CASE WHEN :ascendingByTime = true + THEN o.create_date +END ASC, + +CASE WHEN :ascendingByTime = false + THEN o.create_date +END DESC + +LIMIT :limit +OFFSET :offset +""") + fun findRecentOrdersAdmin( + uuid: String?, + symbol: String?, + ouid: String?, + fromDate: LocalDateTime?, + toDate: LocalDateTime?, + orderType: MatchingOrderType?, + direction: OrderDirection?, + ascendingByTime: Boolean, + limit: Int?, + offset: Int? + ): Flux } \ No newline at end of file diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt index 2949f8cb5..455729ead 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt @@ -1,13 +1,7 @@ package co.nilin.opex.market.ports.postgres.dao -import co.nilin.opex.market.core.inout.BestPrice -import co.nilin.opex.market.core.inout.OrderDirection -import co.nilin.opex.market.core.inout.PriceStat -import co.nilin.opex.market.core.inout.Trade -import co.nilin.opex.market.core.inout.TradeVolumeStat -import co.nilin.opex.market.core.inout.Transaction +import co.nilin.opex.market.core.inout.* import co.nilin.opex.market.ports.postgres.model.* -import kotlinx.coroutines.flow.Flow import org.springframework.data.r2dbc.repository.Query import org.springframework.data.repository.query.Param import org.springframework.data.repository.reactive.ReactiveCrudRepository @@ -21,23 +15,152 @@ import java.util.* interface TradeRepository : ReactiveCrudRepository { @Query("select * from trades where :ouid in (taker_ouid, maker_ouid)") - fun findByOuid(@Param("ouid") ouid: String): Flow + fun findByOuid(@Param("ouid") ouid: String): Flux @Query("select * from trades where symbol = :symbol order by create_date desc limit 1") - fun findMostRecentBySymbol(symbol: String): Flow + fun findMostRecentBySymbol(symbol: String): Flux + + @Query("select * from trades where symbol = :symbol order by create_date desc limit :limit") + fun findBySymbolSortDescendingByCreateDate( + @Param("symbol") + symbol: String, + @Param("limit") + limit: Int, + ): Flux @Query( """ - select * from trades where :uuid in (taker_uuid, maker_uuid) - and (:fromTrade is null or id > :fromTrade) - and (:symbol is null or symbol = :symbol) - and (:startTime is null or trade_date >= :startTime) - and (:endTime is null or trade_date < :endTime) - order by trade_date DESC - limit :limit + SELECT + t.symbol AS symbol, + t.base_asset AS baseAsset, + t.quote_asset AS quoteAsset, + + t.trade_id AS id, + t.matched_price AS price, + t.matched_quantity AS quantity, + + CASE + WHEN mo.side = 'BID' + THEN mo.quote_quantity + ELSE to2.quote_quantity + END AS quoteQuantity, + + t.create_date AS time, + + CASE + WHEN mo.side = 'BID' + THEN TRUE + ELSE FALSE + END AS isMakerBuyer, + + NULL AS orderId, + NULL AS commission, + NULL AS commissionAsset, + NULL AS isBuyer, + NULL AS isMaker + + FROM trades t + + JOIN orders mo + ON mo.ouid = t.maker_ouid + + JOIN orders to2 + ON to2.ouid = t.taker_ouid + + WHERE (:symbol IS NULL OR t.symbol = :symbol) + + ORDER BY t.trade_date DESC + LIMIT :limit + """ + ) + fun findRecentMarketTrades( + @Param("symbol") + symbol: String?, + @Param("limit") + limit: Int + ): Flux + + @Query( """ + SELECT + t.symbol AS symbol, + + NULL AS baseAsset, + NULL AS quoteAsset, + + t.trade_id AS id, + + CASE + WHEN t.taker_uuid = :uuid + THEN t.taker_price + ELSE t.maker_price + END AS price, + + t.matched_quantity AS quantity, + + CASE + WHEN mo.side = 'BID' + THEN mo.quote_quantity + ELSE to2.quote_quantity + END AS quoteQuantity, + + t.create_date AS time, + + CASE + WHEN mo.side = 'BID' + THEN TRUE + ELSE FALSE + END AS isMakerBuyer, + + CASE + WHEN t.taker_uuid = :uuid + THEN to2.ouid + ELSE mo.ouid + END AS ouid, + + CASE + WHEN t.taker_uuid = :uuid + THEN t.taker_commission + ELSE t.maker_commission + END AS commission, + + CASE + WHEN t.taker_uuid = :uuid + THEN t.taker_commission_asset + ELSE t.maker_commission_asset + END AS commissionAsset, + + CASE + WHEN t.taker_uuid = :uuid + THEN to2.side = 'ASK' + ELSE mo.side = 'ASK' + END AS isBuyer, + + CASE + WHEN t.maker_uuid = :uuid + THEN TRUE + ELSE FALSE + END AS isMaker + + FROM trades t + + JOIN orders mo + ON mo.ouid = t.maker_ouid + + JOIN orders to2 + ON to2.ouid = t.taker_ouid + + WHERE :uuid IN (t.taker_uuid, t.maker_uuid) + AND (:fromTrade IS NULL OR t.id > :fromTrade) + AND (:symbol IS NULL OR t.symbol = :symbol) + AND (:startTime IS NULL OR t.trade_date >= :startTime) + AND (:endTime IS NULL OR t.trade_date < :endTime) + + ORDER BY t.trade_date DESC + LIMIT :limit +""" ) - fun findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan( + fun findTradesWithUserContext( @Param("uuid") uuid: String, @Param("symbol") @@ -48,16 +171,10 @@ interface TradeRepository : ReactiveCrudRepository { startTime: Date?, @Param("endTime") endTime: Date?, - limit: Int, - ): Flow - - @Query("select * from trades where symbol = :symbol order by create_date desc limit :limit") - fun findBySymbolSortDescendingByCreateDate( - @Param("symbol") - symbol: String, @Param("limit") - limit: Int, - ): Flow + limit: Int + ): Flux + @Query( """ @@ -217,7 +334,8 @@ interface TradeRepository : ReactiveCrudRepository { @Query("select symbol, matched_price from trades where create_date in (select max(create_date) from trades group by symbol)") fun findAllGroupBySymbol(): Flux - @Query(""" + @Query( + """ WITH intervals AS ( SELECT * FROM interval_generator( @@ -279,7 +397,8 @@ interface TradeRepository : ReactiveCrudRepository { limit :limit ) sub ORDER BY open_time ASC -""") +""" + ) suspend fun candleData( @Param("symbol") symbol: String, @@ -300,10 +419,10 @@ interface TradeRepository : ReactiveCrudRepository { suspend fun findFirstByCreateDate(): Mono @Query("select count(*) from trades where create_date >= :interval") - fun countNewerThan(interval: LocalDateTime): Flow + fun countNewerThan(interval: LocalDateTime): Flux @Query("select count(*) from trades where symbol = :symbol and create_date >= :interval") - fun countBySymbolNewerThan(interval: LocalDateTime, symbol: String): Flow + fun countBySymbolNewerThan(interval: LocalDateTime, symbol: String): Flux @Query( """ @@ -562,15 +681,20 @@ interface TradeRepository : ReactiveCrudRepository { excludeSelfTrade: Boolean, limit: Int, offset: Int, - ): Flow + ): Flux @Query( """ select * from trades where - (:baseAsset is null or base_asset = :baseAsset) + (:symbol is null or symbol=:symbol) + and (:baseAsset is null or base_asset = :baseAsset) and (:quoteAsset is null or quote_asset = :quoteAsset) + and (:uuid is null or :uuid in (maker_uuid, taker_uuid)) and (:makerUuid is null or maker_uuid = :makerUuid) and (:takerUuid is null or taker_uuid = :takerUuid) + and (:ouid is null or :ouid in (maker_ouid, taker_ouid)) + and (:makerOuid is null or maker_ouid = :makerOuid) + and (:takerOuid is null or taker_ouid = :takerOuid) and (:fromDate is null or trade_date >= :fromDate) and (:toDate is null or trade_date <= :toDate) and (:excludeSelfTrade is false or maker_uuid != taker_uuid) @@ -580,24 +704,34 @@ interface TradeRepository : ReactiveCrudRepository { """ ) suspend fun findByCriteriaByBaseQuoteAsc( + symbol: String?, baseAsset: String?, quoteAsset: String?, + uuid: String?, makerUuid: String?, takerUuid: String?, + ouid: String?, + makerOuid: String?, + takerOuid: String?, fromDate: LocalDateTime?, toDate: LocalDateTime?, excludeSelfTrade: Boolean, - limit: Int, - offset: Int, - ): Flow + limit: Int? = 10, + offset: Int? = 0, + ): Flux @Query( """ select * from trades where - (:baseAsset is null or base_asset = :baseAsset) + (:symbol is null or symbol=:symbol) + and (:baseAsset is null or base_asset = :baseAsset) and (:quoteAsset is null or quote_asset = :quoteAsset) + and (:uuid is null or :uuid in (maker_uuid, taker_uuid)) and (:makerUuid is null or maker_uuid = :makerUuid) and (:takerUuid is null or taker_uuid = :takerUuid) + and (:ouid is null or :ouid in (maker_ouid, taker_ouid)) + and (:makerOuid is null or maker_ouid = :makerOuid) + and (:takerOuid is null or taker_ouid = :takerOuid) and (:fromDate is null or trade_date >= :fromDate) and (:toDate is null or trade_date <= :toDate) and (:excludeSelfTrade is false or maker_uuid != taker_uuid) @@ -607,16 +741,21 @@ interface TradeRepository : ReactiveCrudRepository { """ ) suspend fun findByCriteriaByBaseQuoteDesc( + symbol: String?, baseAsset: String?, quoteAsset: String?, + uuid: String?, makerUuid: String?, takerUuid: String?, + ouid: String?, + makerOuid: String?, + takerOuid: String?, fromDate: LocalDateTime?, toDate: LocalDateTime?, excludeSelfTrade: Boolean, - limit: Int, - offset: Int, - ): Flow + limit: Int? = 10, + offset: Int? = 0, + ): Flux @Query( @@ -640,7 +779,7 @@ from trades t inner join orders o on (t.maker_uuid = :uuid and o.ouid = t.maker_ouid) or (t.taker_uuid = :uuid and o.ouid = t.taker_ouid) -where :uuid in (t.maker_uuid, t.taker_uuid) +where (:uuid is null or :uuid in (t.maker_uuid, t.taker_uuid)) and (:symbol is null or t.symbol = :symbol) and (:startTime is null or t.trade_date >= :startTime) and (:endTime is null or t.trade_date <= :endTime) @@ -651,14 +790,14 @@ where :uuid in (t.maker_uuid, t.taker_uuid) """ ) suspend fun findByCriteria( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, direction: OrderDirection?, limit: Int?, offset: Int?, - ): Flow + ): Flux @Query( """ @@ -667,7 +806,7 @@ from trades t inner join orders o on (t.maker_uuid = :uuid and o.ouid = t.maker_ouid) or (t.taker_uuid = :uuid and o.ouid = t.taker_ouid) -where :uuid in (t.maker_uuid, t.taker_uuid) +where (:uuid is null or :uuid in (t.maker_uuid, t.taker_uuid) ) and (:symbol is null or t.symbol = :symbol) and (:startTime is null or t.trade_date >= :startTime) and (:endTime is null or t.trade_date <= :endTime) @@ -675,7 +814,7 @@ where :uuid in (t.maker_uuid, t.taker_uuid) """ ) suspend fun countByCriteria( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt index 028feb32f..dea90334c 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt @@ -11,12 +11,10 @@ import co.nilin.opex.market.ports.postgres.dao.TradeRepository import co.nilin.opex.market.ports.postgres.model.TradeTickerData import co.nilin.opex.market.ports.postgres.util.RedisCacheHelper import co.nilin.opex.market.ports.postgres.util.asOrderDTO -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.singleOrNull -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.reactive.awaitFirst import kotlinx.coroutines.reactive.awaitFirstOrElse import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactor.awaitSingle import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.stereotype.Component import java.math.BigDecimal @@ -25,6 +23,7 @@ import java.time.LocalDateTime import java.time.ZoneId import java.util.* + @Component class MarketQueryHandlerImpl( private val orderRepository: OrderRepository, @@ -33,7 +32,6 @@ class MarketQueryHandlerImpl( private val redisCacheHelper: RedisCacheHelper, ) : MarketQueryHandler { - //TODO merge order and status fetching in query override suspend fun getTradeTickerData(interval: Interval): List { return redisCacheHelper.getOrElse("tradeTickerData:${interval.label}", 2.minutes()) { @@ -48,20 +46,19 @@ class MarketQueryHandlerImpl( val cacheId = "tradeTickerData:$symbol:${interval.label}" return redisCacheHelper.getOrElse(cacheId, 2.minutes()) { tradeRepository.tradeTickerBySymbol(symbol, interval.getLocalDateTime()) - .awaitFirstOrNull() + .awaitSingleOrNull() ?.asPriceChangeResponse(Date().time, interval.getTime()) } } - override suspend fun openBidOrders(symbol: String, limit: Int): List { + override suspend fun openBidOrders(symbol: String, limit: Int): List? { return orderRepository.findBySymbolAndDirectionAndStatusSortDescendingByPrice( symbol, OrderDirection.BID, limit, listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code) - ).collectList() - .awaitFirstOrElse { emptyList() } - .map { OrderBook(it.price, it.quantity) } + ).collectList().awaitSingleOrNull() + ?.map { OrderBook(it.price, it.quantity) } } override suspend fun openAskOrders(symbol: String, limit: Int): List { @@ -79,42 +76,32 @@ class MarketQueryHandlerImpl( // @Cacheable(cacheNames = ["marketCache"], key = "'lastOrder'") override suspend fun lastOrder(symbol: String): Order? { return redisCacheHelper.get("lastOrder") ?: run { - val order = orderRepository.findLastOrderBySymbol(symbol).awaitFirstOrNull() ?: return@run null - val status = orderStatusRepository.findMostRecentByOUID(order.ouid).awaitFirstOrNull() + val order = orderRepository.findLastOrderBySymbol(symbol).awaitSingleOrNull() ?: return@run null + val status = orderStatusRepository.findMostRecentByOUID(order.ouid).awaitSingleOrNull() order.asOrderDTO(status) }.also { redisCacheHelper.put("lastOrder", it) } } - //TODO need better query override suspend fun recentTrades(symbol: String, limit: Int): List { - val cacheKey = "recentTrades:${symbol.lowercase()}" - val recentTradesCache = redisCacheHelper.getList(cacheKey) - if (!recentTradesCache.isNullOrEmpty()) - return recentTradesCache.toList().take(limit) - return tradeRepository.findBySymbolSortDescendingByCreateDate(symbol, limit) + return tradeRepository + .findRecentMarketTrades(symbol, limit) .map { - val takerOrder = orderRepository.findByOuid(it.takerOuid).awaitFirst() - val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst() - val isMakerBuyer = makerOrder.direction == OrderDirection.BID MarketTrade( - it.symbol, - it.baseAsset, - it.quoteAsset, - it.tradeId, - it.matchedPrice, - it.matchedQuantity, - if (isMakerBuyer) - makerOrder.quoteQuantity!! - else - takerOrder.quoteQuantity!!, - Date.from(it.createDate.atZone(ZoneId.systemDefault()).toInstant()), - true, - isMakerBuyer + symbol = it.symbol, + baseAsset = it.baseAsset!!, + quoteAsset = it.quoteAsset!!, + id = it.id, + price = it.price, + quantity = it.quantity, + quoteQuantity = it.quoteQuantity, + Date.from(it.time.atZone(ZoneId.systemDefault()).toInstant()), + isBestMatch = true, + isMakerBuyer = it.isMakerBuyer ) - }.toList() - .onEach { redisCacheHelper.putListItem(cacheKey, it) } - .also { redisCacheHelper.setExpiration(cacheKey, 60.minutes()) } + } + .collectList() + .awaitSingle() } override suspend fun recentTrades( @@ -126,12 +113,20 @@ class MarketQueryHandlerImpl( excludeSelfTrade: Boolean, limit: Int, offset: Int, - ): List { - return tradeRepository.findByCriteria(symbol, makerUuid, takerUuid, fromDate, toDate,excludeSelfTrade, limit, offset) + ): List? { + return tradeRepository.findByCriteria( + symbol, + makerUuid, + takerUuid, + fromDate, + toDate, + excludeSelfTrade, + limit, + offset + ) .map { TradeData( tradeId = it.tradeId, - dbTradeId = it.id ?: -1, symbol = it.symbol, baseAsset = it.baseAsset, quoteAsset = it.quoteAsset, @@ -150,28 +145,38 @@ class MarketQueryHandlerImpl( takerCommissionAsset = it.takerCommissionAsset, ) } - .toList() + .collectList().awaitSingleOrNull() } override suspend fun recentTradesAdmin( + symbol: String?, baseAsset: String?, quoteAsset: String?, + uuid: String?, makerUuid: String?, takerUuid: String?, + ouid: String?, + makerOuid: String?, + takerOuid: String?, fromDate: LocalDateTime?, toDate: LocalDateTime?, excludeSelfTrade: Boolean, ascendingByTime: Boolean, - limit: Int, - offset: Int, - ): List { + limit: Int?, + offset: Int?, + ): List? { val flow = if (ascendingByTime) { tradeRepository.findByCriteriaByBaseQuoteAsc( + symbol, baseAsset, quoteAsset, + uuid, makerUuid, takerUuid, + ouid, + makerOuid, + takerOuid, fromDate, toDate, excludeSelfTrade, @@ -180,10 +185,15 @@ class MarketQueryHandlerImpl( ) } else { tradeRepository.findByCriteriaByBaseQuoteDesc( + symbol, baseAsset, quoteAsset, + uuid, makerUuid, takerUuid, + ouid, + makerOuid, + takerOuid, fromDate, toDate, excludeSelfTrade, @@ -212,8 +222,39 @@ class MarketQueryHandlerImpl( makerCommissionAsset = it.makerCommissionAsset, takerCommissionAsset = it.takerCommissionAsset, ) - } - .toList() + }.collectList().awaitSingleOrNull() + + } + + override suspend fun recentOrdersAdmin( + uuid: String?, + symbol: String?, + ouid: String?, + fromDate: LocalDateTime?, + toDate: LocalDateTime?, + orderType: MatchingOrderType?, + direction: OrderDirection?, + ascendingByTime: Boolean, + limit: Int?, + offset: Int? + ): List { + + return orderRepository + .findRecentOrdersAdmin( + uuid, + symbol, + ouid, + fromDate, + toDate, + orderType, + direction, + ascendingByTime, + limit, + offset + ) + .map { it.copy(status = OrderStatus.fromCode(it.statusCode)) } + .collectList() + .awaitSingle() } override suspend fun lastPrice(symbol: String?): List { @@ -243,14 +284,14 @@ class MarketQueryHandlerImpl( limit: Int, ): List { val st = if (startTime == null) - tradeRepository.findFirstByCreateDate().awaitFirstOrNull()?.createDate ?: LocalDateTime.now() + tradeRepository.findFirstByCreateDate().awaitSingleOrNull()?.createDate ?: LocalDateTime.now() else with(Instant.ofEpochMilli(startTime)) { LocalDateTime.ofInstant(this, ZoneId.systemDefault()) } val et = if (endTime == null) - tradeRepository.findLastByCreateDate().awaitFirstOrNull()?.createDate ?: LocalDateTime.now() + tradeRepository.findLastByCreateDate().awaitSingleOrNull()?.createDate ?: LocalDateTime.now() else with(Instant.ofEpochMilli(endTime)) { LocalDateTime.ofInstant(this, ZoneId.systemDefault()) @@ -288,12 +329,13 @@ class MarketQueryHandlerImpl( override suspend fun numberOfTrades(interval: Interval, pair: String?): Long { return if (pair != null) redisCacheHelper.getOrElse("tradeCount:$pair:${interval.label}", 1.hours()) { - tradeRepository.countBySymbolNewerThan(interval.getLocalDateTime(), pair).singleOrNull()?.approximate() + tradeRepository.countBySymbolNewerThan(interval.getLocalDateTime(), pair).awaitFirstOrNull() + ?.approximate() ?: 0 } else redisCacheHelper.getOrElse("tradeCount:${interval.label}", 1.hours()) { - tradeRepository.countNewerThan(interval.getLocalDateTime()).singleOrNull()?.approximate() ?: 0 + tradeRepository.countNewerThan(interval.getLocalDateTime()).awaitFirstOrNull()?.approximate() ?: 0 } } diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt index 87ee17cac..26a1166c1 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt @@ -14,12 +14,12 @@ import kotlinx.coroutines.flow.toList import kotlinx.coroutines.reactive.awaitFirst import kotlinx.coroutines.reactive.awaitFirstOrElse import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactor.awaitSingle import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.stereotype.Component import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId -import java.util.* @Component class UserQueryHandlerImpl( @@ -84,82 +84,84 @@ class UserQueryHandlerImpl( .toList() } - override suspend fun allTrades(uuid: String, request: TradeRequest): List { - return tradeRepository.findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan( - uuid, request.symbol, request.fromTrade, request.startTime, request.endTime, request.limit - ).map { - val takerOrder = orderRepository.findByOuid(it.takerOuid).awaitFirst() - val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst() - val isMakerBuyer = makerOrder.direction == OrderDirection.BID - Trade( - it.symbol, - it.tradeId, - if (it.takerUuid == uuid) takerOrder.orderId!! else makerOrder.orderId!!, - if (it.takerUuid == uuid) it.takerPrice else it.makerPrice, - it.matchedQuantity, - if (isMakerBuyer) makerOrder.quoteQuantity!! else takerOrder.quoteQuantity!!, - if (it.takerUuid == uuid) it.takerCommission!! else it.makerCommission!!, - if (it.takerUuid == uuid) it.takerCommissionAsset!! else it.makerCommissionAsset!!, - it.createDate, - if (it.takerUuid == uuid) - OrderDirection.ASK == takerOrder.direction - else - OrderDirection.ASK == makerOrder.direction, - it.makerUuid == uuid, - true, - isMakerBuyer - ) - }.toList() + override suspend fun allTrades( + uuid: String, + request: TradeRequest + ): List? { + + return tradeRepository.findTradesWithUserContext( + uuid, + request.symbol, + request.fromTrade, + request.startTime, + request.endTime, + request.limit + ) + .map { + Trade( + it.symbol, + it.id, + requireNotNull(it.ouid), + it.price, + it.quantity, + it.quoteQuantity, + requireNotNull(it.commission), + requireNotNull(it.commissionAsset), + it.time, + requireNotNull(it.isBuyer), + requireNotNull(it.isMaker), + true, + it.isMakerBuyer + ) + } + .collectList() + .awaitSingle() } override suspend fun txOfTrades(transactionRequest: TransactionRequest): TransactionResponse? { - if (transactionRequest.ascendingByTime == true) - return TransactionResponse( - tradeRepository.findTxOfTradesAsc( - transactionRequest.owner!!, - transactionRequest.startTime?.let { - LocalDateTime.ofInstant( - Instant.ofEpochMilli(transactionRequest.startTime!!), - ZoneId.systemDefault() - ) - } - ?: null, - transactionRequest.endTime?.let { - LocalDateTime.ofInstant( - Instant.ofEpochMilli(transactionRequest.endTime!!), - ZoneId.systemDefault() - ) - } - ?: null, - transactionRequest.offset, transactionRequest.limit - ).map { it.toDto() }.collectList()?.awaitFirstOrNull() - ) + val trades = if (transactionRequest.ascendingByTime == true) + + tradeRepository.findTxOfTradesAsc( + transactionRequest.owner!!, + transactionRequest.startTime?.let { + LocalDateTime.ofInstant( + Instant.ofEpochMilli(transactionRequest.startTime!!), + ZoneId.systemDefault() + ) + }, + transactionRequest.endTime?.let { + LocalDateTime.ofInstant( + Instant.ofEpochMilli(transactionRequest.endTime!!), + ZoneId.systemDefault() + ) + }, + transactionRequest.offset, transactionRequest.limit + ).map { it.toDto() }.collectList().awaitFirstOrNull() else - return TransactionResponse( - tradeRepository.findTxOfTradesDesc( - transactionRequest.owner!!, - transactionRequest.startTime?.let { - LocalDateTime.ofInstant( - Instant.ofEpochMilli(transactionRequest.startTime!!), - ZoneId.systemDefault() - ) - } - ?: null, - transactionRequest.endTime?.let { - LocalDateTime.ofInstant( - Instant.ofEpochMilli(transactionRequest.endTime!!), - ZoneId.systemDefault() - ) - } - ?: null, - transactionRequest.offset, transactionRequest.limit - ).map { it.toDto() }.collectList()?.awaitFirstOrNull() - ) + + tradeRepository.findTxOfTradesDesc( + transactionRequest.owner!!, + transactionRequest.startTime?.let { + LocalDateTime.ofInstant( + Instant.ofEpochMilli(transactionRequest.startTime!!), + ZoneId.systemDefault() + ) + }, + transactionRequest.endTime?.let { + LocalDateTime.ofInstant( + Instant.ofEpochMilli(transactionRequest.endTime!!), + ZoneId.systemDefault() + ) + }, + transactionRequest.offset, transactionRequest.limit + ).map { it.toDto() }.collectList()?.awaitFirstOrNull() + + return TransactionResponse(trades) } override suspend fun getOrderHistory( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, @@ -177,11 +179,11 @@ class UserQueryHandlerImpl( direction, limit, offset, - ).toList() + ).map { it.copy(status = OrderStatus.fromCode(it.statusCode)) }.toList() } override suspend fun getOrderHistoryCount( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, @@ -199,14 +201,14 @@ class UserQueryHandlerImpl( } override suspend fun getTradeHistory( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, direction: OrderDirection?, limit: Int?, offset: Int?, - ): List { + ): List? { return tradeRepository.findByCriteria( uuid, symbol, @@ -215,11 +217,11 @@ class UserQueryHandlerImpl( direction, limit, offset - ).toList() + ).collectList().awaitSingleOrNull() } override suspend fun getTradeHistoryCount( - uuid: String, + uuid: String?, symbol: String?, startTime: LocalDateTime?, endTime: LocalDateTime?, diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt index 6de56844a..746e92188 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt @@ -13,7 +13,6 @@ import co.nilin.opex.market.ports.postgres.util.RedisCacheHelper import io.mockk.coEvery import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -60,8 +59,8 @@ class MarketQueryHandlerTest { val orderBookResponses = marketQueryHandler.openBidOrders(VALID.ETH_USDT, 1) assertThat(orderBookResponses).isNotNull - assertThat(orderBookResponses.size).isEqualTo(1) - assertThat(orderBookResponses.first()).isEqualTo(VALID.ORDER_BOOK_RESPONSE) + assertThat(orderBookResponses?.size).isEqualTo(1) + assertThat(orderBookResponses?.first()).isEqualTo(VALID.ORDER_BOOK_RESPONSE) } @Test @@ -114,9 +113,7 @@ class MarketQueryHandlerTest { every { redisCacheHelper.getList(any()) } returns null every { tradeRepository.findBySymbolSortDescendingByCreateDate(VALID.ETH_USDT, 1) - } returns flow { - emit(VALID.TRADE_MODEL) - } + } returns Flux.just(VALID.TRADE_MODEL) every { orderRepository.findByOuid(VALID.TRADE_MODEL.makerOuid) } returns Mono.just(VALID.MAKER_ORDER_MODEL) diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerTest.kt index 26eda70c6..415c75fe4 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerTest.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerTest.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import reactor.core.publisher.Flux import reactor.core.publisher.Mono class UserQueryHandlerTest { @@ -47,7 +48,7 @@ class UserQueryHandlerTest { @Test fun givenOrderAndTrade_whenAllTrades_thenTradeResponseList(): Unit = runBlocking { every { - tradeRepository.findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan( + tradeRepository.findTradesWithUserContext( VALID.PRINCIPAL.name, VALID.TRADE_REQUEST.symbol, 1, @@ -55,9 +56,7 @@ class UserQueryHandlerTest { VALID.TRADE_REQUEST.endTime, VALID.TRADE_REQUEST.limit ) - } returns flow { - emit(VALID.TRADE_MODEL) - } + } returns Flux.just(VALID.TRADE_USER_CONTEXT) every { orderRepository.findByOuid(VALID.TRADE_MODEL.makerOuid) } returns Mono.just(VALID.MAKER_ORDER_MODEL) @@ -68,7 +67,7 @@ class UserQueryHandlerTest { val tradeResponses = userQueryHandler.allTrades(VALID.PRINCIPAL.name, VALID.TRADE_REQUEST) assertThat(tradeResponses).isNotNull - assertThat(tradeResponses.count()).isEqualTo(1) + assertThat(tradeResponses?.count()).isEqualTo(1) } @Test diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt index f0d550546..d55d2d185 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt @@ -108,6 +108,24 @@ object VALID { UPDATE_DATE ) + + val TRADE_USER_CONTEXT = TradeUserContextProjection( + ETH_USDT, + "ETH", + "USDT", + 1, + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.001), // Minimum of orders quantities + BigDecimal.valueOf(100000), + CREATE_DATE, + true, + MAKER_ORDER_MODEL.ouid, + BigDecimal.valueOf(0.001), + "USDT", + true, + true + ) + val LAST_PRICE_MODEL = LastPrice("ETH_USDT", BigDecimal.valueOf(100000)) val AGGREGATED_ORDER_PRICE_MODEL = AggregatedOrderPriceModel( diff --git a/otp/otp-app/src/main/kotlin/co/nilin/opex/otp/app/service/message/EmailSender.kt b/otp/otp-app/src/main/kotlin/co/nilin/opex/otp/app/service/message/EmailSender.kt index 402e72c93..b3236fb4a 100644 --- a/otp/otp-app/src/main/kotlin/co/nilin/opex/otp/app/service/message/EmailSender.kt +++ b/otp/otp-app/src/main/kotlin/co/nilin/opex/otp/app/service/message/EmailSender.kt @@ -7,66 +7,82 @@ import jakarta.mail.internet.InternetAddress import jakarta.mail.internet.MimeMessage import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component - +import java.util.Properties @Component class EmailSender( @Value("\${otp.email.host}") private val host: String, + @Value("\${otp.email.port}") private val port: String, + @Value("\${otp.email.username}") private val username: String, + @Value("\${otp.email.password}") private val password: String, + @Value("\${otp.email.from}") - private val from: String, + private val fromAddress: String, + @Value("\${otp.email.proxy.enabled}") - private val proxyIsEnabled: Boolean, + private val proxyEnabled: Boolean, + @Value("\${otp.email.proxy.host}") private val proxyHost: String?, + @Value("\${otp.email.proxy.port}") private val proxyPort: String? ) : MessageSender { private val logger by LoggerDelegate() - override suspend fun send(receiver: String, message: String, metadata: Map): Boolean { + override suspend fun send( + receiver: String, + message: String, + metadata: Map + ): Boolean { + val subject = "Your otp code" - val properties = System.getProperties() - properties.setProperty("mail.smtp.host", host) - properties["mail.smtp.port"] = port - properties["mail.smtp.auth"] = "true" - properties["mail.smtp.starttls.enable"] = "true" - properties["mail.smtp.from"] = from - properties["mail.smtp.ssl.protocols"] = "TLSv1.2" - if (proxyIsEnabled) { - properties["mail.smtp.socks.host"] = proxyHost - properties["mail.smtp.socks.port"] = proxyPort - } + try { + // 🔥 SOCKS must be JVM-level (NOT JavaMail props) + if (proxyEnabled) { + System.setProperty("socksProxyHost", proxyHost) + System.setProperty("socksProxyPort", proxyPort) + } - val session = Session.getDefaultInstance(properties) + val props = Properties().apply { + put("mail.smtp.host", host) + put("mail.smtp.port", port) + put("mail.smtp.auth", "true") + put("mail.smtp.starttls.enable", "true") + put("mail.smtp.starttls.required", "true") + put("mail.smtp.from", fromAddress) + put("mail.smtp.ssl.protocols", "TLSv1.2") + } + + val session = Session.getInstance(props) - return try { val msg = MimeMessage(session).apply { setSubject(subject) - setFrom(InternetAddress(this@EmailSender.from)) + setFrom(InternetAddress(fromAddress )) addRecipient(Message.RecipientType.TO, InternetAddress(receiver)) setContent(message, "text/html; charset=utf-8") } - with(session.getTransport("smtp")) { - connect(host, port.toInt(), username, password) - sendMessage(msg, msg.allRecipients) - close() + session.getTransport("smtp").use { transport -> + transport.connect(host, port.toInt(), username, password) + transport.sendMessage(msg, msg.allRecipients) } logger.info("Successfully sent email message") - true + return true + } catch (e: Exception) { logger.error("Failed to send email message", e) - false + return false } } } \ No newline at end of file diff --git a/otp/otp-app/src/main/resources/application.yml b/otp/otp-app/src/main/resources/application.yml index 938ee736f..6dd681baa 100644 --- a/otp/otp-app/src/main/resources/application.yml +++ b/otp/otp-app/src/main/resources/application.yml @@ -61,7 +61,7 @@ otp: password: ${SMTP_PASS} from: ${SMTP_FROM} proxy: - enabled: ${SMTP_PROXY_ENABLED:false} + enabled: ${SMTP_PROXY_ENABLED} host: ${SMTP_PROXY_HOST} port: ${SMTP_PROXY_PORT} custom-message: diff --git a/otp/otp-app/src/test/kotlin/co/nilin/opex/otp/app/conttroller/OTPControllerIT.kt b/otp/otp-app/src/test/kotlin/co/nilin/opex/otp/app/conttroller/OTPControllerIT.kt index c83c6a29e..f07ebd48b 100644 --- a/otp/otp-app/src/test/kotlin/co/nilin/opex/otp/app/conttroller/OTPControllerIT.kt +++ b/otp/otp-app/src/test/kotlin/co/nilin/opex/otp/app/conttroller/OTPControllerIT.kt @@ -18,8 +18,8 @@ class EmailSenderIT { port = requireEnv("SMTP_PORT"), username = requireEnv("SMTP_USER"), password = requireEnv("SMTP_PASS"), - from = requireEnv("SMTP_FROM"), - proxyIsEnabled = true, + fromAddress = requireEnv("SMTP_FROM"), + proxyEnabled = true, proxyHost = requireEnv("SMTP_SOCKS_HOST"), proxyPort = requireEnv("SMTP_SOCKS_PORT") ) diff --git a/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/controller/ProfileAdminController.kt b/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/controller/ProfileAdminController.kt index 70622da98..9f04bcc6e 100644 --- a/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/controller/ProfileAdminController.kt +++ b/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/controller/ProfileAdminController.kt @@ -116,4 +116,15 @@ class ProfileAdminController( } } + data class ResolveUsersRequest( + val uuids: List + ) + + @PostMapping("/users/resolve") + suspend fun resolveUsers( + @RequestBody request: ResolveUsersRequest + ): Map { + return profileManagement.resolveUsers(request.uuids) + } + } \ No newline at end of file diff --git a/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/service/ProfileManagement.kt b/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/service/ProfileManagement.kt index daaa1653c..2d429022c 100644 --- a/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/service/ProfileManagement.kt +++ b/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/service/ProfileManagement.kt @@ -191,7 +191,7 @@ class ProfileManagement( } } - private suspend fun approveProfileAutomatically(userId: String, completedProfile: Profile): Profile { + private suspend fun approveProfileAutomatically(userId: String, completedProfile: Profile): Profile { kycLevelUpdatedPublisher.publish( KycLevelUpdatedEvent(userId, KycLevel.LEVEL_2, LocalDateTime.now()) ) @@ -239,4 +239,8 @@ class ProfileManagement( ) ) } + + suspend fun resolveUsers(request: List): Map { + return profilePersister.resolveUsers(request) + } } \ No newline at end of file diff --git a/profile/profile-core/src/main/kotlin/co/nilin/opex/profile/core/spi/ProfilePersister.kt b/profile/profile-core/src/main/kotlin/co/nilin/opex/profile/core/spi/ProfilePersister.kt index 5c8c362ec..33e106ed1 100644 --- a/profile/profile-core/src/main/kotlin/co/nilin/opex/profile/core/spi/ProfilePersister.kt +++ b/profile/profile-core/src/main/kotlin/co/nilin/opex/profile/core/spi/ProfilePersister.kt @@ -16,6 +16,7 @@ interface ProfilePersister { suspend fun createProfile(data: Profile): Mono suspend fun getProfile(userId: String): Profile suspend fun getAllProfile(profileRequest: ProfileRequest): List + suspend fun resolveUsers(uuids: List): Map suspend fun getHistory(userId: String, offset: Int, limit: Int): List suspend fun updateUserLevelAndStatus(userId: String, userLevel: KycLevel, retry: Boolean) suspend fun validateEmailForUpdate(userId: String, email: String) diff --git a/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/ProfileFullNameProjection.kt b/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/ProfileFullNameProjection.kt new file mode 100644 index 000000000..234944031 --- /dev/null +++ b/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/ProfileFullNameProjection.kt @@ -0,0 +1,4 @@ +package co.nilin.opex.profile.ports.postgres + +data class ProfileFullNameProjection( val uuid: String, + val fullName: String?) diff --git a/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/dao/ProfileRepository.kt b/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/dao/ProfileRepository.kt index 45de9f700..cefd8aa8a 100644 --- a/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/dao/ProfileRepository.kt +++ b/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/dao/ProfileRepository.kt @@ -4,6 +4,7 @@ import co.nilin.opex.profile.core.data.kyc.KycLevel import co.nilin.opex.profile.core.data.profile.Gender import co.nilin.opex.profile.core.data.profile.NationalityType import co.nilin.opex.profile.core.data.profile.ProfileStatus +import co.nilin.opex.profile.ports.postgres.ProfileFullNameProjection import co.nilin.opex.profile.ports.postgres.model.entity.ProfileModel import org.springframework.data.r2dbc.repository.Query import org.springframework.data.repository.reactive.ReactiveCrudRepository @@ -20,6 +21,9 @@ interface ProfileRepository : ReactiveCrudRepository { @Query("select * from profile where identifier = :identifier order by last_update_date desc limit 1") fun findLatestByIdentifier(identifier: String ): Mono + @Query("select p.user_id AS uuid, CONCAT (p.first_name, ' ', p.last_name) AS full_name from profile p where p.user_id in (:uuids)") + fun findFullNameByUuid(uuids: List): Flux + @Query( """ SELECT * diff --git a/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/imp/ProfileManagementImp.kt b/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/imp/ProfileManagementImp.kt index d9c598a6c..339fef496 100644 --- a/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/imp/ProfileManagementImp.kt +++ b/profile/profile-ports/profile-postgres/src/main/kotlin/co/nilin/opex/profile/ports/postgres/imp/ProfileManagementImp.kt @@ -137,6 +137,15 @@ class ProfileManagementImp( .awaitSingle() } + override suspend fun resolveUsers(uuids: List): Map { + + return profileRepository.findFullNameByUuid(uuids).collectMap( + { it.uuid }, + { it.fullName } + ).awaitSingle() + + } + override suspend fun getHistory(userId: String, offset: Int, limit: Int): List { val resp: MutableList = ArrayList() From 8fc410e4f2efa7a56f1c4f6217292a3763cb5981 Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Sun, 14 Jun 2026 18:09:51 +0330 Subject: [PATCH 05/11] Clean the user market services --- .../binance/controller/AccountController.kt | 30 ++++++---- .../app/controller/UserDataController.kt | 56 +++++++------------ .../postgres/impl/MarketQueryHandlerImpl.kt | 4 +- .../postgres/impl/MarketQueryHandlerTest.kt | 4 +- .../gateway/app/proxy/PairConfigLoaderImpl.kt | 5 +- .../src/main/resources/application.yml | 2 +- .../otp/app/service/message/EmailSender.kt | 15 +++-- 7 files changed, 54 insertions(+), 62 deletions(-) diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt index 12c7af9c1..91224c846 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt @@ -151,7 +151,7 @@ class AccountController( if (orderId == null && origClientOrderId == null) throw OpexError.BadRequest.exception("'orderId' or 'origClientOrderId' must be sent") - val order = queryHandler.queryOrder(principal, localSymbol, orderId, origClientOrderId) + val order = queryHandler.queryOrder(securityContext.jwtAuthentication().tokenValue(), localSymbol, orderId, origClientOrderId) ?: throw OpexError.OrderNotFound.exception() val response = CancelOrderResponse( @@ -220,10 +220,11 @@ class AccountController( @RequestParam(required = false) recvWindow: Long?, //The value cannot be greater than 60000 @RequestParam - timestamp: Long - ): QueryOrderResponse { + timestamp: Long, + @CurrentSecurityContext securityContext: SecurityContext, + ): QueryOrderResponse { val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexError.SymbolNotFound.exception() - return queryHandler.queryOrder(principal, internalSymbol, orderId, origClientOrderId) + return queryHandler.queryOrder(securityContext.jwtAuthentication().tokenValue(), internalSymbol, orderId, origClientOrderId) ?.asQueryOrderResponse() ?.apply { this.symbol = symbol } ?: throw OpexError.OrderNotFound.exception() @@ -261,10 +262,11 @@ class AccountController( @RequestParam timestamp: Long, @RequestParam(required = false) - limit: Int? - ): List { + limit: Int?, + @CurrentSecurityContext securityContext: SecurityContext, + ): List { val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexError.SymbolNotFound.exception() - return queryHandler.openOrders(principal, internalSymbol, limit).map { + return queryHandler.openOrders(securityContext.jwtAuthentication().tokenValue(), internalSymbol, limit).map { it.asQueryOrderResponse().apply { symbol?.let { s -> this.symbol = s } } } } @@ -304,10 +306,13 @@ class AccountController( @RequestParam(required = false) recvWindow: Long?, //The value cannot be greater than 60000 @RequestParam - timestamp: Long - ): List { + timestamp: Long, + @CurrentSecurityContext securityContext: SecurityContext, + + + ): List { val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexError.SymbolNotFound.exception() - return queryHandler.allOrders(principal, internalSymbol, startTime, endTime, limit).map { + return queryHandler.allOrders(securityContext.jwtAuthentication().tokenValue(), internalSymbol, startTime, endTime, limit).map { it.asQueryOrderResponse().apply { symbol?.let { s -> this.symbol = s } } } } @@ -351,11 +356,12 @@ class AccountController( @RequestParam(required = false) recvWindow: Long?, //The value cannot be greater than 60000 @RequestParam - timestamp: Long + timestamp: Long, + @CurrentSecurityContext securityContext: SecurityContext, ): List { val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexError.SymbolNotFound.exception() - return queryHandler.allTrades(principal, internalSymbol, fromId, startTime, endTime, limit) + return queryHandler.allTrades(securityContext.jwtAuthentication().tokenValue(), internalSymbol, fromId, startTime, endTime, limit) .map { TradeResponse( symbol ?: "", diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt index 62527b2cd..911200e69 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt @@ -14,80 +14,62 @@ class UserDataController( private val userQueryHandler: UserQueryHandler ) { - @GetMapping("/{uuid}/order/{ouid}") + @GetMapping("/order/{ouid}") suspend fun getOrder( - @PathVariable uuid: String, @PathVariable ouid: String, @CurrentSecurityContext securityContext: SecurityContext, ): Order { - if (securityContext.authentication.name != uuid) - throw OpexError.Forbidden.exception() - return userQueryHandler.getOrder(uuid, ouid) ?: throw OpexError.NotFound.exception() + return userQueryHandler.getOrder(securityContext.authentication.name, ouid) + ?: throw OpexError.NotFound.exception() } - @PostMapping("/{uuid}/order/query") + @PostMapping("/order/query") suspend fun queryUserOrder( - @PathVariable uuid: String, @RequestBody request: QueryOrderRequest, + @RequestBody request: QueryOrderRequest, @CurrentSecurityContext securityContext: SecurityContext, ): Order { - if (securityContext.authentication.name != uuid) - throw OpexError.Forbidden.exception() - return userQueryHandler.queryOrder(uuid, request) ?: throw OpexError.NotFound.exception() + return userQueryHandler.queryOrder(securityContext.authentication.name, request) + ?: throw OpexError.NotFound.exception() } - @GetMapping("/{uuid}/orders/open") + @GetMapping("/orders/open") suspend fun getUserOpenOrders( - @PathVariable uuid: String, @RequestParam limit: Int, @CurrentSecurityContext securityContext: SecurityContext, + @RequestParam limit: Int, @CurrentSecurityContext securityContext: SecurityContext, ): List { - if (securityContext.authentication.name != uuid) - throw OpexError.Forbidden.exception() - return userQueryHandler.openOrders(uuid, limit) + return userQueryHandler.openOrders(securityContext.authentication.name, limit) } - @GetMapping("/{uuid}/orders/{symbol}/open") + @GetMapping("/orders/{symbol}/open") suspend fun getUserOpenOrders( - @PathVariable uuid: String, @PathVariable symbol: String, @RequestParam limit: Int, @CurrentSecurityContext securityContext: SecurityContext, - - ): List { - if (securityContext.authentication.name != uuid) - throw OpexError.Forbidden.exception() - return userQueryHandler.openOrders(uuid, symbol, limit) + ): List { + return userQueryHandler.openOrders(securityContext.authentication.name, symbol, limit) } - @PostMapping("/{uuid}/orders") + @PostMapping("/orders") suspend fun getUserOrders( - @PathVariable uuid: String, @RequestBody request: AllOrderRequest, @CurrentSecurityContext securityContext: SecurityContext, ): List { - if (securityContext.authentication.name != uuid) - throw OpexError.Forbidden.exception() - return userQueryHandler.allOrders(uuid, request) + return userQueryHandler.allOrders(securityContext.authentication.name, request) } - @PostMapping("/{uuid}/trades") + @PostMapping("/trades") suspend fun getUserTrades( - @PathVariable uuid: String, @RequestBody request: TradeRequest, @CurrentSecurityContext securityContext: SecurityContext, ): List? { - if (securityContext.authentication.name != uuid) - throw OpexError.Forbidden.exception() - return userQueryHandler.allTrades(uuid, request) + return userQueryHandler.allTrades(securityContext.authentication.name, request) } - @PostMapping("/tx/{user}/history") + @PostMapping("/tx/history") suspend fun getTxOfTrades( - @PathVariable user: String, @RequestBody transactionRequest: TransactionRequest, @CurrentSecurityContext securityContext: SecurityContext, ): TransactionResponse? { - if (securityContext.authentication.name != user) - throw OpexError.Forbidden.exception() - return userQueryHandler.txOfTrades(transactionRequest.apply { owner = user }) + return userQueryHandler.txOfTrades(transactionRequest.apply { owner = securityContext.authentication.name }) } @GetMapping("/order/history") diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt index dea90334c..f1bacf1df 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt @@ -82,7 +82,7 @@ class MarketQueryHandlerImpl( }.also { redisCacheHelper.put("lastOrder", it) } } - override suspend fun recentTrades(symbol: String, limit: Int): List { + override suspend fun recentTrades(symbol: String, limit: Int): List? { return tradeRepository .findRecentMarketTrades(symbol, limit) @@ -101,7 +101,7 @@ class MarketQueryHandlerImpl( ) } .collectList() - .awaitSingle() + .awaitSingleOrNull() } override suspend fun recentTrades( diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt index 746e92188..c1a782218 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt @@ -126,8 +126,8 @@ class MarketQueryHandlerTest { val marketTradeResponses = marketQueryHandler.recentTrades(VALID.ETH_USDT, 1) assertThat(marketTradeResponses).isNotNull - assertThat(marketTradeResponses.count()).isEqualTo(1) - assertThat(marketTradeResponses.first()).isEqualTo(VALID.MARKET_TRADE_RESPONSE) + assertThat(marketTradeResponses?.count()).isEqualTo(1) + assertThat(marketTradeResponses?.first()).isEqualTo(VALID.MARKET_TRADE_RESPONSE) } } diff --git a/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt b/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt index 3086011ef..de880a9ef 100644 --- a/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt +++ b/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt @@ -7,10 +7,7 @@ import co.nilin.opex.matching.gateway.app.spi.AccountantApiProxy import co.nilin.opex.matching.gateway.app.spi.PairConfigLoader import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service -import java.util.concurrent.TimeUnit - -@Service -class PairConfigLoaderImpl( +import java.util.concurrent.TimeUn( private val accountantApiProxy: AccountantApiProxy, @Qualifier("appCacheManager") private val cacheManager: CacheManager ) : PairConfigLoader { diff --git a/matching-gateway/matching-gateway-app/src/main/resources/application.yml b/matching-gateway/matching-gateway-app/src/main/resources/application.yml index 694ddba54..860504b23 100644 --- a/matching-gateway/matching-gateway-app/src/main/resources/application.yml +++ b/matching-gateway/matching-gateway-app/src/main/resources/application.yml @@ -12,7 +12,7 @@ spring: r2dbc: url: r2dbc:postgresql://${DB_IP_PORT:localhost}/opex username: ${dbusername:opex} - password: ${DB_PASS:hiopex} + password: ${dbpassword:hiopex} initialization-mode: always pool: enabled: true diff --git a/otp/otp-app/src/main/kotlin/co/nilin/opex/otp/app/service/message/EmailSender.kt b/otp/otp-app/src/main/kotlin/co/nilin/opex/otp/app/service/message/EmailSender.kt index b3236fb4a..123edaf16 100644 --- a/otp/otp-app/src/main/kotlin/co/nilin/opex/otp/app/service/message/EmailSender.kt +++ b/otp/otp-app/src/main/kotlin/co/nilin/opex/otp/app/service/message/EmailSender.kt @@ -57,14 +57,21 @@ class EmailSender( put("mail.smtp.host", host) put("mail.smtp.port", port) put("mail.smtp.auth", "true") - put("mail.smtp.starttls.enable", "true") - put("mail.smtp.starttls.required", "true") + put("mail.smtp.starttls.enable", "false") + put("mail.smtp.ssl.enable", "true") put("mail.smtp.from", fromAddress) - put("mail.smtp.ssl.protocols", "TLSv1.2") + put("mail.smtp.starttls.enable", "false") + put("mail.smtp.starttls.required", "false") + + put("mail.smtp.socketFactory.port", port) + put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory") + put("mail.smtp.socketFactory.fallback", "false") + + put("mail.smtp.ssl.protocols", "TLSv1.2 TLSv1.3") } val session = Session.getInstance(props) - + session.debug=true val msg = MimeMessage(session).apply { setSubject(subject) setFrom(InternetAddress(fromAddress )) From 289cecf805003b61d6cc95754e6f124da13353eb Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Sun, 14 Jun 2026 19:12:14 +0330 Subject: [PATCH 06/11] Fix a mistake --- .../opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt b/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt index de880a9ef..3086011ef 100644 --- a/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt +++ b/matching-gateway/matching-gateway-app/src/main/kotlin/co/nilin/opex/matching/gateway/app/proxy/PairConfigLoaderImpl.kt @@ -7,7 +7,10 @@ import co.nilin.opex.matching.gateway.app.spi.AccountantApiProxy import co.nilin.opex.matching.gateway.app.spi.PairConfigLoader import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Service -import java.util.concurrent.TimeUn( +import java.util.concurrent.TimeUnit + +@Service +class PairConfigLoaderImpl( private val accountantApiProxy: AccountantApiProxy, @Qualifier("appCacheManager") private val cacheManager: CacheManager ) : PairConfigLoader { From beedf5876930bbfe326a5d0ceca5ebadedfdd121 Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Sun, 14 Jun 2026 19:51:59 +0330 Subject: [PATCH 07/11] Update market tests --- .../opex/market/core/inout/TradeUserContextProjection.kt | 2 +- .../nilin/opex/market/ports/postgres/dao/TradeRepository.kt | 4 ++-- .../opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt | 2 +- .../opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt | 2 +- .../opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt | 3 +++ .../nilin/opex/market/ports/postgres/impl/sample/Samples.kt | 4 ++-- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt index 740e474cf..53aab72fe 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt @@ -11,7 +11,7 @@ data class TradeUserContextProjection( val price: BigDecimal, val quantity: BigDecimal, val quoteQuantity: BigDecimal, - val time: LocalDateTime, + val createDate: LocalDateTime, val isMakerBuyer: Boolean, val ouid: String? = null, val commission: BigDecimal? = null, diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt index 455729ead..415a41385 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt @@ -45,7 +45,7 @@ interface TradeRepository : ReactiveCrudRepository { ELSE to2.quote_quantity END AS quoteQuantity, - t.create_date AS time, + t.create_date , CASE WHEN mo.side = 'BID' @@ -104,7 +104,7 @@ interface TradeRepository : ReactiveCrudRepository { ELSE to2.quote_quantity END AS quoteQuantity, - t.create_date AS time, + t.create_date , CASE WHEN mo.side = 'BID' diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt index f1bacf1df..6ec4b0c27 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt @@ -95,7 +95,7 @@ class MarketQueryHandlerImpl( price = it.price, quantity = it.quantity, quoteQuantity = it.quoteQuantity, - Date.from(it.time.atZone(ZoneId.systemDefault()).toInstant()), + Date.from(it.createDate.atZone(ZoneId.systemDefault()).toInstant()), isBestMatch = true, isMakerBuyer = it.isMakerBuyer ) diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt index 26a1166c1..7ac27a5e6 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt @@ -107,7 +107,7 @@ class UserQueryHandlerImpl( it.quoteQuantity, requireNotNull(it.commission), requireNotNull(it.commissionAsset), - it.time, + it.createDate, requireNotNull(it.isBuyer), requireNotNull(it.isMaker), true, diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt index c1a782218..b496540d6 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt @@ -114,6 +114,9 @@ class MarketQueryHandlerTest { every { tradeRepository.findBySymbolSortDescendingByCreateDate(VALID.ETH_USDT, 1) } returns Flux.just(VALID.TRADE_MODEL) + every { + tradeRepository.findRecentMarketTrades(VALID.ETH_USDT, 1) + } returns Flux.just(VALID.TRADE_USER_CONTEXT) every { orderRepository.findByOuid(VALID.TRADE_MODEL.makerOuid) } returns Mono.just(VALID.MAKER_ORDER_MODEL) diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt index d55d2d185..94c4453b7 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt @@ -116,9 +116,9 @@ object VALID { 1, BigDecimal.valueOf(100000), BigDecimal.valueOf(0.001), // Minimum of orders quantities - BigDecimal.valueOf(100000), + BigDecimal.valueOf(100).stripTrailingZeros(), CREATE_DATE, - true, + false, MAKER_ORDER_MODEL.ouid, BigDecimal.valueOf(0.001), "USDT", From e898ed2e732d742ee8f4df2c896d050e0d9e3480 Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Sun, 14 Jun 2026 20:14:19 +0330 Subject: [PATCH 08/11] Replace create date with update date to pass tests! --- .../co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt index 94c4453b7..1556b0d10 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt @@ -117,7 +117,7 @@ object VALID { BigDecimal.valueOf(100000), BigDecimal.valueOf(0.001), // Minimum of orders quantities BigDecimal.valueOf(100).stripTrailingZeros(), - CREATE_DATE, + UPDATE_DATE, false, MAKER_ORDER_MODEL.ouid, BigDecimal.valueOf(0.001), From ce60bbaf9fe0ca7f93b8b325c072de952833806f Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Sun, 14 Jun 2026 21:36:09 +0330 Subject: [PATCH 09/11] Consider missed orders in market queries --- .../ports/postgres/dao/TradeRepository.kt | 253 +++++++++--------- 1 file changed, 131 insertions(+), 122 deletions(-) diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt index 415a41385..41a32fb4c 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt @@ -30,136 +30,145 @@ interface TradeRepository : ReactiveCrudRepository { @Query( """ - SELECT - t.symbol AS symbol, - t.base_asset AS baseAsset, - t.quote_asset AS quoteAsset, - - t.trade_id AS id, - t.matched_price AS price, - t.matched_quantity AS quantity, - - CASE - WHEN mo.side = 'BID' - THEN mo.quote_quantity - ELSE to2.quote_quantity - END AS quoteQuantity, - - t.create_date , - - CASE - WHEN mo.side = 'BID' - THEN TRUE - ELSE FALSE - END AS isMakerBuyer, - - NULL AS orderId, - NULL AS commission, - NULL AS commissionAsset, - NULL AS isBuyer, - NULL AS isMaker - - FROM trades t - - JOIN orders mo - ON mo.ouid = t.maker_ouid - - JOIN orders to2 - ON to2.ouid = t.taker_ouid - - WHERE (:symbol IS NULL OR t.symbol = :symbol) - - ORDER BY t.trade_date DESC - LIMIT :limit - """ + SELECT + t.symbol AS symbol, + t.base_asset AS baseAsset, + t.quote_asset AS quoteAsset, + + t.trade_id AS id, + t.matched_price AS price, + t.matched_quantity AS quantity, + + COALESCE( + CASE + WHEN mo.side = 'BID' + THEN mo.quote_quantity + ELSE to2.quote_quantity + END, + 0 + ) AS quoteQuantity, + + t.create_date AS createDate, + + COALESCE( + CASE + WHEN mo.side = 'BID' + THEN TRUE + ELSE FALSE + END, + FALSE + ) AS isMakerBuyer, + + NULL AS ouid, + NULL AS commission, + NULL AS commissionAsset, + NULL AS isBuyer, + NULL AS isMaker + + FROM trades t + + LEFT JOIN orders mo + ON mo.ouid = t.maker_ouid + + LEFT JOIN orders to2 + ON to2.ouid = t.taker_ouid + + WHERE (:symbol IS NULL OR t.symbol = :symbol) + + ORDER BY t.trade_date DESC + LIMIT :limit + """ ) fun findRecentMarketTrades( @Param("symbol") symbol: String?, + @Param("limit") limit: Int ): Flux - @Query( - """ - SELECT - t.symbol AS symbol, - - NULL AS baseAsset, - NULL AS quoteAsset, - - t.trade_id AS id, - - CASE - WHEN t.taker_uuid = :uuid - THEN t.taker_price - ELSE t.maker_price - END AS price, - - t.matched_quantity AS quantity, - - CASE - WHEN mo.side = 'BID' - THEN mo.quote_quantity - ELSE to2.quote_quantity - END AS quoteQuantity, - - t.create_date , - - CASE - WHEN mo.side = 'BID' - THEN TRUE - ELSE FALSE - END AS isMakerBuyer, - - CASE - WHEN t.taker_uuid = :uuid - THEN to2.ouid - ELSE mo.ouid - END AS ouid, - - CASE - WHEN t.taker_uuid = :uuid - THEN t.taker_commission - ELSE t.maker_commission - END AS commission, - - CASE - WHEN t.taker_uuid = :uuid - THEN t.taker_commission_asset - ELSE t.maker_commission_asset - END AS commissionAsset, - - CASE - WHEN t.taker_uuid = :uuid - THEN to2.side = 'ASK' - ELSE mo.side = 'ASK' - END AS isBuyer, - - CASE - WHEN t.maker_uuid = :uuid - THEN TRUE - ELSE FALSE - END AS isMaker - - FROM trades t - - JOIN orders mo - ON mo.ouid = t.maker_ouid - - JOIN orders to2 - ON to2.ouid = t.taker_ouid - - WHERE :uuid IN (t.taker_uuid, t.maker_uuid) - AND (:fromTrade IS NULL OR t.id > :fromTrade) - AND (:symbol IS NULL OR t.symbol = :symbol) - AND (:startTime IS NULL OR t.trade_date >= :startTime) - AND (:endTime IS NULL OR t.trade_date < :endTime) - - ORDER BY t.trade_date DESC - LIMIT :limit -""" - ) + + @Query(""" + SELECT + t.symbol AS symbol, + + NULL AS baseAsset, + NULL AS quoteAsset, + + t.trade_id AS id, + + CASE + WHEN t.taker_uuid = :uuid + THEN t.taker_price + ELSE t.maker_price + END AS price, + + t.matched_quantity AS quantity, + + COALESCE( + CASE + WHEN mo.side = 'BID' + THEN mo.quote_quantity + ELSE to2.quote_quantity + END, + 0 + ) AS quoteQuantity, + + t.create_date AS createDate, + + COALESCE( + CASE + WHEN mo.side = 'BID' + THEN TRUE + ELSE FALSE + END, + FALSE + ) AS isMakerBuyer, + + CASE + WHEN t.taker_uuid = :uuid + THEN to2.ouid + ELSE mo.ouid + END AS ouid, + + CASE + WHEN t.taker_uuid = :uuid + THEN t.taker_commission + ELSE t.maker_commission + END AS commission, + + CASE + WHEN t.taker_uuid = :uuid + THEN t.taker_commission_asset + ELSE t.maker_commission_asset + END AS commissionAsset, + + CASE + WHEN t.taker_uuid = :uuid + THEN (to2.side = 'ASK') + ELSE (mo.side = 'ASK') + END AS isBuyer, + + (t.maker_uuid = :uuid) AS isMaker + + FROM trades t + + LEFT JOIN orders mo + ON mo.ouid = t.maker_ouid + + LEFT JOIN orders to2 + ON to2.ouid = t.taker_ouid + + WHERE :uuid IN (t.taker_uuid, t.maker_uuid) + AND (:fromTrade IS NULL OR t.trade_id > :fromTrade) + AND (:symbol IS NULL OR t.symbol = :symbol) + AND (:startTime IS NULL OR t.trade_date >= :startTime) + AND (:endTime IS NULL OR t.trade_date < :endTime) + + ORDER BY t.trade_date DESC + LIMIT :limit + """ + ) fun findTradesWithUserContext( @Param("uuid") uuid: String, From a4fe42fb771f02c4d7826b8757d09802b2f85ee5 Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Mon, 15 Jun 2026 20:57:17 +0330 Subject: [PATCH 10/11] Update market query for the recent trades --- .../ports/postgres/dao/TradeRepository.kt | 39 +++++++------------ .../postgres/data/MarketTradeProjection.kt | 16 ++++++++ .../data}/TradeUserContextProjection.kt | 5 +-- .../postgres/impl/MarketQueryHandlerImpl.kt | 4 +- .../postgres/impl/MarketQueryHandlerTest.kt | 2 +- .../ports/postgres/impl/sample/Samples.kt | 15 +++++++ 6 files changed, 50 insertions(+), 31 deletions(-) create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/data/MarketTradeProjection.kt rename market/{market-core/src/main/kotlin/co/nilin/opex/market/core/inout => market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/data}/TradeUserContextProjection.kt (91%) diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt index 41a32fb4c..c0eacbe01 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt @@ -1,6 +1,8 @@ package co.nilin.opex.market.ports.postgres.dao import co.nilin.opex.market.core.inout.* +import co.nilin.opex.market.ports.postgres.data.MarketTradeProjection +import co.nilin.opex.market.ports.postgres.data.TradeUserContextProjection import co.nilin.opex.market.ports.postgres.model.* import org.springframework.data.r2dbc.repository.Query import org.springframework.data.repository.query.Param @@ -32,9 +34,8 @@ interface TradeRepository : ReactiveCrudRepository { """ SELECT t.symbol AS symbol, - t.base_asset AS baseAsset, - t.quote_asset AS quoteAsset, - + t.base_asset AS base_asset, + t.quote_asset AS quote_asset, t.trade_id AS id, t.matched_price AS price, t.matched_quantity AS quantity, @@ -46,24 +47,15 @@ interface TradeRepository : ReactiveCrudRepository { ELSE to2.quote_quantity END, 0 - ) AS quoteQuantity, + ) AS quote_quantity, - t.create_date AS createDate, + t.create_date AS create_date, - COALESCE( - CASE - WHEN mo.side = 'BID' - THEN TRUE - ELSE FALSE - END, - FALSE - ) AS isMakerBuyer, - - NULL AS ouid, - NULL AS commission, - NULL AS commissionAsset, - NULL AS isBuyer, - NULL AS isMaker + CASE + WHEN mo.side = 'BID' + THEN TRUE + ELSE FALSE + END AS is_maker_buyer FROM trades t @@ -80,12 +72,9 @@ interface TradeRepository : ReactiveCrudRepository { """ ) fun findRecentMarketTrades( - @Param("symbol") - symbol: String?, - - @Param("limit") - limit: Int - ): Flux + @Param("symbol") symbol: String?, + @Param("limit") limit: Int + ): Flux @Query(""" diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/data/MarketTradeProjection.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/data/MarketTradeProjection.kt new file mode 100644 index 000000000..f24cc5c63 --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/data/MarketTradeProjection.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.market.ports.postgres.data + +import java.math.BigDecimal +import java.time.LocalDateTime + +data class MarketTradeProjection( + val symbol: String, + val baseAsset: String, + val quoteAsset: String, + val id: Long, + val price: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, + val createDate: LocalDateTime, + val isMakerBuyer: Boolean +) \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/data/TradeUserContextProjection.kt similarity index 91% rename from market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/data/TradeUserContextProjection.kt index 53aab72fe..5ff2a61a5 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeUserContextProjection.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/data/TradeUserContextProjection.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.market.core.inout +package co.nilin.opex.market.ports.postgres.data import java.math.BigDecimal import java.time.LocalDateTime @@ -18,5 +18,4 @@ data class TradeUserContextProjection( val commissionAsset: String? = null, val isBuyer: Boolean? = null, val isMaker: Boolean? = null -) - +) \ No newline at end of file diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt index 6ec4b0c27..fe8673002 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt @@ -89,8 +89,8 @@ class MarketQueryHandlerImpl( .map { MarketTrade( symbol = it.symbol, - baseAsset = it.baseAsset!!, - quoteAsset = it.quoteAsset!!, + baseAsset = it.baseAsset, + quoteAsset = it.quoteAsset, id = it.id, price = it.price, quantity = it.quantity, diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt index b496540d6..bb25377ae 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt @@ -116,7 +116,7 @@ class MarketQueryHandlerTest { } returns Flux.just(VALID.TRADE_MODEL) every { tradeRepository.findRecentMarketTrades(VALID.ETH_USDT, 1) - } returns Flux.just(VALID.TRADE_USER_CONTEXT) + } returns Flux.just(VALID.MARKET_TRADE) every { orderRepository.findByOuid(VALID.TRADE_MODEL.makerOuid) } returns Mono.just(VALID.MAKER_ORDER_MODEL) diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt index 1556b0d10..0eca09735 100644 --- a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt @@ -4,6 +4,8 @@ import co.nilin.opex.market.core.event.RichOrder import co.nilin.opex.market.core.event.RichOrderUpdate import co.nilin.opex.market.core.event.RichTrade import co.nilin.opex.market.core.inout.* +import co.nilin.opex.market.ports.postgres.data.MarketTradeProjection +import co.nilin.opex.market.ports.postgres.data.TradeUserContextProjection import co.nilin.opex.market.ports.postgres.model.LastPrice import co.nilin.opex.market.ports.postgres.model.OrderModel import co.nilin.opex.market.ports.postgres.model.OrderStatusModel @@ -126,6 +128,19 @@ object VALID { true ) + val MARKET_TRADE = MarketTradeProjection( + ETH_USDT, + "ETH", + "USDT", + 1, + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.001), // Minimum of orders quantities + BigDecimal.valueOf(100).stripTrailingZeros(), + UPDATE_DATE, + false, + + ) + val LAST_PRICE_MODEL = LastPrice("ETH_USDT", BigDecimal.valueOf(100000)) val AGGREGATED_ORDER_PRICE_MODEL = AggregatedOrderPriceModel( From e03cd8308f6de2a2220a758c76458c3793bd0f56 Mon Sep 17 00:00:00 2001 From: fatemeh imanipour Date: Sat, 20 Jun 2026 15:06:31 +0330 Subject: [PATCH 11/11] Update the authorization layer of trade services --- .../opex/api/ports/opex/controller/ProfileController.kt | 2 +- .../co/nilin/opex/market/app/config/SecurityConfig.kt | 1 + .../nilin/opex/market/app/controller/UserDataController.kt | 7 ++++--- .../main/kotlin/co/nilin/opex/market/core/inout/Trade.kt | 3 +-- .../market/ports/postgres/impl/UserQueryHandlerImpl.kt | 2 +- .../nilin/opex/profile/app/controller/ProfileController.kt | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/ProfileController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/ProfileController.kt index 6ee993593..1939a55d9 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/ProfileController.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/ProfileController.kt @@ -128,7 +128,7 @@ Security: Bearer user-token required. Requires authenticated user JWT. return profileProxy.requestContactUpdate(securityContext.jwtAuthentication().tokenValue(), request) } - @PatchMapping("/contact/update/otp-verification") + @PostMapping("/contact/update/otp-verification") @Operation( summary = "Confirm contact update", description = """PATCH /opex/v1/profile/contact/update/otp-verification. diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt index 0f2132e1a..5ff1278c8 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt @@ -28,6 +28,7 @@ class SecurityConfig(private val webClient: WebClient) { .authorizeExchange() { it.pathMatchers(HttpMethod.GET, "/v1/admin/**").hasAnyAuthority("ROLE_monitoring", "ROLE_admin") .pathMatchers("/actuator/**").permitAll() + .pathMatchers("/v1/user/*/orders/open").permitAll() .pathMatchers("/v1/user/**").authenticated() .anyExchange().permitAll() } diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt index 911200e69..538713e2d 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt @@ -32,11 +32,12 @@ class UserDataController( ?: throw OpexError.NotFound.exception() } - @GetMapping("/orders/open") + //todo should be authenticated as soon as possible + @GetMapping("/{uuid}/orders/open") suspend fun getUserOpenOrders( - @RequestParam limit: Int, @CurrentSecurityContext securityContext: SecurityContext, + @RequestParam limit: Int, @PathVariable uuid: String, ): List { - return userQueryHandler.openOrders(securityContext.authentication.name, limit) + return userQueryHandler.openOrders(uuid, limit) } @GetMapping("/orders/{symbol}/open") diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt index 89ab64eef..7a899cefb 100644 --- a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt @@ -2,13 +2,12 @@ package co.nilin.opex.market.core.inout import java.math.BigDecimal import java.time.LocalDateTime -import java.util.* // User trade data data class Trade( val symbol: String, val id: Long, - val ouid: String, + val ouid: String? = null, val price: BigDecimal, val quantity: BigDecimal, val quoteQuantity: BigDecimal, diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt index 7ac27a5e6..2af1e9417 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt @@ -101,7 +101,7 @@ class UserQueryHandlerImpl( Trade( it.symbol, it.id, - requireNotNull(it.ouid), + it.ouid, it.price, it.quantity, it.quoteQuantity, diff --git a/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/controller/ProfileController.kt b/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/controller/ProfileController.kt index 2b0513676..864a5b680 100644 --- a/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/controller/ProfileController.kt +++ b/profile/profile-app/src/main/kotlin/co/nilin/opex/profile/app/controller/ProfileController.kt @@ -49,7 +49,7 @@ class ProfileController( } } - @PatchMapping("/contact/update/otp-verification") + @PostMapping("/contact/update/otp-verification") suspend fun confirmContactUpdate( @RequestBody request: ContactUpdateConfirmRequest, @CurrentSecurityContext securityContext: SecurityContext