Compare commits
No commits in common. "master" and "72578f6b58a540601b8c11f2f0324ca7443fcdb7" have entirely different histories.
master
...
72578f6b58
@ -1,14 +1,12 @@
|
|||||||
## Структура проекта
|
## Структура проекта
|
||||||
|
|
||||||
* **Features** - набор **законченных** фич проекта. Считается, что любая фича, находящаяся в мастере может быть добавлена в
|
* **Features** - набор **законченных** фич проекта. Считается, что любая фича, находящаяся в мастере может быть добавлена в
|
||||||
клиент и использована в нем. Исключением является `common` - это набор вещей, используемых везде.
|
клиент и использована в нем. Исключением является `common` - это набор вещей, используемых везде. В подпунктах представлены
|
||||||
* Части, на которые *обычно* разделяется фича
|
части, на которые *обычно* разделяется фича
|
||||||
* Common - общая для фичи часть. Тут, как правило, хранятся конвенции путей для сетевых соединений, общие типы и пр.
|
* Common - общая для фичи часть. Тут, как правило, хранятся конвенции путей для сетевых соединений, общие типы и пр.
|
||||||
* Server - часть, включаемая в сервер для подключения фичи. Обычно содержит работу с бд, определение модулей сервера и пр.
|
* Server - часть, включаемая в сервер для подключения фичи. Обычно содержит работу с бд, определение модулей сервера и пр.
|
||||||
* Client - часть с клиентским кодом. В большинстве своём включает работу с сервером, MVVM часть (View при этом должны
|
* Client - часть с клиентским кодом. В большинстве своём включает работу с сервером, MVVM часть (View при этом должны
|
||||||
находиться в платформенной части, если их нельзя вынести в сommon часть клиента)
|
находиться в платформенной части, если их нельзя вынести в сommon часть клиента)
|
||||||
* Также существует фича `client`, которая не является фичей самой по-себе. Фактически, это набор разных фич, который системно
|
|
||||||
отсутствуют в бэке и используются в основном для пробрасывания удобных `API` и `View` для клиента
|
|
||||||
* **Services** - модули, отвечающие за клиент-серверную работу фич с точки зрения их взаимодействия. Например, в рамках сервисов
|
* **Services** - модули, отвечающие за клиент-серверную работу фич с точки зрения их взаимодействия. Например, в рамках сервисов
|
||||||
должен быть добавлен модуль для постов - именно через сервисы будет происходить создание поста, его редактирование и удаление
|
должен быть добавлен модуль для постов - именно через сервисы будет происходить создание поста, его редактирование и удаление
|
||||||
* **Client** - итоговый клиент. На момент написания этой доки (`Пн окт 25 12:56:41 +06 2021`) предполагается два варианта:
|
* **Client** - итоговый клиент. На момент написания этой доки (`Пн окт 25 12:56:41 +06 2021`) предполагается два варианта:
|
||||||
|
13
build.gradle
13
build.gradle
@ -4,16 +4,14 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven { url "https://plugins.gradle.org/m2/" }
|
maven { url "https://plugins.gradle.org/m2/" }
|
||||||
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath libs.buildscript.kt.gradle
|
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||||
classpath libs.buildscript.kt.serialization
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath libs.buildscript.jb.dokka
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
classpath libs.buildscript.gh.release
|
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
||||||
classpath libs.buildscript.android.gradle
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||||
classpath libs.buildscript.android.dexcount
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +20,6 @@ allprojects {
|
|||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,9 @@ kotlin {
|
|||||||
api project(":postssystem.features.content.binary.client")
|
api project(":postssystem.features.content.binary.client")
|
||||||
|
|
||||||
api project(":postssystem.services.posts.client")
|
api project(":postssystem.services.posts.client")
|
||||||
|
|
||||||
|
api libs.microutils.fsm.common
|
||||||
|
api libs.microutils.fsm.repos.common
|
||||||
api libs.microutils.crypto
|
api libs.microutils.crypto
|
||||||
|
|
||||||
implementation compose.runtime
|
implementation compose.runtime
|
||||||
@ -40,7 +43,7 @@ kotlin {
|
|||||||
|
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api libs.ktor.client.apache
|
api "io.ktor:ktor-client-apache:$ktor_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package dev.inmo.postssystem.features.common.common
|
package dev.inmo.postssystem.client
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
148
client/src/commonMain/kotlin/dev/inmo/postssystem/client/DI.kt
Normal file
148
client/src/commonMain/kotlin/dev/inmo/postssystem/client/DI.kt
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package dev.inmo.postssystem.client
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.client.ui.fsm.*
|
||||||
|
import dev.inmo.postssystem.features.auth.client.installClientAuthenticator
|
||||||
|
import dev.inmo.postssystem.features.auth.common.*
|
||||||
|
import dev.inmo.postssystem.features.files.client.ClientReadFilesStorage
|
||||||
|
import dev.inmo.postssystem.features.files.common.storage.ReadFilesStorage
|
||||||
|
import dev.inmo.postssystem.features.roles.common.Role
|
||||||
|
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
||||||
|
import dev.inmo.postssystem.features.roles.client.ClientRolesStorage
|
||||||
|
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRoleSerializer
|
||||||
|
import dev.inmo.postssystem.features.users.client.UsersStorageKtorClient
|
||||||
|
import dev.inmo.postssystem.features.users.common.ReadUsersStorage
|
||||||
|
import dev.inmo.postssystem.features.users.common.User
|
||||||
|
import dev.inmo.micro_utils.common.Either
|
||||||
|
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.ktor.client.UnifiedRequester
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.postssystem.client.settings.DefaultSettings
|
||||||
|
import dev.inmo.postssystem.client.settings.Settings
|
||||||
|
import dev.inmo.postssystem.client.settings.auth.AuthSettings
|
||||||
|
import dev.inmo.postssystem.client.settings.auth.DefaultAuthSettings
|
||||||
|
import dev.inmo.postssystem.features.common.common.*
|
||||||
|
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
|
||||||
|
import dev.inmo.postssystem.features.content.common.OtherContentSerializerModuleConfigurator
|
||||||
|
import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator
|
||||||
|
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
|
||||||
|
import dev.inmo.postssystem.publicators.simple.client.SimplePublicatorService
|
||||||
|
import dev.inmo.postssystem.publicators.simple.client.SimplePublicatorServiceClient
|
||||||
|
import dev.inmo.postssystem.services.posts.client.ClientPostsService
|
||||||
|
import dev.inmo.postssystem.services.posts.common.*
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
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.module.Module
|
||||||
|
import org.koin.core.qualifier.*
|
||||||
|
import org.koin.core.scope.Scope
|
||||||
|
import org.koin.dsl.binds
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
|
||||||
|
val SettingsQualifier = StringQualifier("Settings")
|
||||||
|
val RolesQualifier = StringQualifier("Roles")
|
||||||
|
private val FSMHandlersBuilderQualifier = StringQualifier("FSMHandlersBuilder")
|
||||||
|
|
||||||
|
val defaultSerialFormat = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
|
||||||
|
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
|
||||||
|
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
|
||||||
|
single { SerializersModuleConfigurator(getAll()) }
|
||||||
|
|
||||||
|
single {
|
||||||
|
Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
single<StringFormat> { get<Json>() }
|
||||||
|
|
||||||
|
single(SettingsQualifier) { settingsFactory() }
|
||||||
|
single { DBDropper(get(SettingsQualifier)) }
|
||||||
|
single(FSMHandlersBuilderQualifier) { handlersSetter }
|
||||||
|
single { repoFactory() }
|
||||||
|
single { defaultScope }
|
||||||
|
single(UIScopeQualifier) { get<CoroutineScope>().LinkedSupervisorScope(Dispatchers.Main) }
|
||||||
|
single<StatesMachine<UIFSMState>>(UIFSMQualifier) { UIFSM(get()) { (this@single to this@UIFSM).apply(get(
|
||||||
|
FSMHandlersBuilderQualifier
|
||||||
|
)) } }
|
||||||
|
single<AuthSettings> { DefaultAuthSettings(get(SettingsQualifier), get(), koin, get()) }
|
||||||
|
single<Settings> { DefaultSettings(get()) }
|
||||||
|
|
||||||
|
AdditionalModules.modules.forEach {
|
||||||
|
it.apply { load() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.koin.apply {
|
||||||
|
loadModules(
|
||||||
|
listOf(
|
||||||
|
module { single<Koin> { this@apply } }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
RolesManagerRoleSerializer // Just to activate it in JS client
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAuthorizedFeaturesDIModule(
|
||||||
|
serverUrl: String,
|
||||||
|
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
|
||||||
|
onAuthKeyUpdated: suspend (AuthTokenInfo) -> Unit,
|
||||||
|
onUserRetrieved: suspend (User?) -> Unit,
|
||||||
|
onAuthKeyInvalidated: suspend () -> Unit
|
||||||
|
): Module {
|
||||||
|
val serverUrlQualifier = StringQualifier("serverUrl")
|
||||||
|
val credsQualifier = StringQualifier("creds")
|
||||||
|
|
||||||
|
return module {
|
||||||
|
single(createdAtStart = true) {
|
||||||
|
HttpClient {
|
||||||
|
installClientAuthenticator(serverUrl, get(), get(credsQualifier), onAuthKeyUpdated, onUserRetrieved, onAuthKeyInvalidated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
single(credsQualifier) { initialAuthKey }
|
||||||
|
single(serverUrlQualifier) { serverUrl }
|
||||||
|
single<BinaryFormat> {
|
||||||
|
Cbor {
|
||||||
|
serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
single { UnifiedRequester(get(), get()) }
|
||||||
|
|
||||||
|
single { StatusFeatureClient(get(serverUrlQualifier), get()) }
|
||||||
|
|
||||||
|
single<ReadFilesStorage> { ClientReadFilesStorage(get(serverUrlQualifier), get(), get()) }
|
||||||
|
single<ReadUsersStorage> { UsersStorageKtorClient(get(serverUrlQualifier), get()) }
|
||||||
|
single<RolesStorage<Role>> { ClientRolesStorage(get(serverUrlQualifier), get(), Role.serializer()) }
|
||||||
|
single<PostsService> { ClientPostsService(get(serverUrlQualifier), get()) } binds arrayOf(
|
||||||
|
ReadPostsService::class,
|
||||||
|
WritePostsService::class
|
||||||
|
)
|
||||||
|
single<SimplePublicatorService> { SimplePublicatorServiceClient(get(serverUrlQualifier), get()) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.postssystem.client.settings
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.client.settings.auth.AuthSettings
|
||||||
|
|
||||||
|
|
||||||
|
data class DefaultSettings(
|
||||||
|
override val authSettings: AuthSettings
|
||||||
|
) : Settings
|
@ -0,0 +1,12 @@
|
|||||||
|
package dev.inmo.postssystem.client.settings
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.client.settings.auth.AuthSettings
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
|
interface Settings {
|
||||||
|
val authSettings: AuthSettings
|
||||||
|
|
||||||
|
val authorizedDIModule: StateFlow<Module?>
|
||||||
|
get() = authSettings.authorizedDIModule
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
package dev.inmo.postssystem.features.auth.client.settings
|
package dev.inmo.postssystem.client.settings.auth
|
||||||
|
|
||||||
import dev.inmo.postssystem.features.auth.client.ui.AuthUIError
|
import dev.inmo.postssystem.features.auth.client.ui.AuthUIError
|
||||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
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 dev.inmo.postssystem.features.users.common.User
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@ -10,6 +11,7 @@ import org.koin.core.module.Module
|
|||||||
interface AuthSettings {
|
interface AuthSettings {
|
||||||
val authorizedDIModule: StateFlow<Module?>
|
val authorizedDIModule: StateFlow<Module?>
|
||||||
val user: StateFlow<User?>
|
val user: StateFlow<User?>
|
||||||
|
val userRoles: StateFlow<List<Role>>
|
||||||
val loadingJob: Job
|
val loadingJob: Job
|
||||||
|
|
||||||
suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError?
|
suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError?
|
@ -1,16 +1,19 @@
|
|||||||
package dev.inmo.postssystem.features.auth.client.settings
|
package dev.inmo.postssystem.client.settings.auth
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.client.DBDropper
|
||||||
|
import dev.inmo.postssystem.client.getAuthorizedFeaturesDIModule
|
||||||
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
|
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
|
||||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||||
import dev.inmo.postssystem.features.auth.common.*
|
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.status.client.StatusFeatureClient
|
||||||
import dev.inmo.postssystem.features.users.common.User
|
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.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.micro_utils.repos.*
|
||||||
import dev.inmo.postssystem.features.auth.client.createAuthorizedFeaturesDIModule
|
|
||||||
import dev.inmo.postssystem.features.auth.client.ui.AuthUIError.AuthIncorrect
|
|
||||||
import dev.inmo.postssystem.features.common.common.DBDropper
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.koin.core.Koin
|
import org.koin.core.Koin
|
||||||
@ -26,6 +29,8 @@ data class DefaultAuthSettings(
|
|||||||
override val authorizedDIModule: StateFlow<Module?> = _authorizedDIModule.asStateFlow()
|
override val authorizedDIModule: StateFlow<Module?> = _authorizedDIModule.asStateFlow()
|
||||||
private val _user = MutableStateFlow<User?>(null)
|
private val _user = MutableStateFlow<User?>(null)
|
||||||
override val user: StateFlow<User?> = _user.asStateFlow()
|
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 getCurrentServerURL() = repo.get(SERVER_URL_FIELD) as? String
|
||||||
private suspend fun getCurrentUsername() = repo.get(USERNAME_FIELD) as? String
|
private suspend fun getCurrentUsername() = repo.get(USERNAME_FIELD) as? String
|
||||||
@ -37,6 +42,18 @@ data class DefaultAuthSettings(
|
|||||||
updateModule(serverUrl, token.either())
|
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? {
|
override suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError? {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
if (getCurrentServerURL() != serverUrl || getCurrentUsername() != creds.username.string) {
|
if (getCurrentServerURL() != serverUrl || getCurrentUsername() != creds.username.string) {
|
||||||
@ -56,7 +73,7 @@ data class DefaultAuthSettings(
|
|||||||
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
|
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
|
||||||
): AuthUIError? {
|
): AuthUIError? {
|
||||||
val currentModule = authorizedDIModule.value
|
val currentModule = authorizedDIModule.value
|
||||||
val newModule = createAuthorizedFeaturesDIModule(
|
val newModule = getAuthorizedFeaturesDIModule(
|
||||||
serverUrl,
|
serverUrl,
|
||||||
initialAuthKey,
|
initialAuthKey,
|
||||||
{
|
{
|
||||||
@ -84,8 +101,8 @@ data class DefaultAuthSettings(
|
|||||||
currentModule ?.let { koin.loadModules(listOf(currentModule)) }
|
currentModule ?.let { koin.loadModules(listOf(currentModule)) }
|
||||||
}
|
}
|
||||||
return when {
|
return when {
|
||||||
!serverAvailable -> AuthUIError.ServerUnavailable
|
!serverAvailable -> ServerUnavailableAuthUIError
|
||||||
!authCorrect -> AuthIncorrect
|
!authCorrect -> AuthIncorrectAuthUIError
|
||||||
else -> {
|
else -> {
|
||||||
_authorizedDIModule.value = newModule
|
_authorizedDIModule.value = newModule
|
||||||
null
|
null
|
@ -1,34 +1,35 @@
|
|||||||
package dev.inmo.postssystem.features.auth.client.ui
|
package dev.inmo.postssystem.client.ui
|
||||||
|
|
||||||
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
|
import dev.inmo.postssystem.client.settings.auth.AuthSettings
|
||||||
|
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||||
import dev.inmo.postssystem.features.common.common.ui.AbstractUIModel
|
import dev.inmo.postssystem.features.common.common.AbstractUIModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class DefaultAuthUIModel(
|
class DefaultAuthUIModel(
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
private val authSettings: AuthSettings
|
private val authSettings: AuthSettings
|
||||||
) : AbstractUIModel<AuthUIState>(AuthUIState.Loading), AuthUIModel {
|
) : AbstractUIModel<AuthUIState>(LoadingAuthUIState), AuthUIModel {
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
_currentState.value = AuthUIState.Loading
|
_currentState.value = LoadingAuthUIState
|
||||||
authSettings.loadingJob.join()
|
authSettings.loadingJob.join()
|
||||||
if (authSettings.authorizedDIModule.value == null) {
|
if (authSettings.authorizedDIModule.value == null) {
|
||||||
_currentState.value = AuthUIState.DefaultInit
|
_currentState.value = DefaultInitAuthUIState
|
||||||
} else {
|
} else {
|
||||||
_currentState.value = AuthUIState.Authorized
|
_currentState.value = AuthorizedAuthUIState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun initAuth(serverUrl: String, creds: AuthCreds) {
|
override suspend fun initAuth(serverUrl: String, creds: AuthCreds) {
|
||||||
_currentState.value = AuthUIState.Loading
|
_currentState.value = LoadingAuthUIState
|
||||||
val authError = authSettings.auth(serverUrl, creds)
|
val authError = authSettings.auth(serverUrl, creds)
|
||||||
if (authError == null) {
|
if (authError == null) {
|
||||||
_currentState.value = AuthUIState.Authorized
|
_currentState.value = AuthorizedAuthUIState
|
||||||
} else {
|
} else {
|
||||||
_currentState.value = AuthUIState.Init(authError)
|
_currentState.value = InitAuthUIState(authError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package dev.inmo.postssystem.features.common.common.ui.fsm
|
package dev.inmo.postssystem.client.ui.fsm
|
||||||
|
|
||||||
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
|
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.dsl.buildFSM
|
||||||
@ -6,6 +6,8 @@ import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
|
|||||||
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
||||||
import org.koin.core.qualifier.StringQualifier
|
import org.koin.core.qualifier.StringQualifier
|
||||||
|
|
||||||
|
val UIFSMQualifier = StringQualifier("UIFSM")
|
||||||
|
|
||||||
fun UIFSM(
|
fun UIFSM(
|
||||||
repo: DefaultStatesManagerRepo<UIFSMState>,
|
repo: DefaultStatesManagerRepo<UIFSMState>,
|
||||||
handlersSetter: FSMBuilder<UIFSMState>.() -> Unit
|
handlersSetter: FSMBuilder<UIFSMState>.() -> Unit
|
@ -0,0 +1,27 @@
|
|||||||
|
package dev.inmo.postssystem.client.ui.fsm
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
|
||||||
|
import dev.inmo.micro_utils.fsm.common.*
|
||||||
|
|
||||||
|
interface UIFSMHandler<T : UIFSMState> : StatesHandler<T, UIFSMState> {
|
||||||
|
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? = when (e) {
|
||||||
|
is AuthUnavailableException -> if (currentState is AuthUIFSMState) {
|
||||||
|
currentState
|
||||||
|
} else {
|
||||||
|
AuthUIFSMState(currentState)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package dev.inmo.postssystem.client.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)
|
||||||
|
sealed interface UIFSMState : State {
|
||||||
|
val from: UIFSMState?
|
||||||
|
get() = null
|
||||||
|
override val context: String
|
||||||
|
get() = "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
object UIFSMStateSerializer : KSerializer<UIFSMState> by TypedSerializer(
|
||||||
|
"auth" to AuthUIFSMState.serializer(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AuthUIFSMState(
|
||||||
|
override val from: UIFSMState? = CreatePostUIFSMState(),
|
||||||
|
override val context: String = "main"
|
||||||
|
) : UIFSMState
|
||||||
|
val DefaultAuthUIFSMState = AuthUIFSMState()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CreatePostUIFSMState(
|
||||||
|
override val from: UIFSMState? = null,
|
||||||
|
override val context: String = "main"
|
||||||
|
) : UIFSMState
|
@ -1,24 +1,31 @@
|
|||||||
package dev.inmo.postssystem.client
|
package dev.inmo.postssystem.client
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.client.fsm.ui.*
|
||||||
|
import dev.inmo.postssystem.client.ui.*
|
||||||
|
import dev.inmo.postssystem.client.ui.fsm.*
|
||||||
|
import dev.inmo.postssystem.client.ui.fsm.UIFSMStateSerializer
|
||||||
|
import dev.inmo.postssystem.features.auth.client.ui.AuthUIModel
|
||||||
|
import dev.inmo.postssystem.features.auth.client.ui.AuthUIViewModel
|
||||||
import dev.inmo.postssystem.features.auth.common.AuthTokenInfo
|
import dev.inmo.postssystem.features.auth.common.AuthTokenInfo
|
||||||
import dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler
|
import dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler
|
||||||
|
import dev.inmo.micro_utils.fsm.common.CheckableHandlerHolder
|
||||||
|
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||||
import dev.inmo.micro_utils.repos.mappers.withMapper
|
import dev.inmo.micro_utils.repos.mappers.withMapper
|
||||||
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
|
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
|
||||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
import dev.inmo.postssystem.client.settings.auth.AuthSettings
|
||||||
import dev.inmo.postssystem.features.common.common.baseKoin
|
|
||||||
import dev.inmo.postssystem.features.common.common.getAllDistinct
|
import dev.inmo.postssystem.features.common.common.getAllDistinct
|
||||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMHandler
|
import dev.inmo.postssystem.services.posts.client.ui.create.*
|
||||||
import dev.inmo.postssystem.services.posts.client.ui.list.PostsListUIFSMState
|
|
||||||
import dev.inmo.postssystem.services.posts.client.ui.list.PostsListUIState
|
|
||||||
import kotlinx.browser.*
|
import kotlinx.browser.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
import org.koin.core.Koin
|
import org.koin.core.Koin
|
||||||
import org.koin.core.context.loadKoinModules
|
import org.koin.core.context.loadKoinModules
|
||||||
|
import org.koin.core.parameter.ParametersHolder
|
||||||
|
import org.koin.core.qualifier.Qualifier
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
val defaultTypedSerializer = TypedSerializer<Any>(
|
val defaultTypedSerializer = TypedSerializer<Any>(
|
||||||
"AuthTokenInfo" to AuthTokenInfo.serializer(),
|
"AuthTokenInfo" to AuthTokenInfo.serializer(),
|
||||||
@ -28,11 +35,9 @@ val defaultTypedSerializer = TypedSerializer<Any>(
|
|||||||
"Short" to Short.serializer(),
|
"Short" to Short.serializer(),
|
||||||
"Byte" to Byte.serializer(),
|
"Byte" to Byte.serializer(),
|
||||||
"Float" to Float.serializer(),
|
"Float" to Float.serializer(),
|
||||||
"Double" to Double.serializer()
|
"Double" to Double.serializer(),
|
||||||
|
"UIFSMState" to UIFSMStateSerializer
|
||||||
)
|
)
|
||||||
val defaultSerialFormat = Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun baseKoin(): Koin {
|
fun baseKoin(): Koin {
|
||||||
val anyToString: suspend Any.() -> String = {
|
val anyToString: suspend Any.() -> String = {
|
||||||
@ -73,22 +78,69 @@ fun baseKoin(): Koin {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
JSUIFSMStatesRepo(window.history, AuthUIFSMState(PostsListUIFSMState()), getAllDistinct())
|
JSUIFSMStatesRepo(window.history)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
val scope = first
|
|
||||||
first.apply {
|
first.apply {
|
||||||
second.apply {
|
second.apply {
|
||||||
loadKoinModules(
|
loadKoinModules(
|
||||||
module {
|
module {
|
||||||
factory { document.getElementById("main") as HTMLElement }
|
factory { document.getElementById("main") as HTMLElement }
|
||||||
|
|
||||||
|
factory<AuthUIModel> { DefaultAuthUIModel(get(), get()) }
|
||||||
|
factory { AuthUIViewModel(get()) }
|
||||||
|
factory { AuthView(get(), get(UIScopeQualifier)) }
|
||||||
|
|
||||||
|
factory<PostCreateUIModel> { DefaultPostCreateUIModel(get(), get()) }
|
||||||
|
factory { PostCreateUIViewModel(get()) }
|
||||||
|
factory { PostCreateView(get(), getAllDistinct(), get(UIScopeQualifier)) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
getAllDistinct<UIFSMHandler.Registrator>().forEach {
|
strictlyOn<AuthUIFSMState>(get<AuthView>())
|
||||||
with(it) {
|
|
||||||
include()
|
// Костыль, в JS на момент Пн дек 6 14:19:29 +06 2021 если использовать strictlyOn генерируются
|
||||||
}
|
// некорректные безымянные классы (у них отсутствует метод handleState)
|
||||||
}
|
class DefaultStateHandlerWrapper<T : UIFSMState>(
|
||||||
|
private val klass: KClass<out UIFSMHandler<T>>,
|
||||||
|
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 = get<AuthSettings>()
|
||||||
|
authSettings.loadingJob.join()
|
||||||
|
if (authSettings.authorizedDIModule.value == null) {
|
||||||
|
error("Can't perform state $state: Auth module was not initialized")
|
||||||
|
} else {
|
||||||
|
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> registerHandler(
|
||||||
|
klass: KClass<out UIFSMHandler<T>>,
|
||||||
|
qualifier: Qualifier? = null,
|
||||||
|
parameters: ((T) -> ParametersHolder)? = null
|
||||||
|
) = add(
|
||||||
|
DefaultStateHandlerWrapper<T>(
|
||||||
|
klass,
|
||||||
|
T::class,
|
||||||
|
qualifier,
|
||||||
|
parameters
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
registerHandler(PostCreateView::class)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package dev.inmo.postssystem.client
|
package dev.inmo.postssystem.client
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.client.ui.fsm.*
|
||||||
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
||||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
|
import kotlinx.serialization.StringFormat
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
import org.w3c.dom.url.URL
|
import org.w3c.dom.url.URL
|
||||||
|
|
||||||
private fun History.refreshHistory(
|
private fun History.refreshHistory(
|
||||||
states: Iterable<UIFSMState>,
|
states: Iterable<UIFSMState>,
|
||||||
fillers: List<UIFSMStateSearchParamsHandler>
|
|
||||||
) {
|
) {
|
||||||
val currentUrl = window.location.pathname
|
val currentUrl = window.location.pathname
|
||||||
val currentParams = (URL(window.location.href)).searchParams
|
val params = states.mapNotNull<UIFSMState, Pair<String, String>> {
|
||||||
val params = states.flatMap<UIFSMState, Pair<String, String>> { state ->
|
when (it) {
|
||||||
fillers.flatMap {
|
is AuthUIFSMState -> null
|
||||||
it.takeParams(state, currentParams)
|
is CreatePostUIFSMState -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pushState(
|
pushState(
|
||||||
@ -24,11 +24,9 @@ private fun History.refreshHistory(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun takeStates(initialState: UIFSMState, fillers: List<UIFSMStateSearchParamsHandler>): List<UIFSMState> {
|
private fun takeStates(initialState: UIFSMState): List<UIFSMState> {
|
||||||
val params = (URL(window.location.href)).searchParams
|
val params = (URL(window.location.href)).searchParams
|
||||||
val additionalStates = fillers.mapNotNull {
|
val additionalStates = listOfNotNull<UIFSMState>()
|
||||||
it.takeState(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
return additionalStates + listOfNotNull(
|
return additionalStates + listOfNotNull(
|
||||||
if (additionalStates.isEmpty()) {
|
if (additionalStates.isEmpty()) {
|
||||||
@ -43,13 +41,12 @@ private fun takeStates(initialState: UIFSMState, fillers: List<UIFSMStateSearchP
|
|||||||
|
|
||||||
class JSUIFSMStatesRepo(
|
class JSUIFSMStatesRepo(
|
||||||
private val history: History,
|
private val history: History,
|
||||||
private val initialState: UIFSMState,
|
private val initialState: UIFSMState = DefaultAuthUIFSMState
|
||||||
private val fillers: List<UIFSMStateSearchParamsHandler>
|
|
||||||
) : DefaultStatesManagerRepo<UIFSMState> {
|
) : DefaultStatesManagerRepo<UIFSMState> {
|
||||||
private val statesMap = mutableMapOf<String, UIFSMState>()
|
private val statesMap = mutableMapOf<String, UIFSMState>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val states = takeStates(initialState, fillers)
|
val states = takeStates(initialState)
|
||||||
states.forEach {
|
states.forEach {
|
||||||
statesMap[it.context] = it
|
statesMap[it.context] = it
|
||||||
}
|
}
|
||||||
@ -71,12 +68,12 @@ class JSUIFSMStatesRepo(
|
|||||||
|
|
||||||
override suspend fun removeState(state: UIFSMState) {
|
override suspend fun removeState(state: UIFSMState) {
|
||||||
statesMap.remove((state.context as? String) ?: return)
|
statesMap.remove((state.context as? String) ?: return)
|
||||||
history.refreshHistory(statesMap.values, fillers)
|
history.refreshHistory(statesMap.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun set(state: UIFSMState) {
|
override suspend fun set(state: UIFSMState) {
|
||||||
console.log(state)
|
console.log(state)
|
||||||
statesMap[state.context] = state
|
statesMap[state.context] = state
|
||||||
history.refreshHistory(statesMap.values, fillers)
|
history.refreshHistory(statesMap.values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package dev.inmo.postssystem.client
|
package dev.inmo.postssystem.client
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.client.ui.fsm.UIFSMQualifier
|
||||||
|
import dev.inmo.postssystem.client.ui.fsm.UIFSMState
|
||||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||||
import dev.inmo.postssystem.features.common.common.DefaultQualifiers
|
|
||||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
window.addEventListener("load", {
|
window.addEventListener("load", {
|
||||||
val koin = baseKoin()
|
val koin = baseKoin()
|
||||||
val uiStatesMachine = koin.get<StatesMachine<UIFSMState>>(DefaultQualifiers.UIFSMQualifier)
|
val uiStatesMachine = koin.get<StatesMachine<UIFSMState>>(UIFSMQualifier)
|
||||||
uiStatesMachine.start(koin.get())
|
uiStatesMachine.start(koin.get())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package dev.inmo.postssystem.client
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
|
||||||
import org.w3c.dom.url.URLSearchParams
|
|
||||||
|
|
||||||
interface UIFSMStateSearchParamsHandler {
|
|
||||||
fun takeParams(state: UIFSMState, currentParams: URLSearchParams): List<Pair<String, String>>
|
|
||||||
fun takeState(params: URLSearchParams): UIFSMState?
|
|
||||||
}
|
|
@ -1,41 +1,28 @@
|
|||||||
package dev.inmo.postssystem.features.auth.client
|
package dev.inmo.postssystem.client.fsm.ui
|
||||||
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import dev.inmo.jsuikit.elements.*
|
import dev.inmo.jsuikit.elements.*
|
||||||
import dev.inmo.jsuikit.modifiers.*
|
import dev.inmo.jsuikit.modifiers.*
|
||||||
import dev.inmo.jsuikit.utils.Attrs
|
import dev.inmo.jsuikit.utils.Attrs
|
||||||
import dev.inmo.micro_utils.coroutines.compose.renderComposableAndLinkToContextAndRoot
|
import dev.inmo.postssystem.client.ui.fsm.*
|
||||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||||
import dev.inmo.postssystem.features.auth.client.ui.AuthUIError.AuthIncorrect
|
import dev.inmo.postssystem.client.utils.renderComposableAndLinkToContext
|
||||||
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.browser.document
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.dom.*
|
import kotlinx.dom.*
|
||||||
import org.jetbrains.compose.web.attributes.InputType
|
import org.jetbrains.compose.web.attributes.InputType
|
||||||
|
import org.jetbrains.compose.web.dom.*
|
||||||
import org.jetbrains.compose.web.dom.Text
|
import org.jetbrains.compose.web.dom.Text
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
|
|
||||||
@ExperimentalStdlibApi
|
|
||||||
@EagerInitialization
|
|
||||||
val loader = DefaultModuleLoader {
|
|
||||||
factory { AuthView(get(), get(DefaultQualifiers.UIScopeQualifier), getAllDistinct()) }
|
|
||||||
singleWithRandomQualifier {
|
|
||||||
UIFSMHandler.Registrator {
|
|
||||||
strictlyOn(get<AuthView>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} + CommonAuthModuleLoader()
|
|
||||||
|
|
||||||
class AuthView(
|
class AuthView(
|
||||||
private val viewModel: AuthUIViewModel,
|
private val viewModel: AuthUIViewModel,
|
||||||
private val uiScope: CoroutineScope,
|
private val uiScope: CoroutineScope
|
||||||
defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
|
) : JSView<AuthUIFSMState>() {
|
||||||
) : JSView<AuthUIFSMState>(defaultExceptionsHandlers) {
|
|
||||||
|
|
||||||
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
||||||
htmlElement: HTMLElement,
|
htmlElement: HTMLElement,
|
||||||
@ -49,7 +36,7 @@ class AuthView(
|
|||||||
val errorText = mutableStateOf<String?>(null)
|
val errorText = mutableStateOf<String?>(null)
|
||||||
|
|
||||||
val root = htmlElement.appendElement("div") {}
|
val root = htmlElement.appendElement("div") {}
|
||||||
val composition = renderComposableAndLinkToContextAndRoot(root) {
|
val composition = renderComposableAndLinkToContext(root) {
|
||||||
val authBtnDisabled = usernameState.value.isBlank() || passwordState.value.isBlank()
|
val authBtnDisabled = usernameState.value.isBlank() || passwordState.value.isBlank()
|
||||||
|
|
||||||
Flex(
|
Flex(
|
||||||
@ -67,13 +54,13 @@ class AuthView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StandardInput(
|
TextField(
|
||||||
InputType.Text,
|
InputType.Text,
|
||||||
usernameState,
|
usernameState,
|
||||||
disabled,
|
disabled,
|
||||||
"Username",
|
"Username",
|
||||||
)
|
)
|
||||||
StandardInput(
|
TextField(
|
||||||
InputType.Password,
|
InputType.Password,
|
||||||
passwordState,
|
passwordState,
|
||||||
disabled,
|
disabled,
|
||||||
@ -93,23 +80,23 @@ class AuthView(
|
|||||||
|
|
||||||
val viewJob = viewModel.currentState.subscribeSafelyWithoutExceptions(uiScope) {
|
val viewJob = viewModel.currentState.subscribeSafelyWithoutExceptions(uiScope) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is AuthUIState.Init -> {
|
is InitAuthUIState -> {
|
||||||
disabled.value = false
|
disabled.value = false
|
||||||
|
|
||||||
errorText.value = when (it.showError) {
|
errorText.value = when (it.showError) {
|
||||||
AuthUIError.ServerUnavailable -> "Server unavailable"
|
ServerUnavailableAuthUIError -> "Server unavailable"
|
||||||
AuthIncorrect -> {
|
AuthIncorrectAuthUIError -> {
|
||||||
passwordState.value = ""
|
passwordState.value = ""
|
||||||
"Username or password is incorrect"
|
"Username or password is incorrect"
|
||||||
}
|
}
|
||||||
null -> null
|
null -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AuthUIState.Loading -> {
|
LoadingAuthUIState -> {
|
||||||
disabled.value = true
|
disabled.value = true
|
||||||
errorText.value = null
|
errorText.value = null
|
||||||
}
|
}
|
||||||
AuthUIState.Authorized -> {
|
AuthorizedAuthUIState -> {
|
||||||
completion.complete(state.from)
|
completion.complete(state.from)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.postssystem.client.fsm.ui
|
||||||
|
|
||||||
|
import kotlinx.browser.document
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
|
val mainContainer: Element
|
||||||
|
get() = document.getElementById("main")!!
|
@ -1,13 +1,12 @@
|
|||||||
package dev.inmo.postssystem.features.common.common.ui
|
package dev.inmo.postssystem.client.fsm.ui
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.client.ui.fsm.UIFSMHandler
|
||||||
|
import dev.inmo.postssystem.client.ui.fsm.UIFSMState
|
||||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||||
import dev.inmo.postssystem.features.common.common.ui.fsm.*
|
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
abstract class JSView<T : UIFSMState>(
|
abstract class JSView<T : UIFSMState> : UIFSMHandler<T> {
|
||||||
override val defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
|
|
||||||
) : UIFSMHandler<T> {
|
|
||||||
open suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
open suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
||||||
htmlElement: HTMLElement,
|
htmlElement: HTMLElement,
|
||||||
state: T
|
state: T
|
@ -1,19 +1,16 @@
|
|||||||
package dev.inmo.postssystem.services.posts.client
|
package dev.inmo.postssystem.client.fsm.ui
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import dev.inmo.jsuikit.elements.*
|
import dev.inmo.jsuikit.elements.*
|
||||||
import dev.inmo.jsuikit.modifiers.*
|
import dev.inmo.jsuikit.modifiers.*
|
||||||
import dev.inmo.jsuikit.utils.Attrs
|
import dev.inmo.jsuikit.utils.Attrs
|
||||||
import dev.inmo.micro_utils.coroutines.compose.renderComposableAndLinkToContextAndRoot
|
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||||
import dev.inmo.postssystem.features.auth.client.registerAfterAuthHandler
|
import dev.inmo.postssystem.client.ui.fsm.CreatePostUIFSMState
|
||||||
import dev.inmo.postssystem.features.common.common.*
|
import dev.inmo.postssystem.client.ui.fsm.UIFSMState
|
||||||
import dev.inmo.postssystem.features.common.common.ui.JSView
|
import dev.inmo.postssystem.client.utils.renderComposableAndLinkToContext
|
||||||
import dev.inmo.postssystem.features.common.common.ui.fsm.*
|
|
||||||
import dev.inmo.postssystem.features.content.client.ContentClientProvider
|
import dev.inmo.postssystem.features.content.client.ContentClientProvider
|
||||||
import dev.inmo.postssystem.features.content.common.Content
|
import dev.inmo.postssystem.features.content.common.Content
|
||||||
import dev.inmo.postssystem.services.posts.client.ui.create.PostsCreateUIFSMState
|
|
||||||
import dev.inmo.postssystem.services.posts.client.ui.create.PostCreateUIViewModel
|
import dev.inmo.postssystem.services.posts.client.ui.create.PostCreateUIViewModel
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -21,31 +18,20 @@ import org.jetbrains.compose.web.dom.Div
|
|||||||
import org.jetbrains.compose.web.dom.Text
|
import org.jetbrains.compose.web.dom.Text
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
val jsLoader = DefaultModuleLoader {
|
|
||||||
factory { PostCreateView(get(), getAllDistinct(), get(DefaultQualifiers.UIScopeQualifier), getAllDistinct()) }
|
|
||||||
|
|
||||||
singleWithRandomQualifier<UIFSMHandler.Registrator> {
|
|
||||||
UIFSMHandler.Registrator {
|
|
||||||
registerAfterAuthHandler(getKoin(), PostCreateView::class)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PostCreateView(
|
class PostCreateView(
|
||||||
private val createPostCreateUIModel: PostCreateUIViewModel,
|
private val createPostCreateUIModel: PostCreateUIViewModel,
|
||||||
private val contentClientProviders: List<ContentClientProvider>,
|
private val contentClientProviders: List<ContentClientProvider>,
|
||||||
private val uiScope: CoroutineScope,
|
private val uiScope: CoroutineScope
|
||||||
defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
|
) : JSView<CreatePostUIFSMState>() {
|
||||||
) : JSView<PostsCreateUIFSMState>(defaultExceptionsHandlers) {
|
|
||||||
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
||||||
htmlElement: HTMLElement,
|
htmlElement: HTMLElement,
|
||||||
state: PostsCreateUIFSMState
|
state: CreatePostUIFSMState
|
||||||
): UIFSMState? {
|
): UIFSMState? {
|
||||||
val result = CompletableDeferred<UIFSMState?>()
|
val result = CompletableDeferred<UIFSMState?>()
|
||||||
|
|
||||||
val contentProvidersList = mutableStateListOf<Pair<ContentClientProvider, MutableState<Content?>>>()
|
val contentProvidersList = mutableStateListOf<Pair<ContentClientProvider, MutableState<Content?>>>()
|
||||||
|
|
||||||
renderComposableAndLinkToContextAndRoot(htmlElement) {
|
renderComposableAndLinkToContext(htmlElement) {
|
||||||
Flex(
|
Flex(
|
||||||
UIKitFlex.Alignment.Horizontal.Center
|
UIKitFlex.Alignment.Horizontal.Center
|
||||||
) {
|
) {
|
@ -0,0 +1,19 @@
|
|||||||
|
package dev.inmo.postssystem.client.utils
|
||||||
|
|
||||||
|
import dev.inmo.postssystem.features.files.common.FullFileInfo
|
||||||
|
import dev.inmo.micro_utils.common.toArrayBuffer
|
||||||
|
import io.ktor.utils.io.core.readBytes
|
||||||
|
import kotlinx.browser.document
|
||||||
|
import org.w3c.dom.HTMLAnchorElement
|
||||||
|
import org.w3c.dom.url.URL
|
||||||
|
import org.w3c.files.Blob
|
||||||
|
|
||||||
|
fun triggerDownloadFile(fullFileInfo: FullFileInfo) {
|
||||||
|
val hiddenElement = document.createElement("a") as HTMLAnchorElement
|
||||||
|
|
||||||
|
val url = URL.createObjectURL(Blob(arrayOf(fullFileInfo.inputProvider().readBytes().toArrayBuffer())))
|
||||||
|
hiddenElement.href = url
|
||||||
|
hiddenElement.target = "_blank"
|
||||||
|
hiddenElement.download = fullFileInfo.name.name
|
||||||
|
hiddenElement.click()
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package dev.inmo.postssystem.client.utils
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.jetbrains.compose.web.dom.DOMScope
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
fun Composition.linkWithJob(job: Job) {
|
||||||
|
job.invokeOnCompletion {
|
||||||
|
this@linkWithJob.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Composition.linkWithContext(coroutineContext: CoroutineContext) = linkWithJob(coroutineContext.job)
|
||||||
|
|
||||||
|
suspend fun <TElement : Element> renderComposableAndLinkToContext(
|
||||||
|
root: TElement,
|
||||||
|
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
|
||||||
|
content: @Composable DOMScope<TElement>.() -> Unit
|
||||||
|
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
|
||||||
|
linkWithContext(
|
||||||
|
currentCoroutineContext()
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package dev.inmo.postssystem.client.utils
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import dev.inmo.postssystem.features.files.common.FullFileInfo
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
|
||||||
|
import dev.inmo.micro_utils.mime_types.findBuiltinMimeType
|
||||||
|
import dev.inmo.postssystem.features.common.common.BytesBasedInputProvider
|
||||||
|
import io.ktor.utils.io.core.ByteReadPacket
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import org.khronos.webgl.ArrayBuffer
|
||||||
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
import org.w3c.dom.events.Event
|
||||||
|
import org.w3c.files.FileReader
|
||||||
|
import org.w3c.files.get
|
||||||
|
|
||||||
|
fun uploadFileCallbackForHTMLInputChange(
|
||||||
|
onSet: (FullFileInfo) -> Unit
|
||||||
|
): (Event) -> Unit = {
|
||||||
|
(it.target as? HTMLInputElement) ?.apply {
|
||||||
|
files ?.also { files ->
|
||||||
|
files[0] ?.also { file ->
|
||||||
|
val reader: FileReader = FileReader()
|
||||||
|
|
||||||
|
reader.onload = {
|
||||||
|
val bytes = ((it.target.asDynamic()).result as ArrayBuffer).toByteArray()
|
||||||
|
onSet(
|
||||||
|
FullFileInfo(
|
||||||
|
FileName(file.name),
|
||||||
|
findBuiltinMimeType(file.type) ?: KnownMimeTypes.Any,
|
||||||
|
BytesBasedInputProvider(bytes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.readAsArrayBuffer(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fileCallbackForHTMLInputChange(
|
||||||
|
onSet: (MPPFile) -> Unit
|
||||||
|
): (Event) -> Unit = {
|
||||||
|
(it.target as? HTMLInputElement) ?.apply {
|
||||||
|
files ?.also { files ->
|
||||||
|
files[0] ?.also { file ->
|
||||||
|
onSet(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uploadFileCallbackForHTMLInputChange(
|
||||||
|
output: MutableState<FullFileInfo?>
|
||||||
|
): (Event) -> Unit = uploadFileCallbackForHTMLInputChange {
|
||||||
|
output.value = it
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uploadFileCallbackForHTMLInputChange(
|
||||||
|
output: MutableStateFlow<FullFileInfo?>
|
||||||
|
): (Event) -> Unit = uploadFileCallbackForHTMLInputChange {
|
||||||
|
output.value = it
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fileCallbackForHTMLInputChange(
|
||||||
|
output: MutableState<MPPFile?>
|
||||||
|
): (Event) -> Unit = fileCallbackForHTMLInputChange {
|
||||||
|
output.value = it
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fileCallbackForHTMLInputChange(
|
||||||
|
output: MutableStateFlow<MPPFile?>
|
||||||
|
): (Event) -> Unit = fileCallbackForHTMLInputChange {
|
||||||
|
output.value = it
|
||||||
|
}
|
||||||
|
|
@ -26,12 +26,12 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
||||||
buildToolsVersion libs.versions.android.props.buildTools.get()
|
buildToolsVersion "$android_buildToolsVersion"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion libs.versions.android.props.minSdk.get().toInteger()
|
minSdkVersion "$android_minSdkVersion".toInteger()
|
||||||
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
targetSdkVersion "$android_compileSdkVersion".toInteger()
|
||||||
versionCode "${android_code_version}".toInteger()
|
versionCode "${android_code_version}".toInteger()
|
||||||
versionName "$version"
|
versionName "$version"
|
||||||
}
|
}
|
||||||
|
@ -13,21 +13,6 @@ allprojects {
|
|||||||
projectByName(name)
|
projectByName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
allTargetsConfiguration = { ->
|
|
||||||
kotlin {
|
|
||||||
targets.all {
|
|
||||||
compilations.all {
|
|
||||||
kotlinOptions {
|
|
||||||
freeCompilerArgs += [
|
|
||||||
"-P",
|
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
||||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||||
mppJsProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsProject.gradle"
|
mppJsProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsProject.gradle"
|
||||||
|
@ -2,7 +2,6 @@ plugins {
|
|||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
alias(libs.plugins.compose)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@ -12,7 +11,6 @@ kotlin {
|
|||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":postssystem.features.common.client")
|
api project(":postssystem.features.common.client")
|
||||||
api project(":postssystem.features.status.client")
|
|
||||||
api project(":postssystem.features.auth.common")
|
api project(":postssystem.features.auth.common")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package dev.inmo.postssystem.features.auth.client
|
|
||||||
|
|
||||||
import org.koin.core.qualifier.StringQualifier
|
|
||||||
|
|
||||||
object AuthorizedQualifiers {
|
|
||||||
val CredsQualifier = StringQualifier("creds")
|
|
||||||
val ServerUrlQualifier = StringQualifier("serverUrl")
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
)
|
|
@ -5,11 +5,11 @@ import dev.inmo.postssystem.features.users.common.User
|
|||||||
import dev.inmo.micro_utils.common.*
|
import dev.inmo.micro_utils.common.*
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
import io.ktor.client.HttpClientConfig
|
import io.ktor.client.HttpClientConfig
|
||||||
import io.ktor.client.plugins.cookies.AcceptAllCookiesStorage
|
import io.ktor.client.features.cookies.*
|
||||||
import io.ktor.client.plugins.cookies.HttpCookies
|
import io.ktor.client.features.expectSuccess
|
||||||
import io.ktor.client.plugins.expectSuccess
|
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.statement.HttpReceivePipeline
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@ -25,6 +25,10 @@ fun HttpClientConfig<*>.installClientAuthenticator(
|
|||||||
onUserRetrieved: suspend (User?) -> Unit,
|
onUserRetrieved: suspend (User?) -> Unit,
|
||||||
onAuthKeyInvalidated: suspend () -> Unit
|
onAuthKeyInvalidated: suspend () -> Unit
|
||||||
) {
|
) {
|
||||||
|
// install(Logging) {
|
||||||
|
// logger = Logger.DEFAULT
|
||||||
|
// level = LogLevel.HEADERS
|
||||||
|
// }
|
||||||
install(HttpCookies) {
|
install(HttpCookies) {
|
||||||
// Will keep an in-memory map with all the cookies from previous requests.
|
// Will keep an in-memory map with all the cookies from previous requests.
|
||||||
storage = AcceptAllCookiesStorage()
|
storage = AcceptAllCookiesStorage()
|
||||||
@ -94,13 +98,13 @@ fun HttpClientConfig<*>.installClientAuthenticator(
|
|||||||
|
|
||||||
receivePipeline.intercept(HttpReceivePipeline.Before) {
|
receivePipeline.intercept(HttpReceivePipeline.Before) {
|
||||||
if (
|
if (
|
||||||
it.request.url.toString().startsWith(baseUrl)
|
context.request.url.toString().startsWith(baseUrl)
|
||||||
&& it.status == HttpStatusCode.Unauthorized
|
&& context.response.status == HttpStatusCode.Unauthorized
|
||||||
) {
|
) {
|
||||||
authMutex.withLock { refreshToken() }
|
authMutex.withLock { refreshToken() }
|
||||||
val newResponse = it.call.client.request {
|
val newResponse = context.client ?.request<HttpResponse>{
|
||||||
takeFrom(it.request)
|
takeFrom(context.request)
|
||||||
}
|
} ?: return@intercept
|
||||||
proceedWith(newResponse)
|
proceedWith(newResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
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
|
|
||||||
import kotlin.js.JsExport
|
|
||||||
|
|
||||||
internal fun CommonAuthModuleLoader() = 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()) }
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package dev.inmo.postssystem.features.auth.client.ui
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
|
|
||||||
|
|
||||||
data class AuthUIFSMState(
|
|
||||||
override val from: UIFSMState?,
|
|
||||||
override val context: String = "main"
|
|
||||||
) : UIFSMState
|
|
@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.postssystem.features.auth.client.ui
|
package dev.inmo.postssystem.features.auth.client.ui
|
||||||
|
|
||||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||||
import dev.inmo.postssystem.features.common.common.ui.UIModel
|
import dev.inmo.postssystem.features.common.common.UIModel
|
||||||
|
|
||||||
interface AuthUIModel : UIModel<AuthUIState> {
|
interface AuthUIModel : UIModel<AuthUIState> {
|
||||||
suspend fun initAuth(serverUrl: String, creds: AuthCreds)
|
suspend fun initAuth(serverUrl: String, creds: AuthCreds)
|
||||||
|
@ -3,23 +3,18 @@ package dev.inmo.postssystem.features.auth.client.ui
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed interface AuthUIError {
|
sealed class AuthUIError
|
||||||
// @Serializable
|
@Serializable
|
||||||
object ServerUnavailable : AuthUIError
|
object ServerUnavailableAuthUIError : AuthUIError()
|
||||||
// @Serializable
|
@Serializable
|
||||||
object AuthIncorrect : AuthUIError
|
object AuthIncorrectAuthUIError : AuthUIError()
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed interface AuthUIState {
|
sealed class AuthUIState
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Init(val showError: AuthUIError? = null) : AuthUIState
|
data class InitAuthUIState(val showError: AuthUIError? = null) : AuthUIState()
|
||||||
// @Serializable
|
val DefaultInitAuthUIState = InitAuthUIState()
|
||||||
object Loading : AuthUIState
|
@Serializable
|
||||||
// @Serializable
|
object LoadingAuthUIState : AuthUIState()
|
||||||
object Authorized : AuthUIState
|
@Serializable
|
||||||
|
object AuthorizedAuthUIState : AuthUIState()
|
||||||
companion object {
|
|
||||||
val DefaultInit = Init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.postssystem.features.auth.client.ui
|
package dev.inmo.postssystem.features.auth.client.ui
|
||||||
|
|
||||||
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
import dev.inmo.postssystem.features.auth.common.AuthCreds
|
||||||
import dev.inmo.postssystem.features.common.common.ui.UIViewModel
|
import dev.inmo.postssystem.features.common.common.UIViewModel
|
||||||
import dev.inmo.postssystem.features.users.common.Username
|
import dev.inmo.postssystem.features.users.common.Username
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
@ -3,20 +3,10 @@ package dev.inmo.postssystem.features.auth.common
|
|||||||
import com.benasher44.uuid.uuid4
|
import com.benasher44.uuid.uuid4
|
||||||
import dev.inmo.postssystem.features.users.common.Username
|
import dev.inmo.postssystem.features.users.common.Username
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import kotlin.jvm.JvmInline
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
sealed interface AuthKey
|
sealed interface AuthKey
|
||||||
|
|
||||||
@Serializable
|
|
||||||
private data class AuthKeySurrogate(
|
|
||||||
val authCreds: AuthCreds?,
|
|
||||||
val token: AuthToken?,
|
|
||||||
val refreshToken: RefreshToken?
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("authcreds")
|
@SerialName("authcreds")
|
||||||
data class AuthCreds(
|
data class AuthCreds(
|
||||||
|
@ -2,18 +2,17 @@ package dev.inmo.postssystem.features.auth.server
|
|||||||
|
|
||||||
import dev.inmo.postssystem.features.auth.common.*
|
import dev.inmo.postssystem.features.auth.common.*
|
||||||
import dev.inmo.postssystem.features.auth.server.tokens.AuthTokensService
|
import dev.inmo.postssystem.features.auth.server.tokens.AuthTokensService
|
||||||
import dev.inmo.postssystem.features.common.server.ApplicationAuthenticationConfigurator
|
import dev.inmo.postssystem.features.common.server.sessions.ApplicationAuthenticationConfigurator
|
||||||
import dev.inmo.postssystem.features.users.common.User
|
import dev.inmo.postssystem.features.users.common.User
|
||||||
import dev.inmo.micro_utils.coroutines.safely
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
import dev.inmo.micro_utils.ktor.server.*
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.*
|
import dev.inmo.micro_utils.ktor.server.configurators.*
|
||||||
|
import io.ktor.application.*
|
||||||
|
import io.ktor.auth.*
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.call
|
import io.ktor.response.respond
|
||||||
import io.ktor.server.auth.*
|
import io.ktor.routing.*
|
||||||
import io.ktor.server.request.receive
|
import io.ktor.sessions.*
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.sessions.sessions
|
|
||||||
import kotlinx.serialization.builtins.nullable
|
import kotlinx.serialization.builtins.nullable
|
||||||
|
|
||||||
data class AuthUserPrincipal(
|
data class AuthUserPrincipal(
|
||||||
@ -25,9 +24,11 @@ fun User.principal() = AuthUserPrincipal(this)
|
|||||||
|
|
||||||
class AuthenticationRoutingConfigurator(
|
class AuthenticationRoutingConfigurator(
|
||||||
private val authFeature: AuthFeature,
|
private val authFeature: AuthFeature,
|
||||||
private val authTokensService: AuthTokensService
|
private val authTokensService: AuthTokensService,
|
||||||
|
private val unifiedRouter: UnifiedRouter
|
||||||
) : ApplicationRoutingConfigurator.Element, ApplicationAuthenticationConfigurator.Element {
|
) : ApplicationRoutingConfigurator.Element, ApplicationAuthenticationConfigurator.Element {
|
||||||
override fun Route.invoke() {
|
override fun Route.invoke() {
|
||||||
|
unifiedRouter.apply {
|
||||||
route(authRootPathPart) {
|
route(authRootPathPart) {
|
||||||
post(authAuthPathPart) {
|
post(authAuthPathPart) {
|
||||||
safely(
|
safely(
|
||||||
@ -40,7 +41,7 @@ class AuthenticationRoutingConfigurator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
val creds = call.receive<AuthCreds>()
|
val creds = uniload(AuthCreds.serializer())
|
||||||
|
|
||||||
val tokenInfo = authFeature.auth(creds)
|
val tokenInfo = authFeature.auth(creds)
|
||||||
|
|
||||||
@ -50,7 +51,10 @@ class AuthenticationRoutingConfigurator(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
call.sessions.set(tokenSessionKey, tokenInfo.token)
|
call.sessions.set(tokenSessionKey, tokenInfo.token)
|
||||||
call.respond(tokenInfo)
|
unianswer(
|
||||||
|
AuthTokenInfo.serializer().nullable,
|
||||||
|
tokenInfo
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,7 +68,7 @@ class AuthenticationRoutingConfigurator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
val refreshToken = call.receive<RefreshToken>()
|
val refreshToken = uniload(RefreshToken.serializer())
|
||||||
|
|
||||||
val tokenInfo = authFeature.refresh(refreshToken)
|
val tokenInfo = authFeature.refresh(refreshToken)
|
||||||
|
|
||||||
@ -74,7 +78,10 @@ class AuthenticationRoutingConfigurator(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
call.sessions.set(tokenSessionKey, tokenInfo.token)
|
call.sessions.set(tokenSessionKey, tokenInfo.token)
|
||||||
call.respond(tokenInfo)
|
unianswer(
|
||||||
|
AuthTokenInfo.serializer().nullable,
|
||||||
|
tokenInfo
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,15 +95,19 @@ class AuthenticationRoutingConfigurator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
call.respond(
|
unianswer(
|
||||||
authFeature.getMe(call.receive()) ?: HttpStatusCode.NoContent
|
User.serializer().nullable,
|
||||||
|
authFeature.getMe(
|
||||||
|
uniload(AuthToken.serializer())
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun AuthenticationConfig.invoke() {
|
override fun Authentication.Configuration.invoke() {
|
||||||
session<AuthToken> {
|
session<AuthToken> {
|
||||||
validate {
|
validate {
|
||||||
val result = authTokensService.getUserPrincipal(it)
|
val result = authTokensService.getUserPrincipal(it)
|
||||||
|
@ -4,14 +4,14 @@ import dev.inmo.postssystem.features.auth.common.AuthToken
|
|||||||
import dev.inmo.postssystem.features.common.common.Milliseconds
|
import dev.inmo.postssystem.features.common.common.Milliseconds
|
||||||
import dev.inmo.postssystem.features.auth.common.tokenSessionKey
|
import dev.inmo.postssystem.features.auth.common.tokenSessionKey
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationSessionsConfigurator
|
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationSessionsConfigurator
|
||||||
import io.ktor.server.sessions.*
|
import io.ktor.sessions.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class SessionAuthenticationConfigurator(
|
class SessionAuthenticationConfigurator(
|
||||||
private val maxAge: Milliseconds
|
private val maxAge: Milliseconds
|
||||||
) : ApplicationSessionsConfigurator.Element {
|
) : ApplicationSessionsConfigurator.Element {
|
||||||
private val maxAgeInSeconds = TimeUnit.MILLISECONDS.toSeconds(maxAge)
|
private val maxAgeInSeconds = TimeUnit.MILLISECONDS.toSeconds(maxAge)
|
||||||
override fun SessionsConfig.invoke() {
|
override fun Sessions.Configuration.invoke() {
|
||||||
cookie<AuthToken>(tokenSessionKey) {
|
cookie<AuthToken>(tokenSessionKey) {
|
||||||
cookie.maxAgeInSeconds = maxAgeInSeconds
|
cookie.maxAgeInSeconds = maxAgeInSeconds
|
||||||
serializer = object : SessionSerializer<AuthToken> {
|
serializer = object : SessionSerializer<AuthToken> {
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
|
||||||
id "com.android.library"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
api project(":postssystem.features.client.template.common")
|
|
||||||
api project(":postssystem.features.common.client")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<manifest package="dev.inmo.postssystem.features.client.template.client"/>
|
|
@ -1,17 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
|
||||||
id "com.android.library"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
api project(":postssystem.features.common.common")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<manifest package="dev.inmo.postssystem.features.client.template.common"/>
|
|
@ -1,17 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$mppJavaProjectPresetPath"
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
api project(":postssystem.features.client.template.common")
|
|
||||||
api project(":postssystem.features.common.server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,13 +13,8 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api project(":postssystem.features.common.common")
|
api project(":postssystem.features.common.common")
|
||||||
api libs.microutils.repos.ktor.client
|
api libs.microutils.repos.ktor.client
|
||||||
api libs.ktor.client.auth
|
api "io.ktor:ktor-client-auth:$ktor_version"
|
||||||
api libs.ktor.client.logging
|
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
|
api compose.runtime
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.postssystem.features.common.common
|
package dev.inmo.postssystem.features.common.common
|
||||||
|
|
||||||
class AdditionalModules {
|
object AdditionalModules {
|
||||||
private val additionalModules = mutableListOf<ModuleLoader>()
|
private val additionalModules = mutableListOf<ModuleLoader>()
|
||||||
val modules: List<ModuleLoader>
|
val modules: List<ModuleLoader>
|
||||||
get() = additionalModules.toList()
|
get() = additionalModules.toList()
|
||||||
@ -8,8 +8,4 @@ class AdditionalModules {
|
|||||||
fun addModule(moduleLoader: ModuleLoader): Boolean {
|
fun addModule(moduleLoader: ModuleLoader): Boolean {
|
||||||
return additionalModules.add(moduleLoader)
|
return additionalModules.add(moduleLoader)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
val Default = AdditionalModules()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
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>>(DefaultQualifiers.UIFSMQualifier) {
|
|
||||||
UIFSM(get()) {
|
|
||||||
(this@single to this@UIFSM).apply(get(DefaultQualifiers.FSMHandlersBuilderQualifier))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} + AdditionalModules.Default.modules.map {
|
|
||||||
module {
|
|
||||||
with(it) {
|
|
||||||
load()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}.koin.apply {
|
|
||||||
loadModules(
|
|
||||||
listOf(
|
|
||||||
module { single<Koin> { this@apply } }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
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("UIFSM")
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package dev.inmo.postssystem.features.common.common.ui
|
package dev.inmo.postssystem.features.common.common
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -2,27 +2,6 @@ package dev.inmo.postssystem.features.common.common
|
|||||||
|
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
interface ModuleLoader {
|
fun interface ModuleLoader {
|
||||||
fun Module.load()
|
fun Module.load()
|
||||||
|
|
||||||
class ByCallback(private val loadingBlock: Module.() -> Unit) : ModuleLoader {
|
|
||||||
override fun Module.load() {
|
|
||||||
loadingBlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun ModuleLoader.plus(other: ModuleLoader) = ModuleLoader.ByCallback {
|
|
||||||
with(this@plus) {
|
|
||||||
load()
|
|
||||||
}
|
|
||||||
with (other) {
|
|
||||||
load()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DefaultModuleLoader(loadingBlock: Module.() -> Unit): ModuleLoader.ByCallback {
|
|
||||||
val newModuleLoader = ModuleLoader.ByCallback(loadingBlock)
|
|
||||||
AdditionalModules.Default.addModule(newModuleLoader)
|
|
||||||
return newModuleLoader
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package dev.inmo.postssystem.features.common.common.ui
|
package dev.inmo.postssystem.features.common.common
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
@ -0,0 +1,4 @@
|
|||||||
|
package dev.inmo.postssystem.features.common.common
|
||||||
|
|
||||||
|
interface UIView {
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package dev.inmo.postssystem.features.common.common.ui
|
package dev.inmo.postssystem.features.common.common
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@ -7,7 +7,9 @@ interface UIViewModel<StateType> {
|
|||||||
val currentState: StateFlow<StateType>
|
val currentState: StateFlow<StateType>
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractUIViewModel<StateType>(initState: StateType) : UIViewModel<StateType> {
|
abstract class AbstractUIViewModel<StateType> : UIViewModel<StateType> {
|
||||||
protected val _currentState = DefaultMVVMStateFlow(initState)
|
protected val _currentState = DefaultMVVMStateFlow(initState())
|
||||||
override val currentState: StateFlow<StateType> = _currentState.asStateFlow()
|
override val currentState: StateFlow<StateType> = _currentState.asStateFlow()
|
||||||
|
|
||||||
|
abstract fun initState(): StateType
|
||||||
}
|
}
|
@ -1,4 +0,0 @@
|
|||||||
package dev.inmo.postssystem.features.common.common.ui
|
|
||||||
|
|
||||||
interface UIView {
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
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?>
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
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.*
|
|
||||||
|
|
||||||
interface UIFSMState : State {
|
|
||||||
val from: UIFSMState?
|
|
||||||
get() = null
|
|
||||||
override val context: String
|
|
||||||
get() = "main"
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package dev.inmo.postssystem.features.common.common.ui
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import com.soywiz.klock.DateTime
|
|
||||||
import com.soywiz.klock.ISO8601
|
|
||||||
import org.jetbrains.compose.web.dom.Text
|
|
||||||
|
|
||||||
object DateTimeView {
|
|
||||||
val simpleFormat = ISO8601.BaseIsoDateTimeFormat(
|
|
||||||
"DD.MM.YYYY, hh:mm"
|
|
||||||
)
|
|
||||||
@Composable
|
|
||||||
fun Simple(
|
|
||||||
dateTime: DateTime
|
|
||||||
) {
|
|
||||||
Text(dateTime.local.format(simpleFormat))
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,19 +14,19 @@ kotlin {
|
|||||||
api libs.microutils.serialization.typedserializer
|
api libs.microutils.serialization.typedserializer
|
||||||
api libs.microutils.mimetypes
|
api libs.microutils.mimetypes
|
||||||
api libs.klock
|
api libs.klock
|
||||||
api libs.koin.core
|
api "io.insert-koin:koin-core:$koin_version"
|
||||||
api libs.uuid
|
api "com.benasher44:uuid:$uuid_version"
|
||||||
api libs.ktor.http
|
api "io.ktor:ktor-http:$ktor_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api libs.kotlin.reflect
|
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
androidMain {
|
androidMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api libs.kotlin.reflect
|
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
|
|||||||
import kotlinx.serialization.encoding.Decoder
|
import kotlinx.serialization.encoding.Decoder
|
||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
@Serializer(DateTime::class)
|
||||||
object DateTimeSerializer : KSerializer<DateTime> {
|
object DateTimeSerializer : KSerializer<DateTime> {
|
||||||
override val descriptor: SerialDescriptor
|
override val descriptor: SerialDescriptor
|
||||||
get() = Double.serializer().descriptor
|
get() = Double.serializer().descriptor
|
||||||
|
@ -33,10 +33,9 @@ expect class FileBasedInputProvider : SimpleInputProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable(SimpleInputProviderSerializer::class)
|
@Serializable(SimpleInputProviderSerializer::class)
|
||||||
class CustomInputProvider(
|
class CustomInputProvider(private val provider: () -> Input) : SimpleInputProvider {
|
||||||
override val contentBytes: Long? = null,
|
override val contentBytes: Long?
|
||||||
private val provider: () -> Input
|
get() = null
|
||||||
) : SimpleInputProvider {
|
|
||||||
override fun invoke(): Input = provider()
|
override fun invoke(): Input = provider()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api libs.ktor.server.auth
|
api "io.ktor:ktor-auth:$ktor_version"
|
||||||
api libs.logback
|
api "ch.qos.logback:logback-classic:$logback_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package dev.inmo.postssystem.features.common.server
|
package dev.inmo.postssystem.features.common.server.sessions
|
||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
|
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
|
||||||
import io.ktor.server.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.server.auth.*
|
import io.ktor.auth.Authentication
|
||||||
|
import io.ktor.auth.authentication
|
||||||
|
|
||||||
class ApplicationAuthenticationConfigurator(
|
class ApplicationAuthenticationConfigurator(
|
||||||
private val elements: List<Element>
|
private val elements: List<Element>
|
||||||
) : KtorApplicationConfigurator {
|
) : KtorApplicationConfigurator {
|
||||||
fun interface Element { operator fun AuthenticationConfig.invoke() }
|
fun interface Element { operator fun Authentication.Configuration.invoke() }
|
||||||
|
|
||||||
override fun Application.configure() {
|
override fun Application.configure() {
|
||||||
authentication {
|
authentication {
|
@ -1,9 +1,8 @@
|
|||||||
package dev.inmo.postssystem.features.common.server
|
package dev.inmo.postssystem.features.common.server.sessions
|
||||||
|
|
||||||
import org.koin.core.qualifier.StringQualifier
|
import org.koin.core.qualifier.StringQualifier
|
||||||
|
|
||||||
object Qualifiers {
|
object Qualifiers {
|
||||||
val filesFolderQualifier = StringQualifier("filesFolder")
|
val filesFolderQualifier = StringQualifier("filesFolder")
|
||||||
val commonFilesFolderQualifier = StringQualifier("commonFilesFolder")
|
|
||||||
val usersRolesKeyValueFactoryQualifier = StringQualifier("usersRolesKeyValueFactory")
|
val usersRolesKeyValueFactoryQualifier = StringQualifier("usersRolesKeyValueFactory")
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package dev.inmo.postssystem.features.common.server
|
package dev.inmo.postssystem.features.common.server.sessions
|
||||||
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
@ -1,20 +1,17 @@
|
|||||||
package dev.inmo.postssystem.features.content.binary.client
|
package dev.inmo.postssystem.features.content.binary.client
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import dev.inmo.jsuikit.elements.*
|
import dev.inmo.jsuikit.elements.DefaultButton
|
||||||
import dev.inmo.jsuikit.modifiers.UIKitWidth
|
import dev.inmo.jsuikit.modifiers.UIKitWidth
|
||||||
import dev.inmo.micro_utils.common.selectFile
|
import dev.inmo.micro_utils.common.selectFile
|
||||||
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
|
|
||||||
import dev.inmo.postssystem.features.common.common.*
|
import dev.inmo.postssystem.features.common.common.*
|
||||||
import dev.inmo.postssystem.features.content.client.ContentClientProvider
|
import dev.inmo.postssystem.features.content.client.ContentClientProvider
|
||||||
import dev.inmo.postssystem.features.content.common.*
|
import dev.inmo.postssystem.features.content.common.*
|
||||||
import org.jetbrains.compose.web.dom.Img
|
|
||||||
import org.jetbrains.compose.web.dom.Text
|
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
object LoadingClientModule : ModuleLoader {
|
object LoadingClientModule : ModuleLoader {
|
||||||
init {
|
init {
|
||||||
AdditionalModules.Default.addModule(this)
|
AdditionalModules.addModule(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Module.load() {
|
override fun Module.load() {
|
||||||
@ -44,23 +41,4 @@ object BinaryContentClientProvider : ContentClientProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun renderPreview(content: Content): Boolean {
|
|
||||||
if (content is BinaryContent) {
|
|
||||||
Tile {
|
|
||||||
Card(
|
|
||||||
header = {
|
|
||||||
CardTitle {
|
|
||||||
Text(content.filename.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ package dev.inmo.postssystem.features.content.binary.server
|
|||||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
|
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
|
||||||
import dev.inmo.postssystem.features.common.common.singleWithBinds
|
import dev.inmo.postssystem.features.common.common.singleWithBinds
|
||||||
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
|
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
|
||||||
import dev.inmo.postssystem.features.common.server.Qualifiers
|
import dev.inmo.postssystem.features.common.server.sessions.Qualifiers
|
||||||
import dev.inmo.postssystem.features.common.server.ServerModuleLoader
|
import dev.inmo.postssystem.features.common.server.sessions.ServerModuleLoader
|
||||||
import dev.inmo.postssystem.features.content.common.BinaryContent
|
import dev.inmo.postssystem.features.content.common.BinaryContent
|
||||||
import dev.inmo.postssystem.features.content.server.ServerContentStorageWrapper
|
import dev.inmo.postssystem.features.content.server.ServerContentStorageWrapper
|
||||||
import dev.inmo.postssystem.features.files.common.*
|
import dev.inmo.postssystem.features.files.common.*
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
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()) }
|
|
||||||
}
|
|
@ -10,7 +10,4 @@ interface ContentClientProvider {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun renderNewInstance(state: MutableState<Content?>)
|
fun renderNewInstance(state: MutableState<Content?>)
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun renderPreview(content: Content): Boolean
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.postssystem.features.content.common
|
package dev.inmo.postssystem.features.content.common
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.*
|
import dev.inmo.micro_utils.common.FileName
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
import dev.inmo.micro_utils.mime_types.MimeType
|
import dev.inmo.micro_utils.mime_types.MimeType
|
||||||
import dev.inmo.postssystem.features.common.common.SimpleInputProvider
|
import dev.inmo.postssystem.features.common.common.SimpleInputProvider
|
||||||
import kotlinx.serialization.PolymorphicSerializer
|
import kotlinx.serialization.PolymorphicSerializer
|
||||||
@ -29,18 +30,6 @@ data class BinaryContent(
|
|||||||
) : Content
|
) : Content
|
||||||
|
|
||||||
val ContentSerializer = PolymorphicSerializer(Content::class)
|
val ContentSerializer = PolymorphicSerializer(Content::class)
|
||||||
@Serializable
|
|
||||||
data class ContentWrapper(
|
|
||||||
val content: Content
|
|
||||||
)
|
|
||||||
@Serializable
|
|
||||||
data class ContentsWrapper(
|
|
||||||
val content: List<Content>
|
|
||||||
)
|
|
||||||
@Serializable
|
|
||||||
data class ContentsEithersWrapper(
|
|
||||||
val content: List<Either<ContentId, Content>>
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content which is already registered in database. Using its [id] you can retrieve all known
|
* Content which is already registered in database. Using its [id] you can retrieve all known
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
package dev.inmo.postssystem.features.content.text.client
|
package dev.inmo.postssystem.features.content.text.client
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import dev.inmo.jsuikit.elements.Tile
|
|
||||||
import dev.inmo.jsuikit.modifiers.UIKitWidth
|
import dev.inmo.jsuikit.modifiers.UIKitWidth
|
||||||
import dev.inmo.jsuikit.modifiers.include
|
import dev.inmo.jsuikit.modifiers.include
|
||||||
import dev.inmo.postssystem.features.common.common.*
|
import dev.inmo.postssystem.features.common.common.*
|
||||||
import dev.inmo.postssystem.features.content.client.ContentClientProvider
|
import dev.inmo.postssystem.features.content.client.ContentClientProvider
|
||||||
import dev.inmo.postssystem.features.content.common.Content
|
import dev.inmo.postssystem.features.content.common.Content
|
||||||
import dev.inmo.postssystem.features.content.text.common.TextContent
|
import dev.inmo.postssystem.features.content.text.common.TextContent
|
||||||
import org.jetbrains.compose.web.dom.Text
|
|
||||||
import org.jetbrains.compose.web.dom.TextArea
|
import org.jetbrains.compose.web.dom.TextArea
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
object LoadingClientModule : ModuleLoader {
|
object LoadingClientModule : ModuleLoader {
|
||||||
init {
|
init {
|
||||||
AdditionalModules.Default.addModule(this)
|
AdditionalModules.addModule(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Module.load() {
|
override fun Module.load() {
|
||||||
@ -38,17 +36,4 @@ object TextContentClientProvider : ContentClientProvider {
|
|||||||
onInput { state.value = TextContent(it.value) }
|
onInput { state.value = TextContent(it.value) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun renderPreview(content: Content): Boolean {
|
|
||||||
if (content is TextContent) {
|
|
||||||
Tile {
|
|
||||||
Text(content.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.postssystem.features.content.text.server
|
package dev.inmo.postssystem.features.content.text.server
|
||||||
|
|
||||||
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
|
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
|
||||||
import dev.inmo.postssystem.features.common.server.ServerModuleLoader
|
import dev.inmo.postssystem.features.common.server.sessions.ServerModuleLoader
|
||||||
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
|
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
|
||||||
import dev.inmo.postssystem.features.content.server.ServerContentStorageWrapper
|
import dev.inmo.postssystem.features.content.server.ServerContentStorageWrapper
|
||||||
import dev.inmo.postssystem.features.content.text.common.TextContent
|
import dev.inmo.postssystem.features.content.text.common.TextContent
|
||||||
|
@ -12,7 +12,6 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api project(":postssystem.features.files.common")
|
api project(":postssystem.features.files.common")
|
||||||
api project(":postssystem.features.common.client")
|
api project(":postssystem.features.common.client")
|
||||||
api project(":postssystem.features.auth.client")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
|||||||
import dev.inmo.micro_utils.repos.ktor.client.crud.KtorReadStandardCrudRepo
|
import dev.inmo.micro_utils.repos.ktor.client.crud.KtorReadStandardCrudRepo
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.post
|
||||||
import io.ktor.client.request.setBody
|
|
||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
import io.ktor.client.statement.readBytes
|
import io.ktor.client.statement.readBytes
|
||||||
import kotlinx.serialization.BinaryFormat
|
import kotlinx.serialization.BinaryFormat
|
||||||
@ -25,14 +24,15 @@ class ClientReadFilesStorage(
|
|||||||
MetaFileInfoStorageWrapper.serializer().nullable,
|
MetaFileInfoStorageWrapper.serializer().nullable,
|
||||||
FileId.serializer()
|
FileId.serializer()
|
||||||
) {
|
) {
|
||||||
|
private val unifiedRequester = UnifiedRequester(client, serialFormat)
|
||||||
private val fullFilesPath = buildStandardUrl(baseUrl, filesRootPathPart)
|
private val fullFilesPath = buildStandardUrl(baseUrl, filesRootPathPart)
|
||||||
private val fullFilesGetBytesPath = buildStandardUrl(
|
private val fullFilesGetBytesPath = buildStandardUrl(
|
||||||
fullFilesPath,
|
fullFilesPath,
|
||||||
filesGetFilesPathPart
|
filesGetFilesPathPart
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getBytes(id: FileId): ByteArray = client.post(fullFilesGetBytesPath) {
|
override suspend fun getBytes(id: FileId): ByteArray = client.post<HttpResponse>(fullFilesGetBytesPath) {
|
||||||
setBody(serialFormat.encodeToByteArray(FileId.serializer(), id))
|
body = serialFormat.encodeToByteArray(FileId.serializer(), id)
|
||||||
}.readBytes()
|
}.readBytes()
|
||||||
|
|
||||||
override suspend fun getFullFileInfo(
|
override suspend fun getFullFileInfo(
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
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,10 @@ import kotlinx.serialization.Serializable
|
|||||||
sealed interface FileInfo {
|
sealed interface FileInfo {
|
||||||
val name: FileName
|
val name: FileName
|
||||||
val mimeType: MimeType
|
val mimeType: MimeType
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun serializer(): KSerializer<FileInfo> = FileInfoSerializer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object FileInfoSerializer : KSerializer<FileInfo> by TypedSerializer(
|
object FileInfoSerializer : KSerializer<FileInfo> by TypedSerializer(
|
||||||
|
@ -4,49 +4,60 @@ import dev.inmo.postssystem.features.files.common.*
|
|||||||
import dev.inmo.postssystem.features.files.common.storage.*
|
import dev.inmo.postssystem.features.files.common.storage.*
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
import dev.inmo.micro_utils.ktor.server.*
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
||||||
import dev.inmo.micro_utils.repos.ktor.server.crud.*
|
import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadStandardCrudRepoRoutes
|
||||||
import io.ktor.http.HttpStatusCode
|
import dev.inmo.micro_utils.repos.ktor.server.crud.configureWriteStandardCrudRepoRoutes
|
||||||
import io.ktor.http.decodeURLQueryComponent
|
import io.ktor.application.call
|
||||||
import io.ktor.server.application.call
|
import io.ktor.auth.authenticate
|
||||||
import io.ktor.server.auth.authenticate
|
import io.ktor.response.respondBytes
|
||||||
import io.ktor.server.request.receive
|
import io.ktor.routing.*
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.response.respondBytes
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import kotlinx.serialization.builtins.nullable
|
import kotlinx.serialization.builtins.nullable
|
||||||
|
|
||||||
class FilesRoutingConfigurator(
|
class FilesRoutingConfigurator(
|
||||||
private val filesStorage: ReadFilesStorage,
|
private val filesStorage: ReadFilesStorage,
|
||||||
private val writeFilesStorage: WriteFilesStorage?,
|
private val writeFilesStorage: WriteFilesStorage?,
|
||||||
|
private val unifierRouter: UnifiedRouter
|
||||||
) : ApplicationRoutingConfigurator.Element {
|
) : ApplicationRoutingConfigurator.Element {
|
||||||
constructor(filesStorage: FilesStorage) : this(filesStorage, filesStorage)
|
constructor(filesStorage: FilesStorage, unifierRouter: UnifiedRouter) : this(filesStorage, filesStorage, unifierRouter)
|
||||||
|
|
||||||
override fun Route.invoke() {
|
override fun Route.invoke() {
|
||||||
authenticate {
|
authenticate {
|
||||||
route(filesRootPathPart) {
|
route(filesRootPathPart) {
|
||||||
configureReadCRUDRepoRoutes(
|
configureReadStandardCrudRepoRoutes(
|
||||||
filesStorage,
|
filesStorage,
|
||||||
::FileId
|
MetaFileInfoStorageWrapper.serializer(),
|
||||||
|
MetaFileInfoStorageWrapper.serializer().nullable,
|
||||||
|
FileId.serializer(),
|
||||||
|
unifierRouter
|
||||||
)
|
)
|
||||||
writeFilesStorage ?.let {
|
writeFilesStorage ?.let {
|
||||||
configureWriteCRUDRepoRoutes(writeFilesStorage)
|
configureWriteStandardCrudRepoRoutes(
|
||||||
|
writeFilesStorage,
|
||||||
|
FullFileInfoStorageWrapper.serializer(),
|
||||||
|
FullFileInfoStorageWrapper.serializer().nullable,
|
||||||
|
FullFileInfo.serializer(),
|
||||||
|
FileId.serializer(),
|
||||||
|
unifierRouter
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unifierRouter.apply {
|
||||||
post(filesGetFilesPathPart) {
|
post(filesGetFilesPathPart) {
|
||||||
call.respondBytes(
|
call.respondBytes(
|
||||||
filesStorage.getBytes(
|
filesStorage.getBytes(
|
||||||
call.receive()
|
uniload(FileId.serializer())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
get(filesGetFullFileInfoPathPart) {
|
get(filesGetFullFileInfoPathPart) {
|
||||||
call.respond(
|
unianswer(
|
||||||
|
FullFileInfoStorageWrapper.serializer().nullable,
|
||||||
filesStorage.getFullFileInfo(
|
filesStorage.getFullFileInfo(
|
||||||
FileId(call.getParameterOrSendError(filesFileIdParameter) ?.decodeURLQueryComponent() ?: return@get)
|
decodeUrlQueryValueOrSendError(filesFileIdParameter, FileId.serializer()) ?: return@get
|
||||||
) ?: HttpStatusCode.NoContent
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -2,9 +2,7 @@ package dev.inmo.postssystem.features.posts.common
|
|||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import com.soywiz.klock.DateTime
|
||||||
import dev.inmo.postssystem.features.common.common.DateTimeSerializer
|
import dev.inmo.postssystem.features.common.common.DateTimeSerializer
|
||||||
import dev.inmo.postssystem.features.content.common.Content
|
|
||||||
import dev.inmo.postssystem.features.content.common.ContentId
|
import dev.inmo.postssystem.features.content.common.ContentId
|
||||||
import kotlinx.serialization.Polymorphic
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlin.jvm.JvmInline
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
@ -25,8 +23,8 @@ typealias ContentIds = List<ContentId>
|
|||||||
* @see RegisteredPost
|
* @see RegisteredPost
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed interface Post {
|
sealed class Post {
|
||||||
val content: ContentIds
|
abstract val content: ContentIds
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,7 +33,7 @@ sealed interface Post {
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class NewPost(
|
data class NewPost(
|
||||||
override val content: ContentIds
|
override val content: ContentIds
|
||||||
) : Post
|
) : Post()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registered [Post]
|
* Registered [Post]
|
||||||
@ -46,16 +44,4 @@ data class RegisteredPost(
|
|||||||
override val content: ContentIds,
|
override val content: ContentIds,
|
||||||
@Serializable(DateTimeSerializer::class)
|
@Serializable(DateTimeSerializer::class)
|
||||||
val creationDate: DateTime
|
val creationDate: DateTime
|
||||||
) : Post
|
) : Post()
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class PostWithContent(
|
|
||||||
val post: Post,
|
|
||||||
val content: List<@Polymorphic Content>
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class RegisteredPostWithContent(
|
|
||||||
val post: RegisteredPost,
|
|
||||||
val content: List<@Polymorphic Content>
|
|
||||||
)
|
|
||||||
|
@ -12,7 +12,6 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api project(":postssystem.features.roles.common")
|
api project(":postssystem.features.roles.common")
|
||||||
api project(":postssystem.features.common.client")
|
api project(":postssystem.features.common.client")
|
||||||
api project(":postssystem.features.auth.client")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,12 @@ import kotlinx.serialization.KSerializer
|
|||||||
|
|
||||||
class ClientRolesStorage<T : Role>(
|
class ClientRolesStorage<T : Role>(
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
private val client: HttpClient,
|
private val unifiedRequester: UnifiedRequester,
|
||||||
private val serializer: KSerializer<T>
|
private val serializer: KSerializer<T>
|
||||||
) : RolesStorage<T>,
|
) : RolesStorage<T>,
|
||||||
ReadRolesStorage<T> by ReadClientRolesStorage(
|
ReadRolesStorage<T> by ReadClientRolesStorage(
|
||||||
baseUrl, client, serializer
|
baseUrl, unifiedRequester, serializer
|
||||||
),
|
),
|
||||||
WriteRolesStorage<T> by WriteClientRolesStorage(
|
WriteRolesStorage<T> by WriteClientRolesStorage(
|
||||||
baseUrl, client, serializer
|
baseUrl, unifiedRequester, serializer
|
||||||
)
|
)
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
package dev.inmo.postssystem.features.roles.client
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.plus
|
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
|
||||||
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
|
|
||||||
import dev.inmo.postssystem.features.roles.common.Role
|
|
||||||
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import org.koin.core.Koin
|
|
||||||
|
|
||||||
class DefaultRolesSettings(
|
|
||||||
private val koin: Koin,
|
|
||||||
private val authSettings: AuthSettings,
|
|
||||||
private val scope: CoroutineScope
|
|
||||||
) : RolesSettings {
|
|
||||||
private val _userRoles = MutableStateFlow<List<Role>>(emptyList())
|
|
||||||
override val userRoles: StateFlow<List<Role>> = _userRoles.asStateFlow()
|
|
||||||
|
|
||||||
val rolesUpdatingJob = (authSettings.user + authSettings.authorizedDIModule).subscribeSafelyWithoutExceptions(scope) {
|
|
||||||
val user = authSettings.user.value
|
|
||||||
|
|
||||||
if (user == null || authSettings.authorizedDIModule.value == null) {
|
|
||||||
_userRoles.value = emptyList()
|
|
||||||
} else {
|
|
||||||
_userRoles.value = koin.get<RolesStorage<Role>>().getRoles(user.id)
|
|
||||||
}
|
|
||||||
println(user)
|
|
||||||
println(userRoles.value)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
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<RolesSettings> { DefaultRolesSettings(getKoin(), get(), get()) }
|
|
||||||
single<RolesStorage<Role>> { ClientRolesStorage(get(AuthorizedQualifiers.ServerUrlQualifier), get(), Role.serializer()) }
|
|
||||||
}
|
|
@ -3,15 +3,13 @@ package dev.inmo.postssystem.features.roles.client
|
|||||||
import dev.inmo.postssystem.features.roles.common.*
|
import dev.inmo.postssystem.features.roles.common.*
|
||||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||||
import io.ktor.client.HttpClient
|
|
||||||
import io.ktor.client.request.get
|
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
|
||||||
class ReadClientRolesStorage<T : Role>(
|
class ReadClientRolesStorage<T : Role>(
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
private val client: HttpClient,
|
private val unifiedRequester: UnifiedRequester,
|
||||||
private val serializer: KSerializer<T>
|
private val serializer: KSerializer<T>
|
||||||
) : ReadRolesStorage<T> {
|
) : ReadRolesStorage<T> {
|
||||||
private val userRolesSerializer = ListSerializer(serializer)
|
private val userRolesSerializer = ListSerializer(serializer)
|
||||||
@ -23,13 +21,14 @@ class ReadClientRolesStorage<T : Role>(
|
|||||||
|
|
||||||
override suspend fun getSubjects(
|
override suspend fun getSubjects(
|
||||||
role: T
|
role: T
|
||||||
): List<RoleSubject> = client.get(
|
): List<RoleSubject> = unifiedRequester.uniget(
|
||||||
buildStandardUrl(
|
buildStandardUrl(
|
||||||
userRolesFullUrl,
|
userRolesFullUrl,
|
||||||
usersRolesGetSubjectsPathPart,
|
usersRolesGetSubjectsPathPart,
|
||||||
usersRolesRoleQueryParameterName to unifiedRequester.encodeUrlQueryValue(serializer, role)
|
usersRolesRoleQueryParameterName to unifiedRequester.encodeUrlQueryValue(serializer, role)
|
||||||
|
),
|
||||||
|
RoleSubjectsSerializer
|
||||||
)
|
)
|
||||||
).body()
|
|
||||||
|
|
||||||
override suspend fun getRoles(
|
override suspend fun getRoles(
|
||||||
subject: RoleSubject
|
subject: RoleSubject
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package dev.inmo.postssystem.features.roles.client
|
|
||||||
|
|
||||||
import dev.inmo.postssystem.features.roles.common.Role
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
|
|
||||||
interface RolesSettings {
|
|
||||||
val userRoles: StateFlow<List<Role>>
|
|
||||||
}
|
|
@ -8,7 +8,7 @@ import kotlinx.serialization.builtins.serializer
|
|||||||
|
|
||||||
class WriteClientRolesStorage<T : Role>(
|
class WriteClientRolesStorage<T : Role>(
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
private val client: HttpClient,
|
private val unifiedRequester: UnifiedRequester,
|
||||||
private val serializer: KSerializer<T>
|
private val serializer: KSerializer<T>
|
||||||
) : WriteRolesStorage<T> {
|
) : WriteRolesStorage<T> {
|
||||||
private val wrapperSerializer = RolesStorageIncludeExcludeWrapper.serializer(
|
private val wrapperSerializer = RolesStorageIncludeExcludeWrapper.serializer(
|
||||||
|
@ -6,16 +6,17 @@ import kotlinx.serialization.descriptors.SerialDescriptor
|
|||||||
import kotlinx.serialization.encoding.*
|
import kotlinx.serialization.encoding.*
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
|
||||||
@Polymorphic
|
@Serializable(RoleSerializer::class)
|
||||||
interface Role {
|
interface Role {
|
||||||
companion object {
|
companion object {
|
||||||
fun serializer(): KSerializer<Role> = RoleSerializer
|
fun serializer() = RoleSerializer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UnknownRole(val originalJson: JsonElement) : Role
|
data class UnknownRole(val originalJson: JsonElement) : Role
|
||||||
|
|
||||||
|
@Serializer(Role::class)
|
||||||
object RoleSerializer : KSerializer<Role> {
|
object RoleSerializer : KSerializer<Role> {
|
||||||
private val userRoleFormat = Json { ignoreUnknownKeys = true }
|
private val userRoleFormat = Json { ignoreUnknownKeys = true }
|
||||||
private const val keyField = "key"
|
private const val keyField = "key"
|
||||||
|
@ -3,16 +3,30 @@ package dev.inmo.postssystem.features.roles.manager.common
|
|||||||
import dev.inmo.postssystem.features.roles.common.Role
|
import dev.inmo.postssystem.features.roles.common.Role
|
||||||
import dev.inmo.postssystem.features.roles.common.RoleSerializer
|
import dev.inmo.postssystem.features.roles.common.RoleSerializer
|
||||||
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
|
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable(RolesManagerRoleSerializer::class)
|
||||||
sealed interface RolesManagerRole : Role
|
interface RolesManagerRole : Role {
|
||||||
|
companion object {
|
||||||
|
fun serializer() = RolesManagerRoleSerializer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("GeneralRolesManagerRole")
|
|
||||||
object GeneralRolesManagerRole : RolesManagerRole {
|
object GeneralRolesManagerRole : RolesManagerRole {
|
||||||
override fun toString(): String = "GeneralRolesManagerRole"
|
override fun toString(): String = "GeneralRolesManagerRole"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val justForLoading = GeneralRolesManagerRole.serializer()
|
private const val KEY = "roles_manager"
|
||||||
|
|
||||||
|
object RolesManagerRoleSerializer : TypedSerializer<RolesManagerRole>(
|
||||||
|
RolesManagerRole::class,
|
||||||
|
mapOf(
|
||||||
|
"${KEY}_general" to GeneralRolesManagerRole.serializer()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
RoleSerializer.includeSerializer(KEY, RolesManagerRoleSerializer)
|
||||||
|
serializers.forEach { (k, v) -> RoleSerializer.includeSerializer(k, v) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ import dev.inmo.postssystem.features.roles.common.*
|
|||||||
import dev.inmo.postssystem.features.roles.manager.common.GeneralRolesManagerRole
|
import dev.inmo.postssystem.features.roles.manager.common.GeneralRolesManagerRole
|
||||||
import dev.inmo.postssystem.features.roles.server.RolesChecker
|
import dev.inmo.postssystem.features.roles.server.RolesChecker
|
||||||
import dev.inmo.postssystem.features.users.common.User
|
import dev.inmo.postssystem.features.users.common.User
|
||||||
import io.ktor.server.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|
||||||
object RolesManagerRolesChecker : RolesChecker<Role> {
|
object RolesManagerRolesChecker : RolesChecker<Role> {
|
||||||
override val key: String
|
override val key: String
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package dev.inmo.postssystem.features.roles.manager.server
|
package dev.inmo.postssystem.features.roles.manager.server
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
||||||
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
||||||
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRole
|
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRole
|
||||||
|
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRoleSerializer
|
||||||
import dev.inmo.postssystem.features.roles.server.RolesStorageWriteServerRoutesConfigurator
|
import dev.inmo.postssystem.features.roles.server.RolesStorageWriteServerRoutesConfigurator
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
||||||
|
|
||||||
class RolesManagerRolesStorageServerRoutesConfigurator(
|
class RolesManagerRolesStorageServerRoutesConfigurator(
|
||||||
storage: RolesStorage<RolesManagerRole>
|
storage: RolesStorage<RolesManagerRole>,
|
||||||
|
unifiedRouter: UnifiedRouter
|
||||||
) : ApplicationRoutingConfigurator.Element by RolesStorageWriteServerRoutesConfigurator(
|
) : ApplicationRoutingConfigurator.Element by RolesStorageWriteServerRoutesConfigurator(
|
||||||
storage,
|
storage,
|
||||||
RolesManagerRole.serializer(),
|
RolesManagerRoleSerializer,
|
||||||
RolesManagerRolesChecker.key
|
RolesManagerRolesChecker.key,
|
||||||
|
unifiedRouter = unifiedRouter
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@ package dev.inmo.postssystem.features.roles.server
|
|||||||
|
|
||||||
import dev.inmo.postssystem.features.roles.common.*
|
import dev.inmo.postssystem.features.roles.common.*
|
||||||
import dev.inmo.postssystem.features.users.common.User
|
import dev.inmo.postssystem.features.users.common.User
|
||||||
import io.ktor.server.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|
||||||
interface RolesChecker<T : Role> {
|
interface RolesChecker<T : Role> {
|
||||||
val key: String
|
val key: String
|
||||||
|
@ -3,8 +3,9 @@ package dev.inmo.postssystem.features.roles.server
|
|||||||
import dev.inmo.postssystem.features.roles.common.*
|
import dev.inmo.postssystem.features.roles.common.*
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
import dev.inmo.micro_utils.ktor.server.*
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
||||||
import io.ktor.server.auth.authenticate
|
import io.ktor.application.call
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.auth.authenticate
|
||||||
|
import io.ktor.routing.*
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
|
||||||
@ -12,7 +13,8 @@ class RolesStorageWriteServerRoutesConfigurator<T : Role>(
|
|||||||
private val storage: WriteRolesStorage<T>,
|
private val storage: WriteRolesStorage<T>,
|
||||||
private val serializer: KSerializer<T>,
|
private val serializer: KSerializer<T>,
|
||||||
private val includeAuthKey: String,
|
private val includeAuthKey: String,
|
||||||
private val excludeAuthKey: String = includeAuthKey
|
private val excludeAuthKey: String = includeAuthKey,
|
||||||
|
private val unifiedRouter: UnifiedRouter
|
||||||
) : ApplicationRoutingConfigurator.Element {
|
) : ApplicationRoutingConfigurator.Element {
|
||||||
override fun Route.invoke() {
|
override fun Route.invoke() {
|
||||||
route(usersRolesRootPathPart) {
|
route(usersRolesRootPathPart) {
|
||||||
|
@ -3,19 +3,21 @@ package dev.inmo.postssystem.features.roles.server
|
|||||||
import dev.inmo.postssystem.features.auth.common.AuthToken
|
import dev.inmo.postssystem.features.auth.common.AuthToken
|
||||||
import dev.inmo.postssystem.features.auth.server.principal
|
import dev.inmo.postssystem.features.auth.server.principal
|
||||||
import dev.inmo.postssystem.features.auth.server.tokens.AuthTokensService
|
import dev.inmo.postssystem.features.auth.server.tokens.AuthTokensService
|
||||||
import dev.inmo.postssystem.features.common.server.ApplicationAuthenticationConfigurator
|
import dev.inmo.postssystem.features.common.server.sessions.ApplicationAuthenticationConfigurator
|
||||||
import dev.inmo.postssystem.features.roles.common.Role
|
import dev.inmo.postssystem.features.roles.common.Role
|
||||||
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
import dev.inmo.postssystem.features.roles.common.RolesStorage
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.auth.Authentication
|
||||||
|
import io.ktor.auth.session
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.auth.*
|
import io.ktor.response.respond
|
||||||
import io.ktor.server.response.respond
|
|
||||||
|
|
||||||
class RolesAuthenticationConfigurator<T : Role>(
|
class RolesAuthenticationConfigurator<T : Role>(
|
||||||
private val usersRolesStorage: RolesStorage<T>,
|
private val usersRolesStorage: RolesStorage<T>,
|
||||||
private val authTokensService: AuthTokensService,
|
private val authTokensService: AuthTokensService,
|
||||||
private val rolesCheckers: List<RolesChecker<T>>
|
private val rolesCheckers: List<RolesChecker<T>>
|
||||||
) : ApplicationAuthenticationConfigurator.Element {
|
) : ApplicationAuthenticationConfigurator.Element {
|
||||||
override fun AuthenticationConfig.invoke() {
|
override fun Authentication.Configuration.invoke() {
|
||||||
rolesCheckers.forEach { checker ->
|
rolesCheckers.forEach { checker ->
|
||||||
session<AuthToken>(checker.key) {
|
session<AuthToken>(checker.key) {
|
||||||
validate {
|
validate {
|
||||||
|
@ -3,15 +3,16 @@ package dev.inmo.postssystem.features.roles.server
|
|||||||
import dev.inmo.postssystem.features.roles.common.*
|
import dev.inmo.postssystem.features.roles.common.*
|
||||||
import dev.inmo.micro_utils.ktor.server.*
|
import dev.inmo.micro_utils.ktor.server.*
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
||||||
import io.ktor.server.auth.authenticate
|
import io.ktor.auth.authenticate
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.routing.*
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
|
||||||
class RolesStorageReadServerRoutesConfigurator<T : Role>(
|
class RolesStorageReadServerRoutesConfigurator<T : Role>(
|
||||||
private val storage: ReadRolesStorage<T>,
|
private val storage: ReadRolesStorage<T>,
|
||||||
private val serializer: KSerializer<T>
|
private val serializer: KSerializer<T>,
|
||||||
|
private val unifiedRouter: UnifiedRouter
|
||||||
) : ApplicationRoutingConfigurator.Element {
|
) : ApplicationRoutingConfigurator.Element {
|
||||||
private val userRolesSerializer = ListSerializer(serializer)
|
private val userRolesSerializer = ListSerializer(serializer)
|
||||||
override fun Route.invoke() {
|
override fun Route.invoke() {
|
||||||
|
@ -22,6 +22,6 @@ class StatusFeatureClient(
|
|||||||
statusAuthorisedPathPart
|
statusAuthorisedPathPart
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun checkServerStatus() = client.get(fullStatusUrl).status == HttpStatusCode.OK
|
suspend fun checkServerStatus() = client.get<HttpResponse>(fullStatusUrl).status == HttpStatusCode.OK
|
||||||
suspend fun checkServerStatusWithAuth() = client.get(fullAuthorisedStatusUrl).status == HttpStatusCode.OK
|
suspend fun checkServerStatusWithAuth() = client.get<HttpResponse>(fullAuthorisedStatusUrl).status == HttpStatusCode.OK
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ package dev.inmo.postssystem.features.status.server
|
|||||||
import dev.inmo.postssystem.features.status.common.statusAuthorisedPathPart
|
import dev.inmo.postssystem.features.status.common.statusAuthorisedPathPart
|
||||||
import dev.inmo.postssystem.features.status.common.statusRootPart
|
import dev.inmo.postssystem.features.status.common.statusRootPart
|
||||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.auth.authenticate
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.call
|
import io.ktor.response.respond
|
||||||
import io.ktor.server.auth.authenticate
|
import io.ktor.routing.*
|
||||||
import io.ktor.server.response.respond
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
object StatusRoutingConfigurator : ApplicationRoutingConfigurator.Element {
|
object StatusRoutingConfigurator : ApplicationRoutingConfigurator.Element {
|
||||||
override fun Route.invoke() {
|
override fun Route.invoke() {
|
||||||
|
@ -12,7 +12,6 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api project(":postssystem.features.users.common")
|
api project(":postssystem.features.users.common")
|
||||||
api project(":postssystem.features.common.client")
|
api project(":postssystem.features.common.client")
|
||||||
api project(":postssystem.features.auth.client")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user