almost complete rework of ui up to module ui
This commit is contained in:
client
features
auth
client
common
client
content
binary
client
src
jsMain
kotlin
dev
inmo
postssystem
features
content
binary
client
src
commonMain
kotlin
dev
inmo
postssystem
features
content
client
text
client
src
jsMain
kotlin
dev
inmo
postssystem
features
content
text
files
client
roles
client
manager
common
src
commonMain
kotlin
dev
inmo
postssystem
features
roles
manager
common
users
client
gradle
publicators/simple/client
services/posts/client
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
services
jsMain
kotlin
dev
inmo
postssystem
services
posts
client
targets/telegram/loader/client/src/commonMain/kotlin/dev/inmo/postssystem/targets/telegram/loader/client
@ -2,6 +2,7 @@ plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
alias(libs.plugins.compose)
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
@ -11,8 +12,14 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":postssystem.features.common.client")
|
||||
api project(":postssystem.features.roles.client")
|
||||
api project(":postssystem.features.status.client")
|
||||
api project(":postssystem.features.auth.common")
|
||||
}
|
||||
}
|
||||
clientMain {
|
||||
dependencies {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedFeaturesModules.kt
Normal file
39
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedFeaturesModules.kt
Normal file
@ -0,0 +1,39 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import dev.inmo.micro_utils.common.Either
|
||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||
import dev.inmo.postssystem.features.auth.common.AuthKey
|
||||
import dev.inmo.postssystem.features.auth.common.AuthTokenInfo
|
||||
import dev.inmo.postssystem.features.common.common.AdditionalModules
|
||||
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
|
||||
import dev.inmo.postssystem.features.users.common.User
|
||||
import io.ktor.client.HttpClient
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
|
||||
fun createAuthorizedFeaturesDIModule(
|
||||
serverUrl: String,
|
||||
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
|
||||
onAuthKeyUpdated: suspend (AuthTokenInfo) -> Unit,
|
||||
onUserRetrieved: suspend (User?) -> Unit,
|
||||
onAuthKeyInvalidated: suspend () -> Unit
|
||||
): Module {
|
||||
return module {
|
||||
single(AuthorizedQualifiers.CredsQualifier) { initialAuthKey }
|
||||
single(AuthorizedQualifiers.ServerUrlQualifier) { serverUrl }
|
||||
|
||||
single (createdAtStart = true) {
|
||||
HttpClient {
|
||||
installClientAuthenticator(serverUrl, get(), get(AuthorizedQualifiers.CredsQualifier), onAuthKeyUpdated, onUserRetrieved, onAuthKeyInvalidated)
|
||||
}
|
||||
}
|
||||
single { UnifiedRequester(get(), get()) }
|
||||
single { StatusFeatureClient(get(AuthorizedQualifiers.ServerUrlQualifier), get()) }
|
||||
|
||||
AdditionalModules.Authorized.modules.forEach {
|
||||
with(it) {
|
||||
load()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedModuleLoader.kt
Normal file
15
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedModuleLoader.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import dev.inmo.postssystem.features.common.common.AdditionalModules
|
||||
import dev.inmo.postssystem.features.common.common.ModuleLoader
|
||||
import org.koin.core.module.Module
|
||||
|
||||
private val AuthorizedAdditionalModules = AdditionalModules()
|
||||
val AdditionalModules.Companion.Authorized: AdditionalModules
|
||||
get() = AuthorizedAdditionalModules
|
||||
|
||||
fun AuthorizedModuleLoader(loadingBlock: Module.() -> Unit): ModuleLoader.ByCallback {
|
||||
val newModuleLoader = ModuleLoader.ByCallback(loadingBlock)
|
||||
AdditionalModules.Authorized.addModule(newModuleLoader)
|
||||
return newModuleLoader
|
||||
}
|
8
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedQualifiers.kt
Normal file
8
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthorizedQualifiers.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import org.koin.core.qualifier.StringQualifier
|
||||
|
||||
object AuthorizedQualifiers {
|
||||
val CredsQualifier = StringQualifier("creds")
|
||||
val ServerUrlQualifier = StringQualifier("serverUrl")
|
||||
}
|
59
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/BehindAuthUIFSMHandler.kt
Normal file
59
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/BehindAuthUIFSMHandler.kt
Normal file
@ -0,0 +1,59 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import dev.inmo.micro_utils.fsm.common.CheckableHandlerHolder
|
||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
|
||||
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
|
||||
import dev.inmo.postssystem.features.auth.client.ui.AuthUIFSMState
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMHandler
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.parameter.ParametersHolder
|
||||
import org.koin.core.qualifier.Qualifier
|
||||
import org.koin.core.scope.Scope
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// Костыль, в JS на момент Пн дек 6 14:19:29 +06 2021 если использовать strictlyOn генерируются
|
||||
// некорректные безымянные классы (у них отсутствует метод handleState)
|
||||
class DefaultStateHandlerWrapper<T : UIFSMState>(
|
||||
private val klass: KClass<out UIFSMHandler<T>>,
|
||||
private val koin: Koin,
|
||||
private val stateKlass: KClass<T>,
|
||||
private val qualifier: Qualifier? = null,
|
||||
private val parameters: ((T) -> ParametersHolder)? = null
|
||||
) : CheckableHandlerHolder<UIFSMState, UIFSMState> {
|
||||
override suspend fun StatesMachine<in UIFSMState>.handleState(state: UIFSMState): UIFSMState? {
|
||||
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
|
||||
val state = state as T
|
||||
return runCatching {
|
||||
val authSettings = koin.get<AuthSettings>()
|
||||
authSettings.loadingJob.join()
|
||||
if (authSettings.authorizedDIModule.value == null) {
|
||||
error("Can't perform state $state: Auth module was not initialized")
|
||||
} else {
|
||||
koin.get<UIFSMHandler<T>>(klass, qualifier, parameters ?.let { { it(state) } }).run {
|
||||
handleState(state)
|
||||
}
|
||||
}
|
||||
}.getOrElse { e ->
|
||||
e.printStackTrace()
|
||||
AuthUIFSMState(state)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun checkHandleable(state: UIFSMState): Boolean = stateKlass.isInstance(state)
|
||||
}
|
||||
inline fun <reified T : UIFSMState> FSMBuilder<UIFSMState>.registerAfterAuthHandler(
|
||||
koin: Koin,
|
||||
klass: KClass<out UIFSMHandler<T>>,
|
||||
qualifier: Qualifier? = null,
|
||||
noinline parameters: ((T) -> ParametersHolder)? = null
|
||||
) = add(
|
||||
DefaultStateHandlerWrapper<T>(
|
||||
klass,
|
||||
koin,
|
||||
T::class,
|
||||
qualifier,
|
||||
parameters
|
||||
)
|
||||
)
|
27
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ModuleLoader.kt
Normal file
27
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ModuleLoader.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import dev.inmo.micro_utils.common.Optional
|
||||
import dev.inmo.micro_utils.common.optional
|
||||
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
|
||||
import dev.inmo.postssystem.features.auth.client.settings.DefaultAuthSettings
|
||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||
import dev.inmo.postssystem.features.common.common.*
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMExceptionHandler
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
||||
|
||||
val defaultModuleLoader = DefaultModuleLoader {
|
||||
single<AuthSettings> { DefaultAuthSettings(get(DefaultQualifiers.SettingsQualifier), get(), getKoin(), get()) }
|
||||
|
||||
singleWithRandomQualifier {
|
||||
UIFSMExceptionHandler { currentState, exception ->
|
||||
if (exception is AuthUnavailableException) {
|
||||
Optional.presented(AuthUIFSMState(currentState))
|
||||
} else {
|
||||
Optional.absent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
factory<AuthUIModel> { DefaultAuthUIModel(get(), get()) }
|
||||
factory { AuthUIViewModel(get()) }
|
||||
}
|
18
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/settings/AuthSettings.kt
Normal file
18
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/settings/AuthSettings.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package dev.inmo.postssystem.features.auth.client.settings
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.ui.AuthUIError
|
||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||
import dev.inmo.postssystem.features.roles.common.Role
|
||||
import dev.inmo.postssystem.features.users.common.User
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.koin.core.module.Module
|
||||
|
||||
interface AuthSettings {
|
||||
val authorizedDIModule: StateFlow<Module?>
|
||||
val user: StateFlow<User?>
|
||||
val userRoles: StateFlow<List<Role>>
|
||||
val loadingJob: Job
|
||||
|
||||
suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError?
|
||||
}
|
118
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/settings/DefaultAuthSettings.kt
Normal file
118
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/settings/DefaultAuthSettings.kt
Normal file
@ -0,0 +1,118 @@
|
||||
package dev.inmo.postssystem.features.auth.client.settings
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
|
||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||
import dev.inmo.postssystem.features.auth.common.*
|
||||
import dev.inmo.postssystem.features.roles.common.Role
|
||||
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
||||
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
|
||||
import dev.inmo.postssystem.features.users.common.User
|
||||
import dev.inmo.micro_utils.common.Either
|
||||
import dev.inmo.micro_utils.common.either
|
||||
import dev.inmo.micro_utils.coroutines.plus
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.postssystem.features.auth.client.createAuthorizedFeaturesDIModule
|
||||
import dev.inmo.postssystem.features.common.common.DBDropper
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.module.Module
|
||||
|
||||
data class DefaultAuthSettings(
|
||||
private val repo: KeyValueRepo<String, Any>,
|
||||
private val scope: CoroutineScope,
|
||||
private val koin: Koin,
|
||||
private val dbDropper: DBDropper
|
||||
) : AuthSettings {
|
||||
private val _authorizedDIModule = MutableStateFlow<Module?>(null)
|
||||
override val authorizedDIModule: StateFlow<Module?> = _authorizedDIModule.asStateFlow()
|
||||
private val _user = MutableStateFlow<User?>(null)
|
||||
override val user: StateFlow<User?> = _user.asStateFlow()
|
||||
private val _userRoles = MutableStateFlow<List<Role>>(emptyList())
|
||||
override val userRoles: StateFlow<List<Role>> = _userRoles.asStateFlow()
|
||||
|
||||
private suspend fun getCurrentServerURL() = repo.get(SERVER_URL_FIELD) as? String
|
||||
private suspend fun getCurrentUsername() = repo.get(USERNAME_FIELD) as? String
|
||||
private suspend fun getCurrentToken() = repo.get(TOKEN_FIELD) as? AuthTokenInfo
|
||||
|
||||
override val loadingJob: Job = scope.launch {
|
||||
val serverUrl = getCurrentServerURL() ?: return@launch
|
||||
val token = getCurrentToken() ?: return@launch
|
||||
updateModule(serverUrl, token.either())
|
||||
}
|
||||
|
||||
val rolesUpdatingJob = (user + authorizedDIModule).subscribeSafelyWithoutExceptions(scope) {
|
||||
val user = user.value
|
||||
|
||||
if (user == null || authorizedDIModule.value == null) {
|
||||
_userRoles.value = emptyList()
|
||||
} else {
|
||||
_userRoles.value = koin.get<RolesStorage<Role>>().getRoles(user.id)
|
||||
}
|
||||
println(user)
|
||||
println(userRoles.value)
|
||||
}
|
||||
|
||||
override suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError? {
|
||||
return runCatching {
|
||||
if (getCurrentServerURL() != serverUrl || getCurrentUsername() != creds.username.string) {
|
||||
dbDropper()
|
||||
}
|
||||
repo.set(SERVER_URL_FIELD, serverUrl)
|
||||
repo.set(USERNAME_FIELD, creds.username.string)
|
||||
repo.unset(TOKEN_FIELD)
|
||||
updateModule(serverUrl, creds.either())
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
private suspend fun updateModule(
|
||||
serverUrl: String,
|
||||
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
|
||||
): AuthUIError? {
|
||||
val currentModule = authorizedDIModule.value
|
||||
val newModule = createAuthorizedFeaturesDIModule(
|
||||
serverUrl,
|
||||
initialAuthKey,
|
||||
{
|
||||
repo.set(TOKEN_FIELD, it)
|
||||
},
|
||||
{
|
||||
_user.value = it
|
||||
}
|
||||
) {
|
||||
repo.unset(SERVER_URL_FIELD, USERNAME_FIELD, TOKEN_FIELD)
|
||||
_authorizedDIModule.value = null
|
||||
_user.value = null
|
||||
throw AuthUnavailableException
|
||||
}
|
||||
currentModule ?.let { koin.unloadModules(listOf(currentModule)) }
|
||||
koin.loadModules(listOf(newModule))
|
||||
val statusFeature = koin.get<StatusFeatureClient>()
|
||||
|
||||
val serverAvailable = statusFeature.checkServerStatus()
|
||||
val authCorrect = serverAvailable && runCatching {
|
||||
statusFeature.checkServerStatusWithAuth()
|
||||
}.getOrElse { false }
|
||||
if (!serverAvailable && !authCorrect) {
|
||||
koin.unloadModules(listOf(newModule))
|
||||
currentModule ?.let { koin.loadModules(listOf(currentModule)) }
|
||||
}
|
||||
return when {
|
||||
!serverAvailable -> ServerUnavailableAuthUIError
|
||||
!authCorrect -> AuthIncorrectAuthUIError
|
||||
else -> {
|
||||
_authorizedDIModule.value = newModule
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SERVER_URL_FIELD = "AuthServerURL"
|
||||
private const val USERNAME_FIELD = "AuthUsername"
|
||||
private const val TOKEN_FIELD = "AuthToken"
|
||||
}
|
||||
}
|
14
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ui/AuthUIFSMState.kt
Normal file
14
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ui/AuthUIFSMState.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package dev.inmo.postssystem.features.auth.client.ui
|
||||
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AuthUIFSMState(
|
||||
override val from: UIFSMState?,
|
||||
override val context: String = "main"
|
||||
) : UIFSMState {
|
||||
companion object {
|
||||
val default = AuthUIFSMState(null)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package dev.inmo.postssystem.features.auth.client.ui
|
||||
|
||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||
import dev.inmo.postssystem.features.common.common.UIModel
|
||||
import dev.inmo.postssystem.features.common.common.ui.UIModel
|
||||
|
||||
interface AuthUIModel : UIModel<AuthUIState> {
|
||||
suspend fun initAuth(serverUrl: String, creds: AuthCreds)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.inmo.postssystem.features.auth.client.ui
|
||||
|
||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||
import dev.inmo.postssystem.features.common.common.UIViewModel
|
||||
import dev.inmo.postssystem.features.common.common.ui.UIViewModel
|
||||
import dev.inmo.postssystem.features.users.common.Username
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
|
35
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ui/DefaultAuthUIModel.kt
Normal file
35
features/auth/client/src/commonMain/kotlin/dev/inmo/postssystem/features/auth/client/ui/DefaultAuthUIModel.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package dev.inmo.postssystem.features.auth.client.ui
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
|
||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||
import dev.inmo.postssystem.features.common.common.ui.AbstractUIModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DefaultAuthUIModel(
|
||||
private val scope: CoroutineScope,
|
||||
private val authSettings: AuthSettings
|
||||
) : AbstractUIModel<AuthUIState>(LoadingAuthUIState), AuthUIModel {
|
||||
init {
|
||||
scope.launch {
|
||||
_currentState.value = LoadingAuthUIState
|
||||
authSettings.loadingJob.join()
|
||||
if (authSettings.authorizedDIModule.value == null) {
|
||||
_currentState.value = DefaultInitAuthUIState
|
||||
} else {
|
||||
_currentState.value = AuthorizedAuthUIState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun initAuth(serverUrl: String, creds: AuthCreds) {
|
||||
_currentState.value = LoadingAuthUIState
|
||||
val authError = authSettings.auth(serverUrl, creds)
|
||||
if (authError == null) {
|
||||
_currentState.value = AuthorizedAuthUIState
|
||||
} else {
|
||||
_currentState.value = InitAuthUIState(authError)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
119
features/auth/client/src/jsMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthView.kt
Normal file
119
features/auth/client/src/jsMain/kotlin/dev/inmo/postssystem/features/auth/client/AuthView.kt
Normal file
@ -0,0 +1,119 @@
|
||||
package dev.inmo.postssystem.features.auth.client
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import dev.inmo.jsuikit.elements.*
|
||||
import dev.inmo.jsuikit.modifiers.*
|
||||
import dev.inmo.jsuikit.utils.Attrs
|
||||
import dev.inmo.micro_utils.coroutines.compose.renderComposableAndLinkToContextAndRoot
|
||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||
import dev.inmo.postssystem.features.common.common.*
|
||||
import dev.inmo.postssystem.features.common.common.ui.JSView
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.*
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.dom.*
|
||||
import org.jetbrains.compose.web.attributes.InputType
|
||||
import org.jetbrains.compose.web.dom.Text
|
||||
import org.w3c.dom.*
|
||||
|
||||
val loader = DefaultModuleLoader {
|
||||
factory { AuthView(get(), get(DefaultQualifiers.UIScopeQualifier), getAllDistinct()) }
|
||||
singleWithRandomQualifier<UIFSMHandler.Registrator> {
|
||||
UIFSMHandler.Registrator {
|
||||
strictlyOn(get<AuthView>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuthView(
|
||||
private val viewModel: AuthUIViewModel,
|
||||
private val uiScope: CoroutineScope,
|
||||
defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
|
||||
) : JSView<AuthUIFSMState>(defaultExceptionsHandlers) {
|
||||
|
||||
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
||||
htmlElement: HTMLElement,
|
||||
state: AuthUIFSMState
|
||||
): UIFSMState? {
|
||||
val completion = CompletableDeferred<UIFSMState?>()
|
||||
|
||||
val usernameState = mutableStateOf("")
|
||||
val passwordState = mutableStateOf("")
|
||||
val disabled = mutableStateOf(true)
|
||||
val errorText = mutableStateOf<String?>(null)
|
||||
|
||||
val root = htmlElement.appendElement("div") {}
|
||||
val composition = renderComposableAndLinkToContextAndRoot(root) {
|
||||
val authBtnDisabled = usernameState.value.isBlank() || passwordState.value.isBlank()
|
||||
|
||||
Flex(
|
||||
UIKitFlex.Alignment.Horizontal.Center
|
||||
) {
|
||||
Card(
|
||||
Attrs(UIKitText.Alignment.Horizontal.Center),
|
||||
bodyAttrs = Attrs(UIKitWidth.Fixed.Medium),
|
||||
) {
|
||||
CardTitle { Text("Log in") }
|
||||
|
||||
if (errorText.value != null) {
|
||||
CardBadge(Attrs(UIKitLabel.Error)) {
|
||||
Text(errorText.value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
TextField(
|
||||
InputType.Text,
|
||||
usernameState,
|
||||
disabled,
|
||||
"Username",
|
||||
)
|
||||
TextField(
|
||||
InputType.Password,
|
||||
passwordState,
|
||||
disabled,
|
||||
"Password"
|
||||
)
|
||||
|
||||
DefaultButton("Authorise", UIKitButton.Type.Primary, UIKitMargin.Small, disabled = authBtnDisabled) {
|
||||
it.nativeEvent.preventDefault()
|
||||
val serverUrl = document.location ?.run { "$hostname:$port" }
|
||||
if (serverUrl != null) {
|
||||
uiScope.launchSafelyWithoutExceptions { viewModel.initAuth(serverUrl, usernameState.value, passwordState.value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val viewJob = viewModel.currentState.subscribeSafelyWithoutExceptions(uiScope) {
|
||||
when (it) {
|
||||
is InitAuthUIState -> {
|
||||
disabled.value = false
|
||||
|
||||
errorText.value = when (it.showError) {
|
||||
ServerUnavailableAuthUIError -> "Server unavailable"
|
||||
AuthIncorrectAuthUIError -> {
|
||||
passwordState.value = ""
|
||||
"Username or password is incorrect"
|
||||
}
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
LoadingAuthUIState -> {
|
||||
disabled.value = true
|
||||
errorText.value = null
|
||||
}
|
||||
AuthorizedAuthUIState -> {
|
||||
completion.complete(state.from)
|
||||
}
|
||||
}
|
||||
}
|
||||
return completion.await().also {
|
||||
composition.dispose()
|
||||
viewJob.cancel()
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,11 @@ kotlin {
|
||||
api "io.ktor:ktor-client-auth:$ktor_version"
|
||||
api "io.ktor:ktor-client-logging:$ktor_version"
|
||||
|
||||
api libs.microutils.common.compose
|
||||
api libs.microutils.coroutines.compose
|
||||
api libs.microutils.fsm.common
|
||||
api libs.microutils.fsm.repos.common
|
||||
|
||||
api compose.runtime
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.inmo.postssystem.features.common.common
|
||||
|
||||
object AdditionalModules {
|
||||
class AdditionalModules {
|
||||
private val additionalModules = mutableListOf<ModuleLoader>()
|
||||
val modules: List<ModuleLoader>
|
||||
get() = additionalModules.toList()
|
||||
@ -8,4 +8,8 @@ object AdditionalModules {
|
||||
fun addModule(moduleLoader: ModuleLoader): Boolean {
|
||||
return additionalModules.add(moduleLoader)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Default = AdditionalModules()
|
||||
}
|
||||
}
|
||||
|
12
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/DBDropper.kt
Normal file
12
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/DBDropper.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package dev.inmo.postssystem.features.common.common
|
||||
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
|
||||
class DBDropper(
|
||||
private val repo: KeyValueRepo<String, Any>
|
||||
) {
|
||||
suspend operator fun invoke() {
|
||||
repo.unset(repo.getAllByWithNextPaging { keys(it) })
|
||||
}
|
||||
}
|
67
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/DefaultModules.kt
Normal file
67
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/DefaultModules.kt
Normal file
@ -0,0 +1,67 @@
|
||||
package dev.inmo.postssystem.features.common.common
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope
|
||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
|
||||
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.serialization.BinaryFormat
|
||||
import kotlinx.serialization.StringFormat
|
||||
import kotlinx.serialization.cbor.Cbor
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.scope.Scope
|
||||
import org.koin.dsl.module
|
||||
|
||||
/**
|
||||
* Entrypoint for getting [org.koin.core.Koin] DI for the client
|
||||
*
|
||||
* @param repoFactory Factory for creating of [DefaultStatesManagerRepo] for [dev.inmo.postssystem.client.ui.fsm.UIFSM]
|
||||
*/
|
||||
fun baseKoin(
|
||||
defaultScope: CoroutineScope,
|
||||
settingsFactory: Scope.() -> KeyValueRepo<String, Any>,
|
||||
repoFactory: Scope.() -> DefaultStatesManagerRepo<UIFSMState>,
|
||||
handlersSetter: Pair<Scope, FSMBuilder<UIFSMState>>.() -> Unit
|
||||
): Koin = startKoin {
|
||||
modules(
|
||||
module {
|
||||
single {
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
|
||||
}
|
||||
}
|
||||
single<StringFormat> { get<Json>() }
|
||||
single {
|
||||
Cbor { serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } } }
|
||||
}
|
||||
single<BinaryFormat> { get<Cbor>() }
|
||||
|
||||
single(DefaultQualifiers.SettingsQualifier) { settingsFactory() }
|
||||
single { DBDropper(get(DefaultQualifiers.SettingsQualifier)) }
|
||||
single(DefaultQualifiers.FSMHandlersBuilderQualifier) { handlersSetter }
|
||||
single { repoFactory() }
|
||||
single { defaultScope }
|
||||
single(DefaultQualifiers.UIScopeQualifier) { get<CoroutineScope>().LinkedSupervisorScope(Dispatchers.Main) }
|
||||
single<StatesMachine<UIFSMState>>(UIFSMQualifier) { UIFSM(get()) { (this@single to this@UIFSM).apply(get(
|
||||
DefaultQualifiers.FSMHandlersBuilderQualifier
|
||||
)) } }
|
||||
|
||||
AdditionalModules.Default.modules.forEach {
|
||||
it.apply { load() }
|
||||
}
|
||||
}
|
||||
)
|
||||
}.koin.apply {
|
||||
loadModules(
|
||||
listOf(
|
||||
module { single<Koin> { this@apply } }
|
||||
)
|
||||
)
|
||||
}
|
10
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/DefaultQualifiers.kt
Normal file
10
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/DefaultQualifiers.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package dev.inmo.postssystem.features.common.common
|
||||
|
||||
import org.koin.core.qualifier.StringQualifier
|
||||
|
||||
object DefaultQualifiers {
|
||||
val SettingsQualifier = StringQualifier("Settings")
|
||||
val FSMHandlersBuilderQualifier = StringQualifier("FSMHandlersBuilder")
|
||||
val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
|
||||
val UIFSMQualifier = StringQualifier("FSM")
|
||||
}
|
@ -2,6 +2,18 @@ package dev.inmo.postssystem.features.common.common
|
||||
|
||||
import org.koin.core.module.Module
|
||||
|
||||
fun interface ModuleLoader {
|
||||
interface ModuleLoader {
|
||||
fun Module.load()
|
||||
|
||||
class ByCallback(private val loadingBlock: Module.() -> Unit) : ModuleLoader {
|
||||
override fun Module.load() {
|
||||
loadingBlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun DefaultModuleLoader(loadingBlock: Module.() -> Unit): ModuleLoader.ByCallback {
|
||||
val newModuleLoader = ModuleLoader.ByCallback(loadingBlock)
|
||||
AdditionalModules.Default.addModule(newModuleLoader)
|
||||
return newModuleLoader
|
||||
}
|
||||
|
4
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/UIView.kt
4
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/UIView.kt
@ -1,4 +0,0 @@
|
||||
package dev.inmo.postssystem.features.common.common
|
||||
|
||||
interface UIView {
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package dev.inmo.postssystem.features.common.common
|
||||
package dev.inmo.postssystem.features.common.common.ui
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.inmo.postssystem.features.common.common
|
||||
package dev.inmo.postssystem.features.common.common.ui
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
4
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/UIView.kt
Normal file
4
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/UIView.kt
Normal file
@ -0,0 +1,4 @@
|
||||
package dev.inmo.postssystem.features.common.common.ui
|
||||
|
||||
interface UIView {
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package dev.inmo.postssystem.features.common.common
|
||||
package dev.inmo.postssystem.features.common.common.ui
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@ -7,9 +7,7 @@ interface UIViewModel<StateType> {
|
||||
val currentState: StateFlow<StateType>
|
||||
}
|
||||
|
||||
abstract class AbstractUIViewModel<StateType> : UIViewModel<StateType> {
|
||||
protected val _currentState = DefaultMVVMStateFlow(initState())
|
||||
abstract class AbstractUIViewModel<StateType>(initState: StateType) : UIViewModel<StateType> {
|
||||
protected val _currentState = DefaultMVVMStateFlow(initState)
|
||||
override val currentState: StateFlow<StateType> = _currentState.asStateFlow()
|
||||
|
||||
abstract fun initState(): StateType
|
||||
}
|
17
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/fsm/UIFSM.kt
Normal file
17
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/fsm/UIFSM.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package dev.inmo.postssystem.features.common.common.ui.fsm
|
||||
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
|
||||
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
|
||||
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
||||
import org.koin.core.qualifier.StringQualifier
|
||||
|
||||
val UIFSMQualifier = StringQualifier("UIFSM")
|
||||
|
||||
fun UIFSM(
|
||||
repo: DefaultStatesManagerRepo<UIFSMState>,
|
||||
handlersSetter: FSMBuilder<UIFSMState>.() -> Unit
|
||||
) = buildFSM<UIFSMState> {
|
||||
statesManager = DefaultStatesManager(repo)
|
||||
handlersSetter()
|
||||
}
|
7
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/fsm/UIFSMExceptionHandler.kt
Normal file
7
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/fsm/UIFSMExceptionHandler.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package dev.inmo.postssystem.features.common.common.ui.fsm
|
||||
|
||||
import dev.inmo.micro_utils.common.Optional
|
||||
|
||||
fun interface UIFSMExceptionHandler {
|
||||
suspend fun handle(currentState: UIFSMState, exception: Throwable): Optional<UIFSMState?>
|
||||
}
|
34
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/fsm/UIFSMHandler.kt
Normal file
34
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/fsm/UIFSMHandler.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package dev.inmo.postssystem.features.common.common.ui.fsm
|
||||
|
||||
import dev.inmo.micro_utils.common.onPresented
|
||||
import dev.inmo.micro_utils.fsm.common.*
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
|
||||
import org.koin.core.scope.Scope
|
||||
|
||||
interface UIFSMHandler<T : UIFSMState> : StatesHandler<T, UIFSMState> {
|
||||
fun interface Registrator {
|
||||
fun FSMBuilder<UIFSMState>.include()
|
||||
}
|
||||
val defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
|
||||
|
||||
suspend fun StatesMachine<in UIFSMState>.safeHandleState(state: T): UIFSMState?
|
||||
override suspend fun StatesMachine<in UIFSMState>.handleState(state: T): UIFSMState? {
|
||||
return runCatching {
|
||||
safeHandleState(state).also(::println)
|
||||
}.getOrElse {
|
||||
errorToNextStep(state, it) ?.let { return it } ?: throw it
|
||||
}.also(::println)
|
||||
}
|
||||
|
||||
suspend fun errorToNextStep(
|
||||
currentState: T,
|
||||
e: Throwable
|
||||
): UIFSMState? {
|
||||
defaultExceptionsHandlers.forEach {
|
||||
it.handle(currentState, e).onPresented { state ->
|
||||
return state
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
17
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/fsm/UIFSMState.kt
Normal file
17
features/common/client/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/ui/fsm/UIFSMState.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package dev.inmo.postssystem.features.common.common.ui.fsm
|
||||
|
||||
import dev.inmo.micro_utils.fsm.common.State
|
||||
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
|
||||
import kotlinx.serialization.*
|
||||
|
||||
@Serializable(UIFSMStateSerializer::class)
|
||||
interface UIFSMState : State {
|
||||
val from: UIFSMState?
|
||||
get() = null
|
||||
override val context: String
|
||||
get() = "main"
|
||||
}
|
||||
|
||||
object UIFSMStateSerializer : KSerializer<UIFSMState>, TypedSerializer<UIFSMState>(
|
||||
UIFSMState::class
|
||||
)
|
19
features/common/client/src/jsMain/kotlin/dev/inmo/postssystem/features/common/common/ui/JSView.kt
Normal file
19
features/common/client/src/jsMain/kotlin/dev/inmo/postssystem/features/common/common/ui/JSView.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package dev.inmo.postssystem.features.common.common.ui
|
||||
|
||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||
import dev.inmo.postssystem.features.common.common.ui.fsm.*
|
||||
import kotlinx.browser.document
|
||||
import org.w3c.dom.HTMLElement
|
||||
|
||||
abstract class JSView<T : UIFSMState>(
|
||||
override val defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
|
||||
) : UIFSMHandler<T> {
|
||||
open suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
||||
htmlElement: HTMLElement,
|
||||
state: T
|
||||
): UIFSMState? = null
|
||||
|
||||
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(state: T): UIFSMState? {
|
||||
return safeHandleState(document.getElementById(state.context) as? HTMLElement ?: return null, state)
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import org.koin.core.module.Module
|
||||
|
||||
object LoadingClientModule : ModuleLoader {
|
||||
init {
|
||||
AdditionalModules.addModule(this)
|
||||
AdditionalModules.Default.addModule(this)
|
||||
}
|
||||
|
||||
override fun Module.load() {
|
||||
|
11
features/content/client/src/commonMain/kotlin/dev/inmo/postssystem/features/content/client/ModuleLoader.kt
Normal file
11
features/content/client/src/commonMain/kotlin/dev/inmo/postssystem/features/content/client/ModuleLoader.kt
Normal file
@ -0,0 +1,11 @@
|
||||
package dev.inmo.postssystem.features.content.client
|
||||
|
||||
import dev.inmo.postssystem.features.common.common.*
|
||||
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
|
||||
import dev.inmo.postssystem.features.content.common.OtherContentSerializerModuleConfigurator
|
||||
|
||||
val loader = DefaultModuleLoader {
|
||||
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
|
||||
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
|
||||
single { SerializersModuleConfigurator(getAll()) }
|
||||
}
|
@ -12,7 +12,7 @@ import org.koin.core.module.Module
|
||||
|
||||
object LoadingClientModule : ModuleLoader {
|
||||
init {
|
||||
AdditionalModules.addModule(this)
|
||||
AdditionalModules.Default.addModule(this)
|
||||
}
|
||||
|
||||
override fun Module.load() {
|
||||
|
@ -12,6 +12,7 @@ kotlin {
|
||||
dependencies {
|
||||
api project(":postssystem.features.files.common")
|
||||
api project(":postssystem.features.common.client")
|
||||
api project(":postssystem.features.auth.client")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
features/files/client/src/commonMain/kotlin/dev/inmo/postssystem/features/files/client/ModuleLoader.kt
Normal file
9
features/files/client/src/commonMain/kotlin/dev/inmo/postssystem/features/files/client/ModuleLoader.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package dev.inmo.postssystem.features.files.client
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.AuthorizedModuleLoader
|
||||
import dev.inmo.postssystem.features.auth.client.AuthorizedQualifiers
|
||||
import dev.inmo.postssystem.features.files.common.storage.ReadFilesStorage
|
||||
|
||||
val loader = AuthorizedModuleLoader {
|
||||
single<ReadFilesStorage> { ClientReadFilesStorage(get(AuthorizedQualifiers.ServerUrlQualifier), get(), get()) }
|
||||
}
|
@ -12,6 +12,7 @@ kotlin {
|
||||
dependencies {
|
||||
api project(":postssystem.features.roles.common")
|
||||
api project(":postssystem.features.common.client")
|
||||
api project(":postssystem.features.auth.client")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
features/roles/client/src/commonMain/kotlin/dev/inmo/postssystem/features/roles/client/ModuleLoader.kt
Normal file
10
features/roles/client/src/commonMain/kotlin/dev/inmo/postssystem/features/roles/client/ModuleLoader.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package dev.inmo.postssystem.features.roles.client
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.AuthorizedModuleLoader
|
||||
import dev.inmo.postssystem.features.auth.client.AuthorizedQualifiers
|
||||
import dev.inmo.postssystem.features.roles.common.Role
|
||||
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
||||
|
||||
val loader = AuthorizedModuleLoader {
|
||||
single<RolesStorage<Role>> { ClientRolesStorage(get(AuthorizedQualifiers.ServerUrlQualifier), get(), Role.serializer()) }
|
||||
}
|
@ -5,6 +5,8 @@ import dev.inmo.postssystem.features.roles.common.RoleSerializer
|
||||
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private val justForLoading = RolesManagerRoleSerializer
|
||||
|
||||
@Serializable(RolesManagerRoleSerializer::class)
|
||||
interface RolesManagerRole : Role {
|
||||
companion object {
|
||||
|
@ -12,6 +12,7 @@ kotlin {
|
||||
dependencies {
|
||||
api project(":postssystem.features.users.common")
|
||||
api project(":postssystem.features.common.client")
|
||||
api project(":postssystem.features.auth.client")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
features/users/client/src/commonMain/kotlin/dev/inmo/postssystem/features/users/client/ModuleLoader.kt
Normal file
9
features/users/client/src/commonMain/kotlin/dev/inmo/postssystem/features/users/client/ModuleLoader.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package dev.inmo.postssystem.features.users.client
|
||||
|
||||
import dev.inmo.postssystem.features.auth.client.AuthorizedModuleLoader
|
||||
import dev.inmo.postssystem.features.auth.client.AuthorizedQualifiers
|
||||
import dev.inmo.postssystem.features.users.common.ReadUsersStorage
|
||||
|
||||
val loader = AuthorizedModuleLoader {
|
||||
single<ReadUsersStorage> { UsersStorageKtorClient(get(AuthorizedQualifiers.ServerUrlQualifier), get()) }
|
||||
}
|
Reference in New Issue
Block a user