diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UpdateUserConfigRequest.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UpdateUserConfigRequest.kt new file mode 100644 index 000000000..a0f57f949 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UpdateUserConfigRequest.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.api.core.inout + +import co.nilin.opex.common.data.CalenderType +import co.nilin.opex.common.data.Theme +import co.nilin.opex.common.data.UserLanguage + +data class UpdateUserConfigRequest( + val theme: Theme?, + val language: UserLanguage?, + val calender: CalenderType? +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserLevelConfig.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserLevelConfig.kt new file mode 100644 index 000000000..b6e05b8af --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserLevelConfig.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.api.core.inout + +data class UserLevelConfig( + val userLevel: String, + val language: String, + val name: String, + val description: String? = null, + val permissions: Permissions +) + +data class Permissions( + val onChainDepositAllowed: Boolean? = false, + val offChainDepositAllowed: Boolean? = false, + val onChainWithdrawAllowed: Boolean? = false, + val offChainWithdrawAllowed: Boolean? = false +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserWebConfig.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserWebConfig.kt new file mode 100644 index 000000000..08b920bbc --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserWebConfig.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.api.core.inout + +import co.nilin.opex.common.data.CalenderType +import co.nilin.opex.common.data.Theme +import co.nilin.opex.common.data.UserLanguage + +data class UserWebConfig( + var theme: Theme, + var language: UserLanguage, + var calender: CalenderType +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/ConfigProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/ConfigProxy.kt index a5518654a..af2969e65 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/ConfigProxy.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/ConfigProxy.kt @@ -1,11 +1,21 @@ package co.nilin.opex.api.core.spi +import co.nilin.opex.api.core.inout.UpdateUserConfigRequest import co.nilin.opex.api.core.inout.UpdateWebConfigRequest +import co.nilin.opex.api.core.inout.UserLevelConfig +import co.nilin.opex.api.core.inout.UserWebConfig import co.nilin.opex.common.data.WebConfig interface ConfigProxy { - suspend fun getWebConfig(): WebConfig - suspend fun updateWebConfig(token: String, request: UpdateWebConfigRequest) : WebConfig + suspend fun updateWebConfig(token: String, request: UpdateWebConfigRequest): WebConfig + suspend fun getUserLevelConfig(): List + suspend fun updateUserLevelConfig(token: String, userLevelConfig: UserLevelConfig): UserLevelConfig + suspend fun deleteUserLevelConfig(token: String, userLevel: String, language: String) + suspend fun getUserConfig(token: String): UserWebConfig + suspend fun updateUserConfig(token: String, request: UpdateUserConfigRequest): UserWebConfig + suspend fun getUserFavoritePair(token: String): Set + suspend fun addUserFavoritePair(token: String, pairs: Set): Set + suspend fun removeUserFavoritePair(token: String, pairs: Set): Set } diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt index 4e60277b4..f9bd55dc6 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt @@ -52,6 +52,8 @@ class SecurityConfig( .pathMatchers("/opex/v1/admin/transactions/**").hasAnyAuthority("ROLE_monitoring", "ROLE_admin") .pathMatchers("/opex/v1/storage/**").permitAll() .pathMatchers("/opex/v1/web/config/**").permitAll() + .pathMatchers("/opex/v1/user-level/config/**").permitAll() + .pathMatchers("/opex/v1/user/config/**").authenticated() .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") diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/ConfigController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/ConfigController.kt index dd8576efd..1c32859a9 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/ConfigController.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/ConfigController.kt @@ -1,6 +1,9 @@ package co.nilin.opex.api.ports.opex.controller +import co.nilin.opex.api.core.inout.UpdateUserConfigRequest import co.nilin.opex.api.core.inout.UpdateWebConfigRequest +import co.nilin.opex.api.core.inout.UserLevelConfig +import co.nilin.opex.api.core.inout.UserWebConfig 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 @@ -26,4 +29,59 @@ class ConfigController(private val configProxy: ConfigProxy) { return configProxy.updateWebConfig(securityContext.jwtAuthentication().tokenValue(), request) } + @GetMapping("/user-level/config") + suspend fun getUserLevelConfig(): List { + return configProxy.getUserLevelConfig() + } + + @PutMapping("/admin/user-level/config") + suspend fun updateUserLevelConfig( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestBody userLevelConfig: UserLevelConfig + ): UserLevelConfig { + return configProxy.updateUserLevelConfig(securityContext.jwtAuthentication().tokenValue(), userLevelConfig) + } + + @DeleteMapping("/admin/user-level/config/{userLevel}/{language}") + suspend fun updateUserLevelConfig( + @CurrentSecurityContext securityContext: SecurityContext, + @PathVariable userLevel: String, + @PathVariable language: String + ) { + configProxy.deleteUserLevelConfig(securityContext.jwtAuthentication().tokenValue(), userLevel, language) + } + + @GetMapping("/user/config") + suspend fun getUserConfig(@CurrentSecurityContext securityContext: SecurityContext): UserWebConfig { + return configProxy.getUserConfig(securityContext.jwtAuthentication().tokenValue()) + } + + @PutMapping("/user/config") + suspend fun updateConfig( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestBody request: UpdateUserConfigRequest + ): UserWebConfig { + return configProxy.updateUserConfig(securityContext.jwtAuthentication().tokenValue(), request) + } + + @GetMapping("/user/config/pair") + suspend fun getUserFavoritePair(@CurrentSecurityContext securityContext: SecurityContext): Set { + return configProxy.getUserFavoritePair(securityContext.jwtAuthentication().tokenValue()) + } + + @PostMapping("/user/config/pair") + suspend fun addUserFavoritePair( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestBody pairs: Set + ): Set { + return configProxy.addUserFavoritePair(securityContext.jwtAuthentication().tokenValue(), pairs) + } + + @DeleteMapping("/user/config/pair") + suspend fun removeUserFavoritePair( + @CurrentSecurityContext securityContext: SecurityContext, + @RequestBody pairs: Set + ): Set { + return configProxy.removeUserFavoritePair(securityContext.jwtAuthentication().tokenValue(), pairs) + } } \ 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/ConfigProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ConfigProxyImpl.kt index 8f6d6d265..a6a15f512 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ConfigProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/ConfigProxyImpl.kt @@ -1,19 +1,25 @@ package co.nilin.opex.api.ports.proxy.impl +import co.nilin.opex.api.core.inout.UpdateUserConfigRequest import co.nilin.opex.api.core.inout.UpdateWebConfigRequest +import co.nilin.opex.api.core.inout.UserLevelConfig +import co.nilin.opex.api.core.inout.UserWebConfig import co.nilin.opex.api.core.spi.ConfigProxy import co.nilin.opex.common.OpexError +import co.nilin.opex.common.data.UserLanguage import co.nilin.opex.common.data.WebConfig +import co.nilin.opex.common.utils.LanguageUtils.getUserLanguage import co.nilin.opex.common.utils.LoggerDelegate import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod 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 org.springframework.web.reactive.function.client.* import reactor.core.publisher.Mono @Component @@ -38,7 +44,7 @@ class ConfigProxyImpl(@Qualifier("generalWebClient") private val webClient: WebC token: String, request: UpdateWebConfigRequest ): WebConfig { - return webClient.put() + return webClient.post() .uri("$baseUrl/web/v1") .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.AUTHORIZATION, "Bearer $token") @@ -48,4 +54,119 @@ class ConfigProxyImpl(@Qualifier("generalWebClient") private val webClient: WebC .bodyToMono() .awaitFirstOrElse { throw OpexError.BadRequest.exception() } } + + override suspend fun getUserLevelConfig(): List { + val lang = UserLanguage.safeValueOf(getUserLanguage().awaitSingleOrNull()).toString() + return webClient.get() + .uri("$baseUrl/user-level/v1") { + it.queryParam("language", lang) + it.build() + } + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitSingle() + } + + override suspend fun updateUserLevelConfig( + token: String, + userLevelConfig: UserLevelConfig + ): UserLevelConfig { + return webClient.post() + .uri("$baseUrl/user-level/v1") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .body(Mono.just(userLevelConfig)) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitFirstOrElse { throw OpexError.BadRequest.exception() } + } + + override suspend fun deleteUserLevelConfig( + token: String, + userLevel: String, + language: String + ) { + webClient.delete() + .uri("$baseUrl/user-level/v1/${userLevel}/${language}") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .retrieve() + .onStatus({ it.isError }) { response -> + response.createException() + } + .awaitBodilessEntity() + } + + override suspend fun getUserConfig(token: String): UserWebConfig { + return webClient.get() + .uri("$baseUrl/user/v1") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitFirstOrElse { throw OpexError.BadRequest.exception("Failed to get user config") } + } + + override suspend fun updateUserConfig( + token: String, + request: UpdateUserConfigRequest + ): UserWebConfig { + return webClient.post() + .uri("$baseUrl/user/v1") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .body(Mono.just(request)) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitFirstOrElse { throw OpexError.BadRequest.exception() } + } + + override suspend fun getUserFavoritePair(token: String): Set { + return webClient.get() + .uri("$baseUrl/user/v1/pair") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono>() + .awaitFirstOrElse { throw OpexError.BadRequest.exception("Failed to get user favorite pair") } + } + + override suspend fun addUserFavoritePair( + token: String, + pairs: Set + ): Set { + return webClient.post() + .uri("$baseUrl/user/v1/pair") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .body(Mono.just(pairs)) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono>() + .awaitFirstOrElse { throw OpexError.BadRequest.exception() } + } + + override suspend fun removeUserFavoritePair( + token: String, + pairs: Set + ): Set { + return webClient.method(HttpMethod.DELETE) + .uri("$baseUrl/user/v1/pair") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .bodyValue(pairs) + .retrieve() + .onStatus({ it.isError }) { it.createException() } + .bodyToMono>() + .awaitFirstOrElse { throw OpexError.BadRequest.exception() } + } } +