Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package co.nilin.opex.api.app.config

import co.nilin.opex.common.data.UserLanguage
import co.nilin.opex.common.utils.LanguageUtils.getDefaultUserLanguage
import io.netty.channel.ChannelOption
import io.netty.handler.logging.LogLevel
import org.springframework.cloud.client.ServiceInstance
Expand Down Expand Up @@ -76,7 +77,7 @@ class WebClientConfig(private val logbook: Logbook) {

private fun languageFilter() = ExchangeFilterFunction { request, next ->
Mono.deferContextual { ctx ->
val lang = ctx.getOrDefault("lang", UserLanguage.EN.toString())
val lang = ctx.getOrDefault("lang", getDefaultUserLanguage())
val mutatedRequest = ClientRequest.from(request)
.header("Accept-Language", lang)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class OpexFilterExceptionHandler(

if (ex is OpexException) {
return translator.translate(ex).flatMap { error ->
exchange.response.statusCode = HttpStatusCode.valueOf(error.status.value())
exchange.response.statusCode = HttpStatusCode.valueOf(error.status?.value() ?: 500)
exchange.response.headers.contentType = MediaType.APPLICATION_JSON

val bytes = objectMapper.writeValueAsBytes(error)
Expand Down
2 changes: 2 additions & 0 deletions api/api-app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ app:
url: http://opex-bc-gateway
storage:
url: http://storage
config:
url: http://opex-config
auth:
cert-url: http://keycloak:8080/realms/opex/protocol/openid-connect/certs
iss-url: ${TOKEN_ISSUER_URL:http://keycloak:8080/realms/opex}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.nilin.opex.api.core.inout

import co.nilin.opex.common.data.CalenderType
import co.nilin.opex.common.data.LanguageOption
import co.nilin.opex.common.data.Theme
import co.nilin.opex.common.data.UserLanguage

data class UpdateWebConfigRequest(
val logoUrl: String?,
val title: String?,
val description: String?,
var defaultLanguage: UserLanguage?,
var supportedLanguages: Map<UserLanguage, LanguageOption>?,
var supportedCalenders: List<CalenderType>?,
val defaultTheme: Theme?,
val supportedThemes: List<Theme>?,
val supportEmail: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.nilin.opex.api.core.spi

import co.nilin.opex.api.core.inout.UpdateWebConfigRequest
import co.nilin.opex.common.data.WebConfig

interface ConfigProxy {

suspend fun getWebConfig(): WebConfig
suspend fun updateWebConfig(token: String, request: UpdateWebConfigRequest) : WebConfig

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class SecurityConfig(
// Opex endpoints
.pathMatchers("/opex/v1/admin/transactions/**").hasAnyAuthority("ROLE_monitoring", "ROLE_admin")
.pathMatchers("/opex/v1/storage/**").permitAll()
.pathMatchers("/opex/v1/web/config/**").permitAll()
.pathMatchers("/opex/v1/admin/**").hasAuthority("ROLE_admin")
.pathMatchers("/opex/v1/deposit/**").hasAuthority("PERM_deposit:write")
.pathMatchers(HttpMethod.POST, "/opex/v1/order").hasAuthority("PERM_order:write")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package co.nilin.opex.api.ports.opex.controller

import co.nilin.opex.api.core.inout.UpdateWebConfigRequest
import co.nilin.opex.api.core.spi.ConfigProxy
import co.nilin.opex.api.ports.opex.util.jwtAuthentication
import co.nilin.opex.api.ports.opex.util.tokenValue
import co.nilin.opex.common.data.WebConfig
import org.springframework.security.core.annotation.CurrentSecurityContext
import org.springframework.security.core.context.SecurityContext
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/opex/v1")
class ConfigController(private val configProxy: ConfigProxy) {

@GetMapping("/web/config")
suspend fun getWebConfig(): WebConfig {
return configProxy.getWebConfig()
}

@PutMapping("/admin/web/config")
suspend fun updateWebConfig(
@CurrentSecurityContext securityContext: SecurityContext,
@RequestBody request: UpdateWebConfigRequest
): WebConfig {
return configProxy.updateWebConfig(securityContext.jwtAuthentication().tokenValue(), request)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class GatewayAdminController(
private val walletProxy: WalletProxy,
) {
@PostMapping("/{currencySymbol}/gateway")
suspend fun addCurrencyToGateway(
suspend fun addGatewayToCurrency(
@CurrentSecurityContext securityContext: SecurityContext,
@PathVariable("currencySymbol") currencySymbol: String,
@RequestBody body: CurrencyGatewayCommand,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package co.nilin.opex.api.ports.proxy.impl

import co.nilin.opex.api.core.inout.UpdateWebConfigRequest
import co.nilin.opex.api.core.spi.ConfigProxy
import co.nilin.opex.common.OpexError
import co.nilin.opex.common.data.WebConfig
import co.nilin.opex.common.utils.LoggerDelegate
import kotlinx.coroutines.reactive.awaitFirstOrElse
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
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.bodyToMono
import reactor.core.publisher.Mono

@Component
class ConfigProxyImpl(@Qualifier("generalWebClient") private val webClient: WebClient) : ConfigProxy {

private val logger by LoggerDelegate()

@Value("\${app.config.url}")
private lateinit var baseUrl: String

override suspend fun getWebConfig(): WebConfig {
return webClient.get()
.uri("$baseUrl/web/v1")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus({ t -> t.isError }, { it.createException() })
.bodyToMono<WebConfig>()
.awaitFirstOrElse { throw OpexError.BadRequest.exception("Failed to get web config") }
}

override suspend fun updateWebConfig(
token: String,
request: UpdateWebConfigRequest
): WebConfig {
return webClient.put()
.uri("$baseUrl/web/v1")
.accept(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "Bearer $token")
.body(Mono.just(request))
.retrieve()
.onStatus({ t -> t.isError }, { it.createException() })
.bodyToMono<WebConfig>()
.awaitFirstOrElse { throw OpexError.BadRequest.exception() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ interface CurrencyImplementationRepository : ReactiveCrudRepository<CurrencyOnCh
gatewayUuid: String? = null,
chain: String? = null,
implementationSymbol: String? = null,
lang: String? = UserLanguage.getDefault().toString()
lang: String? = UserLanguage.getDefaultLanguage()
): Flux<CurrencyOnChainGatewayView>?

fun deleteByGatewayUuid(uuid: String): Mono<Void>
Expand Down Expand Up @@ -101,7 +101,7 @@ interface CurrencyImplementationRepository : ReactiveCrudRepository<CurrencyOnCh
fun findByGatewayUuidAndCurrencySymbol(
gatewayUuid: String,
symbol: String,
lang: String? = UserLanguage.getDefault().toString()
lang: String? = UserLanguage.getDefaultLanguage()
): Mono<CurrencyOnChainGatewayView>?

@Query("select * from currency_on_chain_gateway where chain = :chain and is_token is false")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package co.nilin.opex.common.config

import co.nilin.opex.common.translation.CustomErrorTranslator
import co.nilin.opex.common.service.CustomErrorTranslator
import co.nilin.opex.utility.error.spi.ErrorTranslator
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.context.MessageSource
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import org.springframework.context.annotation.Scope
import org.springframework.context.support.ReloadableResourceBundleMessageSource
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.web.server.ServerWebExchange

@Configuration
class ErrorConfig {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package co.nilin.opex.common.data

enum class CalenderType {
JALALI, HIJRI, GREGORIAN
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package co.nilin.opex.common.data

enum class LanguageDirection {
RTL, LTR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package co.nilin.opex.common.data

data class LanguageOption(
val label: String,
val nativeLabel: String,
val direction: LanguageDirection,
val defaultCalender: CalenderType,
)
5 changes: 5 additions & 0 deletions common/src/main/kotlin/co/nilin/opex/common/data/Theme.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package co.nilin.opex.common.data

enum class Theme {
DARK, LIGHT, SYSTEM
}
16 changes: 8 additions & 8 deletions common/src/main/kotlin/co/nilin/opex/common/data/UserLanguage.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package co.nilin.opex.common.data

//After adjusting versions in various modules it should be moved to common project
import co.nilin.opex.common.service.GlobalWebConfigCache

enum class UserLanguage {
EN, FA;
EN, FA, AR, UZ;

companion object {

fun getDefaultLanguage(): String =
GlobalWebConfigCache.webConfig?.defaultLanguage?.name ?: EN.name

fun safeValueOf(lang: String?): UserLanguage {
return try {
if (lang.isNullOrBlank()) EN
if (lang.isNullOrBlank()) valueOf(getDefaultLanguage())
else valueOf(lang.uppercase())
} catch (e: IllegalArgumentException) {
EN
valueOf(getDefaultLanguage())
}
}

fun getDefault(): UserLanguage {
return EN
}
}
}
13 changes: 13 additions & 0 deletions common/src/main/kotlin/co/nilin/opex/common/data/WebConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package co.nilin.opex.common.data

data class WebConfig(
val logoUrl: String?,
val title: String?,
val description: String?,
var defaultLanguage: UserLanguage?,
var supportedLanguages: Map<UserLanguage, LanguageOption>?,
var supportedCalenders: List<CalenderType>?,
val defaultTheme: Theme?,
var supportedThemes: List<Theme>?,
val supportEmail: String?,
)
26 changes: 26 additions & 0 deletions common/src/main/kotlin/co/nilin/opex/common/proxy/ConfigClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package co.nilin.opex.common.proxy

import co.nilin.opex.common.config.CommonWebClient
import co.nilin.opex.common.data.WebConfig
import kotlinx.coroutines.reactive.awaitFirst
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.core.ParameterizedTypeReference
import org.springframework.stereotype.Component

inline fun <reified T : Any?> typeRef(): ParameterizedTypeReference<T> = object : ParameterizedTypeReference<T>() {}

@Component
class ConfigClient(
@Qualifier("CommonWebClient") private val webClient: CommonWebClient
) {

suspend fun getWebConfig(): WebConfig {
return webClient.delegate.get().uri("http://config:8080/web/v1") {
it.build()
}.retrieve()
.onStatus({ t -> t.isError }, { it.createException() })
.bodyToMono(typeRef<WebConfig>())
.log()
.awaitFirst()
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package co.nilin.opex.common.translation
package co.nilin.opex.common.proxy

import co.nilin.opex.common.config.CommonWebClient
import co.nilin.opex.common.data.MessageTranslation
Expand All @@ -9,11 +9,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.core.ParameterizedTypeReference
import org.springframework.stereotype.Component

inline fun <reified T : Any?> typeRef(): ParameterizedTypeReference<T> = object : ParameterizedTypeReference<T>() {}

@Component
@ConditionalOnProperty(name = ["app.custom-message.enabled"], havingValue = "true", matchIfMissing = false)
class ConfigClient(
class CustomMessageClient(
@Qualifier("CommonWebClient") private val webClient: CommonWebClient
) {
@Value("\${app.custom-message.base-url}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package co.nilin.opex.common.translation
package co.nilin.opex.common.service

import co.nilin.opex.common.utils.LanguageUtils.getUserLanguage
import co.nilin.opex.utility.error.data.DefaultExceptionResponse
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package co.nilin.opex.common.service

import co.nilin.opex.common.data.UserLanguage.EN
import co.nilin.opex.utility.interceptors.spi.UserLanguageConfig
import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Service

@Service
@Primary
class CustomUserLanguageConfig : UserLanguageConfig {
override fun getDefaultLanguage(): String =
runCatching {
GlobalWebConfigCache.webConfig?.defaultLanguage?.name
}.getOrNull() ?: EN.name
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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
import co.nilin.opex.common.proxy.CustomMessageClient
import kotlinx.coroutines.*
import org.slf4j.LoggerFactory
import org.springframework.context.MessageSource
Expand All @@ -13,8 +14,8 @@ import javax.annotation.PreDestroy

@Service
class TranslationCacheService(
private val configClient: ConfigClient?,
private val messageSource: MessageSource?
private val customMessageClient: CustomMessageClient?,
private val messageSource: MessageSource?
) {

private val cache: MutableMap<Pair<String, UserLanguage>, MessageTranslation> = ConcurrentHashMap()
Expand All @@ -28,8 +29,8 @@ class TranslationCacheService(
job = CoroutineScope(Dispatchers.IO).launch {
logger.info("Going to get messages which are updated after {}", lastUpdate)
while (isActive) {
if (configClient != null) {
val newMessages = configClient.getMessagesUpdatedAfter(cache?.let { lastUpdate })
if (customMessageClient != null) {
val newMessages = customMessageClient.getMessagesUpdatedAfter(cache?.let { lastUpdate })
newMessages?.forEach { msg ->
cache[Pair(msg.key, msg.language)] =
MessageTranslation(msg.key, msg.message, msg.language)
Expand Down
Loading
Loading