Compare commits

..

1 Commits

Author SHA1 Message Date
0f983d9286 try to fix ktor update 2022-05-18 23:32:37 +06:00
51 changed files with 323 additions and 346 deletions

View File

@@ -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`) предполагается два варианта:

View File

@@ -8,6 +8,7 @@ import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.common.common.baseKoin 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.features.common.common.ui.fsm.UIFSMHandler
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMStateSerializer
import dev.inmo.postssystem.services.posts.client.ui.list.PostsListUIFSMState import dev.inmo.postssystem.services.posts.client.ui.list.PostsListUIFSMState
import dev.inmo.postssystem.services.posts.client.ui.list.PostsListUIState import dev.inmo.postssystem.services.posts.client.ui.list.PostsListUIState
import kotlinx.browser.* import kotlinx.browser.*
@@ -28,7 +29,8 @@ 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 { val defaultSerialFormat = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true

View File

@@ -8,9 +8,8 @@ import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.common.common.* 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.UIFSMExceptionHandler
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
import kotlin.js.JsExport
internal fun CommonAuthModuleLoader() = DefaultModuleLoader { val defaultModuleLoader = DefaultModuleLoader {
single<AuthSettings> { DefaultAuthSettings(get(DefaultQualifiers.SettingsQualifier), get(), getKoin(), get()) } single<AuthSettings> { DefaultAuthSettings(get(DefaultQualifiers.SettingsQualifier), get(), getKoin(), get()) }
singleWithRandomQualifier { singleWithRandomQualifier {

View File

@@ -4,9 +4,9 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
sealed interface AuthUIError { sealed interface AuthUIError {
// @Serializable @Serializable
object ServerUnavailable : AuthUIError object ServerUnavailable : AuthUIError
// @Serializable @Serializable
object AuthIncorrect : AuthUIError object AuthIncorrect : AuthUIError
} }
@@ -14,9 +14,9 @@ sealed interface AuthUIError {
sealed interface AuthUIState { sealed interface AuthUIState {
@Serializable @Serializable
data class Init(val showError: AuthUIError? = null) : AuthUIState data class Init(val showError: AuthUIError? = null) : AuthUIState
// @Serializable @Serializable
object Loading : AuthUIState object Loading : AuthUIState
// @Serializable @Serializable
object Authorized : AuthUIState object Authorized : AuthUIState
companion object { companion object {

View File

@@ -29,7 +29,7 @@ val loader = DefaultModuleLoader {
strictlyOn(get<AuthView>()) strictlyOn(get<AuthView>())
} }
} }
} + CommonAuthModuleLoader() }
class AuthView( class AuthView(
private val viewModel: AuthUIViewModel, private val viewModel: AuthUIViewModel,

View File

@@ -2,7 +2,7 @@ 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.*
@@ -10,7 +10,6 @@ import dev.inmo.micro_utils.ktor.server.configurators.*
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call import io.ktor.server.application.call
import io.ktor.server.auth.* import io.ktor.server.auth.*
import io.ktor.server.request.receive
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.* import io.ktor.server.routing.*
import io.ktor.server.sessions.sessions import io.ktor.server.sessions.sessions
@@ -25,72 +24,84 @@ 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() {
route(authRootPathPart) { unifiedRouter.apply {
post(authAuthPathPart) { route(authRootPathPart) {
safely( post(authAuthPathPart) {
{ safely(
// TODO:: add error info {
it.printStackTrace() // TODO:: add error info
call.respond( it.printStackTrace()
HttpStatusCode.InternalServerError, call.respond(
"Something went wrong" HttpStatusCode.InternalServerError,
) "Something went wrong"
} )
) { }
val creds = call.receive<AuthCreds>() ) {
val creds = uniload(AuthCreds.serializer())
val tokenInfo = authFeature.auth(creds)
val tokenInfo = authFeature.auth(creds)
if (tokenInfo == null) {
if (call.response.status() == null) { if (tokenInfo == null) {
call.respond(HttpStatusCode.Forbidden) if (call.response.status() == null) {
call.respond(HttpStatusCode.Forbidden)
}
} else {
call.sessions.set(tokenSessionKey, tokenInfo.token)
unianswer(
AuthTokenInfo.serializer().nullable,
tokenInfo
)
} }
} else {
call.sessions.set(tokenSessionKey, tokenInfo.token)
call.respond(tokenInfo)
} }
} }
} post (authRefreshPathPart) {
post (authRefreshPathPart) { safely(
safely( {
{ // TODO:: add error info
// TODO:: add error info call.respond(
call.respond( HttpStatusCode.InternalServerError,
HttpStatusCode.InternalServerError, "Something went wrong"
"Something went wrong" )
) }
} ) {
) { val refreshToken = uniload(RefreshToken.serializer())
val refreshToken = call.receive<RefreshToken>()
val tokenInfo = authFeature.refresh(refreshToken)
val tokenInfo = authFeature.refresh(refreshToken)
if (tokenInfo == null) {
if (tokenInfo == null) { if (call.response.status() == null) {
if (call.response.status() == null) { call.respond(HttpStatusCode.Forbidden)
call.respond(HttpStatusCode.Forbidden) }
} else {
call.sessions.set(tokenSessionKey, tokenInfo.token)
unianswer(
AuthTokenInfo.serializer().nullable,
tokenInfo
)
} }
} else {
call.sessions.set(tokenSessionKey, tokenInfo.token)
call.respond(tokenInfo)
} }
} }
} post(authGetMePathPart) {
post(authGetMePathPart) { safely(
safely( {
{ // TODO:: add error info
// TODO:: add error info call.respond(
call.respond( HttpStatusCode.InternalServerError,
HttpStatusCode.InternalServerError, "Something went wrong"
"Something went wrong" )
}
) {
unianswer(
User.serializer().nullable,
authFeature.getMe(
uniload(AuthToken.serializer())
)
) )
} }
) {
call.respond(
authFeature.getMe(call.receive()) ?: HttpStatusCode.NoContent
)
} }
} }
} }

View File

@@ -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")
}
}
}
}

View File

@@ -1 +0,0 @@
<manifest package="dev.inmo.postssystem.features.client.template.client"/>

View File

@@ -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")
}
}
}
}

View File

@@ -1 +0,0 @@
<manifest package="dev.inmo.postssystem.features.client.template.common"/>

View File

@@ -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")
}
}
}
}

View File

@@ -49,16 +49,12 @@ fun baseKoin(
single { repoFactory() } single { repoFactory() }
single { defaultScope } single { defaultScope }
single(DefaultQualifiers.UIScopeQualifier) { get<CoroutineScope>().LinkedSupervisorScope(Dispatchers.Main) } single(DefaultQualifiers.UIScopeQualifier) { get<CoroutineScope>().LinkedSupervisorScope(Dispatchers.Main) }
single<StatesMachine<UIFSMState>>(DefaultQualifiers.UIFSMQualifier) { single<StatesMachine<UIFSMState>>(UIFSMQualifier) { UIFSM(get()) { (this@single to this@UIFSM).apply(get(
UIFSM(get()) { DefaultQualifiers.FSMHandlersBuilderQualifier
(this@single to this@UIFSM).apply(get(DefaultQualifiers.FSMHandlersBuilderQualifier)) )) } }
}
} AdditionalModules.Default.modules.forEach {
} + AdditionalModules.Default.modules.map { it.apply { load() }
module {
with(it) {
load()
}
} }
} }
) )

View File

@@ -6,5 +6,5 @@ object DefaultQualifiers {
val SettingsQualifier = StringQualifier("Settings") val SettingsQualifier = StringQualifier("Settings")
val FSMHandlersBuilderQualifier = StringQualifier("FSMHandlersBuilder") val FSMHandlersBuilderQualifier = StringQualifier("FSMHandlersBuilder")
val UIScopeQualifier = StringQualifier("CoroutineScopeUI") val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
val UIFSMQualifier = StringQualifier("UIFSM") val UIFSMQualifier = StringQualifier("FSM")
} }

View File

@@ -12,15 +12,6 @@ interface ModuleLoader {
} }
} }
operator fun ModuleLoader.plus(other: ModuleLoader) = ModuleLoader.ByCallback {
with(this@plus) {
load()
}
with (other) {
load()
}
}
fun DefaultModuleLoader(loadingBlock: Module.() -> Unit): ModuleLoader.ByCallback { fun DefaultModuleLoader(loadingBlock: Module.() -> Unit): ModuleLoader.ByCallback {
val newModuleLoader = ModuleLoader.ByCallback(loadingBlock) val newModuleLoader = ModuleLoader.ByCallback(loadingBlock)
AdditionalModules.Default.addModule(newModuleLoader) AdditionalModules.Default.addModule(newModuleLoader)

View File

@@ -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

View File

@@ -1,4 +1,4 @@
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.server.application.Application

View File

@@ -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")
} }

View File

@@ -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

View File

@@ -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.*

View File

@@ -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

View File

@@ -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

View File

@@ -25,6 +25,7 @@ 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,

View File

@@ -4,13 +4,10 @@ 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.server.application.call import io.ktor.server.application.call
import io.ktor.server.auth.authenticate import io.ktor.server.auth.authenticate
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.response.respondBytes import io.ktor.server.response.respondBytes
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlinx.serialization.builtins.nullable import kotlinx.serialization.builtins.nullable
@@ -18,33 +15,47 @@ 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
)
} }
post(filesGetFilesPathPart) { unifierRouter.apply {
call.respondBytes( post(filesGetFilesPathPart) {
filesStorage.getBytes( call.respondBytes(
call.receive() filesStorage.getBytes(
uniload(FileId.serializer())
)
) )
) }
} get(filesGetFullFileInfoPathPart) {
get(filesGetFullFileInfoPathPart) { unianswer(
call.respond( FullFileInfoStorageWrapper.serializer().nullable,
filesStorage.getFullFileInfo( filesStorage.getFullFileInfo(
FileId(call.getParameterOrSendError(filesFileIdParameter) ?.decodeURLQueryComponent() ?: return@get) decodeUrlQueryValueOrSendError(filesFileIdParameter, FileId.serializer()) ?: return@get
) ?: HttpStatusCode.NoContent )
) )
}
} }
} }
} }

View File

@@ -25,8 +25,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 +35,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,12 +46,12 @@ 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 @Serializable
data class PostWithContent( data class PostWithContent(
val post: Post, val post: Post,
val content: List<@Polymorphic Content> val content: List<Content>
) )
@Serializable @Serializable

View File

@@ -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
) )

View File

@@ -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)
) ),
).body() RoleSubjectsSerializer
)
override suspend fun getRoles( override suspend fun getRoles(
subject: RoleSubject subject: RoleSubject

View File

@@ -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(

View File

@@ -6,7 +6,7 @@ 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(): KSerializer<Role> = RoleSerializer

View File

@@ -1,14 +1,17 @@
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.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(), RolesManagerRole.serializer(),
RolesManagerRolesChecker.key RolesManagerRolesChecker.key,
unifiedRouter = unifiedRouter
) )

View File

@@ -12,7 +12,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) {

View File

@@ -3,7 +3,7 @@ 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.http.HttpStatusCode import io.ktor.http.HttpStatusCode

View File

@@ -11,7 +11,8 @@ 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() {

View File

@@ -9,10 +9,10 @@ import kotlinx.serialization.builtins.nullable
class UsersStorageKtorClient( class UsersStorageKtorClient(
baseUrl: String, baseUrl: String,
client: HttpClient unifiedRequester: UnifiedRequester
) : ReadUsersStorage, ReadCRUDRepo<User, UserId> by KtorReadStandardCrudRepo( ) : ReadUsersStorage, ReadCRUDRepo<User, UserId> by KtorReadStandardCrudRepo(
buildStandardUrl(baseUrl, usersServerPathPart), buildStandardUrl(baseUrl, usersServerPathPart),
client, unifiedRequester,
User.serializer(), User.serializer(),
User.serializer().nullable, User.serializer().nullable,
UserId.serializer() UserId.serializer()

View File

@@ -1,21 +1,28 @@
package dev.inmo.postssystem.features.users.server package dev.inmo.postssystem.features.users.server
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.postssystem.features.users.common.* import dev.inmo.postssystem.features.users.common.*
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.configureReadCRUDRepoRoutes import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadStandardCrudRepoRoutes
import io.ktor.server.auth.authenticate import io.ktor.server.auth.authenticate
import io.ktor.server.routing.Route import io.ktor.server.routing.Route
import io.ktor.server.routing.route import io.ktor.server.routing.route
import kotlinx.serialization.builtins.nullable
class UsersStorageServerRoutesConfigurator( class UsersStorageServerRoutesConfigurator(
private val usersStorage: ReadUsersStorage private val usersStorage: ReadUsersStorage,
private val unifiedRouter: UnifiedRouter
) : ApplicationRoutingConfigurator.Element { ) : ApplicationRoutingConfigurator.Element {
override fun Route.invoke() { override fun Route.invoke() {
authenticate { authenticate {
route(usersServerPathPart) { route(usersServerPathPart) {
configureReadCRUDRepoRoutes( configureReadStandardCrudRepoRoutes(
usersStorage usersStorage,
) { UserId(it.toLong()) } User.serializer(),
User.serializer().nullable,
UserId.serializer(),
unifiedRouter
)
} }
} }
} }

View File

@@ -1,6 +1,6 @@
kotlin.code.style=official kotlin.code.style=official
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx4g
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true

View File

@@ -2,25 +2,25 @@
kotlin = "1.6.21" kotlin = "1.6.21"
kotlin-serialization = "1.3.3" kotlin-serialization = "1.3.3"
jsuikit = "0.1.7" jsuikit = "0.1.1"
compose = "1.2.0-alpha01-dev731" compose = "1.2.0-alpha01-dev686"
microutils = "0.11.12" microutils = "0.10.4"
tgbotapi = "2.1.2" tgbotapi = "1.1.1"
ktor = "2.0.3" ktor = "2.0.1"
klock = "2.7.0" klock = "2.7.0"
koin = "3.2.0" koin = "3.2.0"
exposed = "0.38.2" exposed = "0.38.2"
psql = "42.3.6" psql = "42.3.0"
scrimage = "4.0.31" scrimage = "4.0.31"
dokka = "1.6.21" dokka = "1.6.21"
logback = "1.2.10" logback = "1.2.10"
uuid = "0.4.1" uuid = "0.4.0"
android-junit = "4.12" android-junit = "4.12"
android-test-junit = "1.1.2" android-test-junit = "1.1.2"
android-espresso-core = "3.3.0" android-espresso-core = "3.3.0"
gh-release = "2.4.1" gh-release = "2.3.7"
android-gradle = "7.0.4" android-gradle = "7.0.4"
dexcount = "3.1.0" dexcount = "3.1.0"

View File

@@ -10,7 +10,7 @@ import kotlinx.serialization.builtins.serializer
class SimplePublicatorServiceClient( class SimplePublicatorServiceClient(
baseUrl: String, baseUrl: String,
private val client: HttpClient private val unifiedRequester: UnifiedRequester
) : SimplePublicatorService { ) : SimplePublicatorService {
private val fullUrl = buildStandardUrl( private val fullUrl = buildStandardUrl(
baseUrl, baseUrl,

View File

@@ -2,7 +2,7 @@ package dev.inmo.postssystem.publicators.simple.server
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
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 kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import org.koin.core.module.Module import org.koin.core.module.Module

View File

@@ -1,6 +1,6 @@
package dev.inmo.postssystem.server package dev.inmo.postssystem.server
import dev.inmo.postssystem.features.common.server.ServerModuleLoader import dev.inmo.postssystem.features.common.server.sessions.ServerModuleLoader
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.io.File import java.io.File

View File

@@ -3,9 +3,10 @@ package dev.inmo.postssystem.server
import dev.inmo.postssystem.features.auth.server.AuthenticationRoutingConfigurator import dev.inmo.postssystem.features.auth.server.AuthenticationRoutingConfigurator
import dev.inmo.postssystem.features.auth.server.SessionAuthenticationConfigurator import dev.inmo.postssystem.features.auth.server.SessionAuthenticationConfigurator
import dev.inmo.postssystem.features.auth.server.tokens.* import dev.inmo.postssystem.features.auth.server.tokens.*
import dev.inmo.postssystem.features.common.server.ApplicationAuthenticationConfigurator import dev.inmo.postssystem.features.common.server.sessions.ApplicationAuthenticationConfigurator
import dev.inmo.postssystem.features.files.common.* 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.postssystem.features.files.common.storage.WriteFilesStorage
import dev.inmo.postssystem.features.files.server.FilesRoutingConfigurator import dev.inmo.postssystem.features.files.server.FilesRoutingConfigurator
import dev.inmo.postssystem.features.roles.common.* import dev.inmo.postssystem.features.roles.common.*
import dev.inmo.postssystem.features.roles.common.keyvalue.KeyValuesRolesOriginalRepo import dev.inmo.postssystem.features.roles.common.keyvalue.KeyValuesRolesOriginalRepo
@@ -18,19 +19,20 @@ import dev.inmo.postssystem.features.status.server.StatusRoutingConfigurator
import dev.inmo.postssystem.features.users.common.ExposedUsersStorage import dev.inmo.postssystem.features.users.common.ExposedUsersStorage
import dev.inmo.postssystem.features.users.server.UsersStorageServerRoutesConfigurator import dev.inmo.postssystem.features.users.server.UsersStorageServerRoutesConfigurator
import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.configurators.* import dev.inmo.micro_utils.ktor.server.configurators.*
import dev.inmo.micro_utils.ktor.server.createKtorServer import dev.inmo.micro_utils.ktor.server.createKtorServer
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedKeyValuesRepo import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedOneToManyKeyValueRepo
import dev.inmo.postssystem.features.common.common.* import dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.features.common.server.Qualifiers import dev.inmo.postssystem.features.common.server.sessions.Qualifiers
import dev.inmo.postssystem.features.content.common.* import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.content.server.ServerContentStorageAggregator import dev.inmo.postssystem.features.content.server.ServerContentStorageAggregator
import dev.inmo.postssystem.features.content.server.storage.* import dev.inmo.postssystem.features.content.server.storage.*
import dev.inmo.postssystem.features.posts.server.* import dev.inmo.postssystem.features.posts.server.*
import dev.inmo.postssystem.features.publication.server.PublicationManager import dev.inmo.postssystem.features.publication.server.PublicationManager
import dev.inmo.postssystem.services.posts.server.* import dev.inmo.postssystem.services.posts.server.*
import io.ktor.server.application.pluginOrNull import io.ktor.server.application.featureOrNull
import io.ktor.server.application.log import io.ktor.server.application.log
import io.ktor.server.routing.Route import io.ktor.server.routing.Route
import io.ktor.server.routing.Routing import io.ktor.server.routing.Routing
@@ -47,7 +49,6 @@ import org.jetbrains.exposed.sql.Database
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.core.parameter.ParametersHolder import org.koin.core.parameter.ParametersHolder
import org.koin.core.qualifier.StringQualifier import org.koin.core.qualifier.StringQualifier
import org.koin.core.qualifier.named
import org.koin.dsl.* import org.koin.dsl.*
import java.io.File import java.io.File
@@ -87,18 +88,19 @@ fun getDIModule(
} }
} }
single { UnifiedRouter(get()) }
singleWithBinds { config } singleWithBinds { config }
singleWithBinds { get<Config>().databaseConfig } singleWithBinds { get<Config>().databaseConfig }
singleWithBinds { get<Config>().authConfig } singleWithBinds { get<Config>().authConfig }
singleWithBinds(Qualifiers.filesFolderQualifier) { get<Config>().filesFolderFile } singleWithBinds(Qualifiers.filesFolderQualifier) { get<Config>().filesFolderFile }
singleWithBinds(Qualifiers.commonFilesFolderQualifier) { File(get<Config>().filesFolderFile, "common").apply { mkdirs() } }
singleWithBinds { get<DatabaseConfig>().database } singleWithBinds { get<DatabaseConfig>().database }
singleWithBinds { ExposedUsersStorage(get()) } singleWithBinds { ExposedUsersStorage(get()) }
singleWithBinds { exposedUsersAuthenticator(get(), get()) } singleWithBinds { exposedUsersAuthenticator(get(), get()) }
factory<KeyValuesRolesOriginalRepo>(Qualifiers.usersRolesKeyValueFactoryQualifier) { (tableName: String) -> factory<KeyValuesRolesOriginalRepo>(Qualifiers.usersRolesKeyValueFactoryQualifier) { (tableName: String) ->
ExposedKeyValuesRepo(get(), { text("subject") }, { text("role") }, tableName) ExposedOneToManyKeyValueRepo(get(), { text("subject") }, { text("role") }, tableName)
} }
single { single {
RolesManagerRoleStorage(get(Qualifiers.usersRolesKeyValueFactoryQualifier) { ParametersHolder(mutableListOf("rolesManager")) }) RolesManagerRoleStorage(get(Qualifiers.usersRolesKeyValueFactoryQualifier) { ParametersHolder(mutableListOf("rolesManager")) })
@@ -139,24 +141,13 @@ fun getDIModule(
) )
} }
singleWithBinds<FilesStorage> {
val metasRepo = MetasKeyValueRepo(
get(),
ExposedKeyValueRepo(get(), { text("fileid") }, { text("metaInfo") }, "CommonFilesMetaInfoTable")
)
DefaultFilesStorage(
DiskReadFilesStorage(get(Qualifiers.commonFilesFolderQualifier), metasRepo),
WriteDistFilesStorage(get(Qualifiers.commonFilesFolderQualifier), metasRepo)
)
}
// Routing configurators // Routing configurators
singleWithBinds { FilesRoutingConfigurator(get(), null) } singleWithBinds { FilesRoutingConfigurator(get(), null, get()) }
singleWithBinds { StatusRoutingConfigurator } singleWithBinds { StatusRoutingConfigurator }
singleWithBinds { UsersStorageServerRoutesConfigurator(get()) } singleWithBinds { UsersStorageServerRoutesConfigurator(get(), get()) }
singleWithBinds { RolesStorageReadServerRoutesConfigurator<Role>(get(), RoleSerializer) } singleWithBinds { RolesStorageReadServerRoutesConfigurator<Role>(get(), RoleSerializer, get()) }
singleWithBinds { RolesManagerRolesStorageServerRoutesConfigurator(get()) } singleWithBinds { RolesManagerRolesStorageServerRoutesConfigurator(get(), get()) }
singleWithBinds { ServerPostsServiceRoutingConfigurator(get(), get(), get()) } singleWithBinds { ServerPostsServiceRoutingConfigurator(get(), get(), get(), get()) }
singleWithBinds { ClientStaticRoutingConfiguration("web") } singleWithBinds { ClientStaticRoutingConfiguration("web") }
singleWithBinds { singleWithBinds {
@@ -178,7 +169,7 @@ fun getDIModule(
get() get()
) )
} }
singleWithBinds { AuthenticationRoutingConfigurator(get(), get()) } singleWithBinds { AuthenticationRoutingConfigurator(get(), get(), get()) }
singleWithBinds { NotFoundStatusPageRedirectToIndex("/") } singleWithBinds { NotFoundStatusPageRedirectToIndex("/") }
if (config.debugMode) { if (config.debugMode) {
@@ -203,7 +194,7 @@ fun getDIModule(
it.apply { configure() } it.apply { configure() }
} }
if (config.debugMode) { if (config.debugMode) {
pluginOrNull(Routing) ?.print() featureOrNull(Routing) ?.print()
} }
} }
} }

View File

@@ -10,7 +10,7 @@ data class NotFoundStatusPageRedirectToIndex(
val redirectTo: String val redirectTo: String
) : StatusPagesConfigurator.Element { ) : StatusPagesConfigurator.Element {
override fun StatusPagesConfig.invoke() { override fun StatusPagesConfig.invoke() {
status(HttpStatusCode.NotFound) { _ -> status(HttpStatusCode.NotFound) {
call.respondRedirect(redirectTo) call.respondRedirect(redirectTo)
} }
} }

View File

@@ -5,7 +5,7 @@ import dev.inmo.postssystem.services.posts.common.*
class ClientPostsService( class ClientPostsService(
baseUrl: String, baseUrl: String,
client: HttpClient unifiedRequester: UnifiedRequester
) : PostsService, ) : PostsService,
ReadPostsService by ClientReadPostsService(baseUrl, client), ReadPostsService by ClientReadPostsService(baseUrl, unifiedRequester),
WritePostsService by ClientWritePostsService(baseUrl, client) WritePostsService by ClientWritePostsService(baseUrl, unifiedRequester)

View File

@@ -12,10 +12,10 @@ import kotlinx.serialization.builtins.nullable
class ClientReadPostsService( class ClientReadPostsService(
private val baseUrl: String, private val baseUrl: String,
private val client: HttpClient private val unifiedRequester: UnifiedRequester
) : ReadPostsService, ReadCRUDRepo<RegisteredPost, PostId> by KtorReadStandardCrudRepo( ) : ReadPostsService, ReadCRUDRepo<RegisteredPost, PostId> by KtorReadStandardCrudRepo(
buildStandardUrl(baseUrl, postsRootPath), buildStandardUrl(baseUrl, postsRootPath),
client, unifiedRequester,
RegisteredPost.serializer(), RegisteredPost.serializer(),
RegisteredPost.serializer().nullable, RegisteredPost.serializer().nullable,
PostId.serializer() PostId.serializer()

View File

@@ -25,17 +25,17 @@ import kotlinx.serialization.modules.polymorphic
class ClientWritePostsService( class ClientWritePostsService(
private val baseUrl: String, private val baseUrl: String,
client: HttpClient unifiedRequester: UnifiedRequester
) : WritePostsService { ) : WritePostsService {
private val root = buildStandardUrl(baseUrl, postsRootPath) private val root = buildStandardUrl(baseUrl, postsRootPath)
// private val unifiedRequester = UnifiedRequester( private val unifiedRequester = UnifiedRequester(
// unifiedRequester.client, unifiedRequester.client,
// unifiedRequester.serialFormat.createWithSerializerModuleExtension { unifiedRequester.serialFormat.createWithSerializerModuleExtension {
// polymorphic(Content::class) { polymorphic(Content::class) {
// subclass(BinaryContent::class, BinaryContentSerializer(TempFileIdentifierInputProvider::class, TempFileIdentifierInputProvider.serializer())) subclass(BinaryContent::class, BinaryContentSerializer(TempFileIdentifierInputProvider::class, TempFileIdentifierInputProvider.serializer()))
// } }
// } }
// ) )
private val contentEitherSerializer = EitherSerializer(ContentId.serializer(), ContentSerializer) private val contentEitherSerializer = EitherSerializer(ContentId.serializer(), ContentSerializer)
private val contentsEitherSerializer = ListSerializer(contentEitherSerializer) private val contentsEitherSerializer = ListSerializer(contentEitherSerializer)
@@ -58,7 +58,7 @@ class ClientWritePostsService(
return (content as? BinaryContent) ?.let { return (content as? BinaryContent) ?.let {
when (val provider = it.inputProvider) { when (val provider = it.inputProvider) {
is FileBasedInputProvider -> { is FileBasedInputProvider -> {
val fileId = client.tempUpload( val fileId = unifiedRequester.tempUpload(
tempUploadFullPath, tempUploadFullPath,
provider.file provider.file
) )

View File

@@ -3,14 +3,14 @@ package dev.inmo.postssystem.services.posts.client.ui.create
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
sealed interface PostCreateUIState { sealed class PostCreateUIState {
// @Serializable @Serializable
object Init : PostCreateUIState object Init : PostCreateUIState()
// @Serializable @Serializable
object Uploading : PostCreateUIState object Uploading : PostCreateUIState()
// @Serializable @Serializable
object Fail : PostCreateUIState object Fail : PostCreateUIState()
// @Serializable @Serializable
object Completed : PostCreateUIState object Completed : PostCreateUIState()
} }

View File

@@ -4,13 +4,13 @@ import dev.inmo.postssystem.features.posts.common.RegisteredPostWithContent
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
sealed interface PostsListUIState { sealed class PostsListUIState {
// @Serializable @Serializable
object Loading : PostsListUIState object Loading : PostsListUIState()
@Serializable @Serializable
data class Show( data class Show(
val posts: List<RegisteredPostWithContent> val posts: List<RegisteredPostWithContent>
) : PostsListUIState ) : PostsListUIState()
} }

View File

@@ -10,19 +10,18 @@ import dev.inmo.micro_utils.mime_types.findBuiltinMimeType
import dev.inmo.micro_utils.repos.ktor.common.crud.createRouting import dev.inmo.micro_utils.repos.ktor.common.crud.createRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.updateRouting import dev.inmo.micro_utils.repos.ktor.common.crud.updateRouting
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.removeRoute import dev.inmo.micro_utils.repos.ktor.common.one_to_many.removeRoute
import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadCRUDRepoRoutes import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadStandardCrudRepoRoutes
import dev.inmo.postssystem.features.common.common.FileBasedInputProvider import dev.inmo.postssystem.features.common.common.FileBasedInputProvider
import dev.inmo.postssystem.features.content.common.* import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.files.common.FileId import dev.inmo.postssystem.features.files.common.FileId
import dev.inmo.postssystem.features.posts.common.* import dev.inmo.postssystem.features.posts.common.*
import dev.inmo.postssystem.services.posts.common.* import dev.inmo.postssystem.services.posts.common.*
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData import io.ktor.http.server.content.PartData
import io.ktor.http.content.streamProvider import io.ktor.http.server.content.streamProvider
import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call import io.ktor.server.application.call
import io.ktor.server.auth.authenticate import io.ktor.server.auth.authenticate
import io.ktor.server.request.receive
import io.ktor.server.request.receiveMultipart import io.ktor.server.request.receiveMultipart
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.* import io.ktor.server.routing.*
@@ -33,6 +32,8 @@ import io.ktor.utils.io.streams.asInput
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.builtins.*
import kotlinx.serialization.modules.polymorphic
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.attribute.FileTime import java.nio.file.attribute.FileTime
@@ -41,8 +42,19 @@ import java.util.concurrent.TimeUnit
class ServerPostsServiceRoutingConfigurator( class ServerPostsServiceRoutingConfigurator(
private val readPostsService: ReadPostsService, private val readPostsService: ReadPostsService,
private val writePostsService: WritePostsService? = readPostsService as? WritePostsService, private val writePostsService: WritePostsService? = readPostsService as? WritePostsService,
private val scope: CoroutineScope private val scope: CoroutineScope,
unifiedRouter: UnifiedRouter
) : ApplicationRoutingConfigurator.Element { ) : ApplicationRoutingConfigurator.Element {
private val unifiedRouter = UnifiedRouter(
serialFormat = unifiedRouter.serialFormat.createWithSerializerModuleExtension {
polymorphic(Content::class) {
subclass(BinaryContent::class, BinaryContentSerializer(TempFileIdentifierInputProvider::class, TempFileIdentifierInputProvider.serializer()))
}
}
)
private val contentEitherSerializer = EitherSerializer(ContentId.serializer(), ContentSerializer)
private val contentsEitherSerializer = ListSerializer(contentEitherSerializer)
private val contentsSerializer = ListSerializer(ContentSerializer)
private val temporalFilesMap = mutableMapOf<FileId, MPPFile>() private val temporalFilesMap = mutableMapOf<FileId, MPPFile>()
private val temporalFilesMutex = Mutex() private val temporalFilesMutex = Mutex()
@@ -85,16 +97,20 @@ class ServerPostsServiceRoutingConfigurator(
} }
private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContents(): List<Content> { private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContents(): List<Content> {
return call.receive<ContentsWrapper>().content.mapNotNull { return unifiedRouter.run {
mapBinary(it as? BinaryContent ?: return@mapNotNull it) uniload(contentsSerializer).mapNotNull {
mapBinary(it as? BinaryContent ?: return@mapNotNull it)
}
} }
} }
private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContentsEithers(): List<Either<ContentId, Content>> { private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContentsEithers(): List<Either<ContentId, Content>> {
return call.receive<ContentsEithersWrapper>().content.mapNotNull { return unifiedRouter.run {
it.mapOnSecond { uniload(contentsEitherSerializer).mapNotNull {
mapBinary(it as? BinaryContent ?: return@mapOnSecond null) ?.either() it.mapOnSecond {
} ?: it mapBinary(it as? BinaryContent ?: return@mapOnSecond null) ?.either()
} ?: it
}
} }
} }
@@ -103,69 +119,83 @@ class ServerPostsServiceRoutingConfigurator(
override fun Route.invoke() { override fun Route.invoke() {
authenticate { authenticate {
route(postsRootPath) { route(postsRootPath) {
configureReadCRUDRepoRoutes( configureReadStandardCrudRepoRoutes(
readPostsService readPostsService,
) { PostId(it.toLong()) } RegisteredPost.serializer(),
RegisteredPost.serializer().nullable,
PostId.serializer(),
unifiedRouter
)
writePostsService ?.let { writePostsService ?.let {
post(createRouting) { unifiedRouter.apply {
val data = receiveContents()
call.respond( post(createRouting) {
writePostsService.create(FullNewPost(data)) ?: HttpStatusCode.NoContent val data = receiveContents()
)
}
post(updateRouting) { unianswer(
call.respond( RegisteredPost.serializer().nullable,
writePostsService.update( writePostsService.create(FullNewPost(data))
call.getQueryParameterOrSendError(postsPostIdParameter)?.toLong()?.let(::PostId) ?: return@post,
receiveContentsEithers()
) ?: HttpStatusCode.NoContent
)
}
post(removeRoute) {
call.respond(
writePostsService.remove(
call.receive()
) )
)
}
post(postsCreateTempPathPart) {
val multipart = call.receiveMultipart()
var fileInfo: Pair<FileId, MPPFile>? = null
var part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
}
part = multipart.readPart()
} }
part ?.let {
if (it is PartData.FileItem) { post(updateRouting) {
val fileId = FileId(uuid4().toString()) val postId = call.decodeUrlQueryValueOrSendError(postsPostIdParameter, PostId.serializer()) ?: return@post
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let val data = receiveContentsEithers()
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
outputStream().use { outputStream -> unianswer(
it.streamProvider().use { RegisteredPost.serializer().nullable,
it.copyTo(outputStream) writePostsService.update(
postId,
data
)
)
}
post(removeRoute) {
val postId = uniload(PostId.serializer())
unianswer(
Unit.serializer(),
writePostsService.remove(postId)
)
}
post(postsCreateTempPathPart) {
val multipart = call.receiveMultipart()
var fileInfo: Pair<FileId, MPPFile>? = null
var part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
}
part = multipart.readPart()
}
part ?.let {
if (it is PartData.FileItem) {
val fileId = FileId(uuid4().toString())
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
outputStream().use { outputStream ->
it.streamProvider().use {
it.copyTo(outputStream)
}
} }
deleteOnExit()
} }
deleteOnExit()
} }
} }
}
fileInfo ?.also { (fileId, file) -> fileInfo ?.also { (fileId, file) ->
temporalFilesMutex.withLock { temporalFilesMutex.withLock {
temporalFilesMap[fileId] = file temporalFilesMap[fileId] = file
} }
call.respond(fileId.string) call.respond(fileId.string)
} ?: call.respond(HttpStatusCode.BadRequest) } ?: call.respond(HttpStatusCode.BadRequest)
}
} }

View File

@@ -1,7 +1,7 @@
package dev.inmo.postssystem.targets.telegram.loader.server package dev.inmo.postssystem.targets.telegram.loader.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.publication.server.PublicationTarget import dev.inmo.postssystem.features.publication.server.PublicationTarget
import dev.inmo.postssystem.targets.telegram.content.polls.common.PollContent import dev.inmo.postssystem.targets.telegram.content.polls.common.PollContent