Compare commits

..

9 Commits

Author SHA1 Message Date
f20aa4a359 update dependencies 2022-07-08 23:53:35 +06:00
ff973e63fc start migration onto ktor-based serialization of data 2022-06-12 18:54:28 +06:00
399405a4fb update dependencies 2022-06-12 18:18:07 +06:00
b7e5e2745d update dependencies 2022-06-05 00:30:41 +06:00
92cd98b80f add client subfeature 2022-05-21 14:48:53 +06:00
b73df49925 fixes in auth 2022-05-20 00:15:49 +06:00
f835dc1557 update tgbotapi 2022-05-19 00:01:50 +06:00
5bbe0b8a0e fix of build 2022-05-18 23:41:07 +06:00
9216c013ec try to fix ktor update 2022-05-18 23:36:39 +06:00
51 changed files with 350 additions and 327 deletions

View File

@@ -1,12 +1,14 @@
## Структура проекта
* **Features** - набор **законченных** фич проекта. Считается, что любая фича, находящаяся в мастере может быть добавлена в
клиент и использована в нем. Исключением является `common` - это набор вещей, используемых везде. В подпунктах представлены
части, на которые *обычно* разделяется фича
* Common - общая для фичи часть. Тут, как правило, хранятся конвенции путей для сетевых соединений, общие типы и пр.
* Server - часть, включаемая в сервер для подключения фичи. Обычно содержит работу с бд, определение модулей сервера и пр.
* Client - часть с клиентским кодом. В большинстве своём включает работу с сервером, MVVM часть (View при этом должны
находиться в платформенной части, если их нельзя вынести в сommon часть клиента)
клиент и использована в нем. Исключением является `common` - это набор вещей, используемых везде.
* Части, на которые *обычно* разделяется фича
* Common - общая для фичи часть. Тут, как правило, хранятся конвенции путей для сетевых соединений, общие типы и пр.
* Server - часть, включаемая в сервер для подключения фичи. Обычно содержит работу с бд, определение модулей сервера и пр.
* Client - часть с клиентским кодом. В большинстве своём включает работу с сервером, MVVM часть (View при этом должны
находиться в платформенной части, если их нельзя вынести в сommon часть клиента)
* Также существует фича `client`, которая не является фичей самой по-себе. Фактически, это набор разных фич, который системно
отсутствуют в бэке и используются в основном для пробрасывания удобных `API` и `View` для клиента
* **Services** - модули, отвечающие за клиент-серверную работу фич с точки зрения их взаимодействия. Например, в рамках сервисов
должен быть добавлен модуль для постов - именно через сервисы будет происходить создание поста, его редактирование и удаление
* **Client** - итоговый клиент. На момент написания этой доки (`Пн окт 25 12:56:41 +06 2021`) предполагается два варианта:

View File

@@ -8,7 +8,6 @@ import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.common.common.baseKoin
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.UIFSMStateSerializer
import dev.inmo.postssystem.services.posts.client.ui.list.PostsListUIFSMState
import dev.inmo.postssystem.services.posts.client.ui.list.PostsListUIState
import kotlinx.browser.*
@@ -29,8 +28,7 @@ val defaultTypedSerializer = TypedSerializer<Any>(
"Short" to Short.serializer(),
"Byte" to Byte.serializer(),
"Float" to Float.serializer(),
"Double" to Double.serializer(),
"UIFSMState" to UIFSMStateSerializer
"Double" to Double.serializer()
)
val defaultSerialFormat = Json {
ignoreUnknownKeys = true

View File

@@ -8,8 +8,9 @@ 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
val defaultModuleLoader = DefaultModuleLoader {
internal fun CommonAuthModuleLoader() = DefaultModuleLoader {
single<AuthSettings> { DefaultAuthSettings(get(DefaultQualifiers.SettingsQualifier), get(), getKoin(), get()) }
singleWithRandomQualifier {

View File

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

View File

@@ -29,7 +29,7 @@ val loader = DefaultModuleLoader {
strictlyOn(get<AuthView>())
}
}
}
} + CommonAuthModuleLoader()
class AuthView(
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.server.tokens.AuthTokensService
import dev.inmo.postssystem.features.common.server.sessions.ApplicationAuthenticationConfigurator
import dev.inmo.postssystem.features.common.server.ApplicationAuthenticationConfigurator
import dev.inmo.postssystem.features.users.common.User
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.server.*
@@ -10,6 +10,7 @@ import dev.inmo.micro_utils.ktor.server.configurators.*
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.auth.*
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.*
import io.ktor.server.sessions.sessions
@@ -24,84 +25,72 @@ fun User.principal() = AuthUserPrincipal(this)
class AuthenticationRoutingConfigurator(
private val authFeature: AuthFeature,
private val authTokensService: AuthTokensService,
private val unifiedRouter: UnifiedRouter
private val authTokensService: AuthTokensService
) : ApplicationRoutingConfigurator.Element, ApplicationAuthenticationConfigurator.Element {
override fun Route.invoke() {
unifiedRouter.apply {
route(authRootPathPart) {
post(authAuthPathPart) {
safely(
{
// TODO:: add error info
it.printStackTrace()
call.respond(
HttpStatusCode.InternalServerError,
"Something went wrong"
)
}
) {
val creds = uniload(AuthCreds.serializer())
val tokenInfo = authFeature.auth(creds)
if (tokenInfo == null) {
if (call.response.status() == null) {
call.respond(HttpStatusCode.Forbidden)
}
} else {
call.sessions.set(tokenSessionKey, tokenInfo.token)
unianswer(
AuthTokenInfo.serializer().nullable,
tokenInfo
)
}
}
}
post (authRefreshPathPart) {
safely(
{
// TODO:: add error info
call.respond(
HttpStatusCode.InternalServerError,
"Something went wrong"
)
}
) {
val refreshToken = uniload(RefreshToken.serializer())
val tokenInfo = authFeature.refresh(refreshToken)
if (tokenInfo == null) {
if (call.response.status() == null) {
call.respond(HttpStatusCode.Forbidden)
}
} else {
call.sessions.set(tokenSessionKey, tokenInfo.token)
unianswer(
AuthTokenInfo.serializer().nullable,
tokenInfo
)
}
}
}
post(authGetMePathPart) {
safely(
{
// TODO:: add error info
call.respond(
HttpStatusCode.InternalServerError,
"Something went wrong"
)
}
) {
unianswer(
User.serializer().nullable,
authFeature.getMe(
uniload(AuthToken.serializer())
)
route(authRootPathPart) {
post(authAuthPathPart) {
safely(
{
// TODO:: add error info
it.printStackTrace()
call.respond(
HttpStatusCode.InternalServerError,
"Something went wrong"
)
}
) {
val creds = call.receive<AuthCreds>()
val tokenInfo = authFeature.auth(creds)
if (tokenInfo == null) {
if (call.response.status() == null) {
call.respond(HttpStatusCode.Forbidden)
}
} else {
call.sessions.set(tokenSessionKey, tokenInfo.token)
call.respond(tokenInfo)
}
}
}
post (authRefreshPathPart) {
safely(
{
// TODO:: add error info
call.respond(
HttpStatusCode.InternalServerError,
"Something went wrong"
)
}
) {
val refreshToken = call.receive<RefreshToken>()
val tokenInfo = authFeature.refresh(refreshToken)
if (tokenInfo == null) {
if (call.response.status() == null) {
call.respond(HttpStatusCode.Forbidden)
}
} else {
call.sessions.set(tokenSessionKey, tokenInfo.token)
call.respond(tokenInfo)
}
}
}
post(authGetMePathPart) {
safely(
{
// TODO:: add error info
call.respond(
HttpStatusCode.InternalServerError,
"Something went wrong"
)
}
) {
call.respond(
authFeature.getMe(call.receive()) ?: HttpStatusCode.NoContent
)
}
}
}

View File

@@ -0,0 +1,18 @@
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

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

View File

@@ -0,0 +1,17 @@
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

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

View File

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

View File

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

View File

@@ -12,6 +12,15 @@ interface ModuleLoader {
}
}
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)

View File

@@ -6,8 +6,6 @@ import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import org.koin.core.qualifier.StringQualifier
val UIFSMQualifier = StringQualifier("UIFSM")
fun UIFSM(
repo: DefaultStatesManagerRepo<UIFSMState>,
handlersSetter: FSMBuilder<UIFSMState>.() -> Unit

View File

@@ -1,4 +1,4 @@
package dev.inmo.postssystem.features.common.server.sessions
package dev.inmo.postssystem.features.common.server
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
import io.ktor.server.application.Application

View File

@@ -1,8 +1,9 @@
package dev.inmo.postssystem.features.common.server.sessions
package dev.inmo.postssystem.features.common.server
import org.koin.core.qualifier.StringQualifier
object Qualifiers {
val filesFolderQualifier = StringQualifier("filesFolder")
val commonFilesFolderQualifier = StringQualifier("commonFilesFolder")
val usersRolesKeyValueFactoryQualifier = StringQualifier("usersRolesKeyValueFactory")
}

View File

@@ -1,4 +1,4 @@
package dev.inmo.postssystem.features.common.server.sessions
package dev.inmo.postssystem.features.common.server
import kotlinx.serialization.json.JsonObject
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.postssystem.features.common.common.singleWithBinds
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
import dev.inmo.postssystem.features.common.server.sessions.Qualifiers
import dev.inmo.postssystem.features.common.server.sessions.ServerModuleLoader
import dev.inmo.postssystem.features.common.server.Qualifiers
import dev.inmo.postssystem.features.common.server.ServerModuleLoader
import dev.inmo.postssystem.features.content.common.BinaryContent
import dev.inmo.postssystem.features.content.server.ServerContentStorageWrapper
import dev.inmo.postssystem.features.files.common.*

View File

@@ -1,7 +1,6 @@
package dev.inmo.postssystem.features.content.common
import dev.inmo.micro_utils.common.FileName
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.mime_types.MimeType
import dev.inmo.postssystem.features.common.common.SimpleInputProvider
import kotlinx.serialization.PolymorphicSerializer
@@ -30,6 +29,18 @@ data class BinaryContent(
) : Content
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

View File

@@ -1,7 +1,7 @@
package dev.inmo.postssystem.features.content.text.server
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
import dev.inmo.postssystem.features.common.server.sessions.ServerModuleLoader
import dev.inmo.postssystem.features.common.server.ServerModuleLoader
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
import dev.inmo.postssystem.features.content.server.ServerContentStorageWrapper
import dev.inmo.postssystem.features.content.text.common.TextContent

View File

@@ -25,7 +25,6 @@ class ClientReadFilesStorage(
MetaFileInfoStorageWrapper.serializer().nullable,
FileId.serializer()
) {
private val unifiedRequester = UnifiedRequester(client, serialFormat)
private val fullFilesPath = buildStandardUrl(baseUrl, filesRootPathPart)
private val fullFilesGetBytesPath = buildStandardUrl(
fullFilesPath,

View File

@@ -4,10 +4,13 @@ import dev.inmo.postssystem.features.files.common.*
import dev.inmo.postssystem.features.files.common.storage.*
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadStandardCrudRepoRoutes
import dev.inmo.micro_utils.repos.ktor.server.crud.configureWriteStandardCrudRepoRoutes
import dev.inmo.micro_utils.repos.ktor.server.crud.*
import io.ktor.http.HttpStatusCode
import io.ktor.http.decodeURLQueryComponent
import io.ktor.server.application.call
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.routing.*
import kotlinx.serialization.builtins.nullable
@@ -15,47 +18,33 @@ import kotlinx.serialization.builtins.nullable
class FilesRoutingConfigurator(
private val filesStorage: ReadFilesStorage,
private val writeFilesStorage: WriteFilesStorage?,
private val unifierRouter: UnifiedRouter
) : ApplicationRoutingConfigurator.Element {
constructor(filesStorage: FilesStorage, unifierRouter: UnifiedRouter) : this(filesStorage, filesStorage, unifierRouter)
constructor(filesStorage: FilesStorage) : this(filesStorage, filesStorage)
override fun Route.invoke() {
authenticate {
route(filesRootPathPart) {
configureReadStandardCrudRepoRoutes(
configureReadCRUDRepoRoutes(
filesStorage,
MetaFileInfoStorageWrapper.serializer(),
MetaFileInfoStorageWrapper.serializer().nullable,
FileId.serializer(),
unifierRouter
::FileId
)
writeFilesStorage ?.let {
configureWriteStandardCrudRepoRoutes(
writeFilesStorage,
FullFileInfoStorageWrapper.serializer(),
FullFileInfoStorageWrapper.serializer().nullable,
FullFileInfo.serializer(),
FileId.serializer(),
unifierRouter
)
configureWriteCRUDRepoRoutes(writeFilesStorage)
}
unifierRouter.apply {
post(filesGetFilesPathPart) {
call.respondBytes(
filesStorage.getBytes(
uniload(FileId.serializer())
)
post(filesGetFilesPathPart) {
call.respondBytes(
filesStorage.getBytes(
call.receive()
)
}
get(filesGetFullFileInfoPathPart) {
unianswer(
FullFileInfoStorageWrapper.serializer().nullable,
filesStorage.getFullFileInfo(
decodeUrlQueryValueOrSendError(filesFileIdParameter, FileId.serializer()) ?: return@get
)
)
}
)
}
get(filesGetFullFileInfoPathPart) {
call.respond(
filesStorage.getFullFileInfo(
FileId(call.getParameterOrSendError(filesFileIdParameter) ?.decodeURLQueryComponent() ?: return@get)
) ?: HttpStatusCode.NoContent
)
}
}
}

View File

@@ -25,8 +25,8 @@ typealias ContentIds = List<ContentId>
* @see RegisteredPost
*/
@Serializable
sealed class Post {
abstract val content: ContentIds
sealed interface Post {
val content: ContentIds
}
/**
@@ -35,7 +35,7 @@ sealed class Post {
@Serializable
data class NewPost(
override val content: ContentIds
) : Post()
) : Post
/**
* Registered [Post]
@@ -46,12 +46,12 @@ data class RegisteredPost(
override val content: ContentIds,
@Serializable(DateTimeSerializer::class)
val creationDate: DateTime
) : Post()
) : Post
@Serializable
data class PostWithContent(
val post: Post,
val content: List<Content>
val content: List<@Polymorphic Content>
)
@Serializable

View File

@@ -6,12 +6,12 @@ import kotlinx.serialization.KSerializer
class ClientRolesStorage<T : Role>(
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,
private val client: HttpClient,
private val serializer: KSerializer<T>
) : RolesStorage<T>,
ReadRolesStorage<T> by ReadClientRolesStorage(
baseUrl, unifiedRequester, serializer
baseUrl, client, serializer
),
WriteRolesStorage<T> by WriteClientRolesStorage(
baseUrl, unifiedRequester, serializer
baseUrl, client, serializer
)

View File

@@ -3,13 +3,15 @@ package dev.inmo.postssystem.features.roles.client
import dev.inmo.postssystem.features.roles.common.*
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
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.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
class ReadClientRolesStorage<T : Role>(
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,
private val client: HttpClient,
private val serializer: KSerializer<T>
) : ReadRolesStorage<T> {
private val userRolesSerializer = ListSerializer(serializer)
@@ -21,14 +23,13 @@ class ReadClientRolesStorage<T : Role>(
override suspend fun getSubjects(
role: T
): List<RoleSubject> = unifiedRequester.uniget(
): List<RoleSubject> = client.get(
buildStandardUrl(
userRolesFullUrl,
usersRolesGetSubjectsPathPart,
usersRolesRoleQueryParameterName to unifiedRequester.encodeUrlQueryValue(serializer, role)
),
RoleSubjectsSerializer
)
)
).body()
override suspend fun getRoles(
subject: RoleSubject

View File

@@ -8,7 +8,7 @@ import kotlinx.serialization.builtins.serializer
class WriteClientRolesStorage<T : Role>(
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,
private val client: HttpClient,
private val serializer: KSerializer<T>
) : WriteRolesStorage<T> {
private val wrapperSerializer = RolesStorageIncludeExcludeWrapper.serializer(

View File

@@ -6,7 +6,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
@Serializable(RoleSerializer::class)
@Polymorphic
interface Role {
companion object {
fun serializer(): KSerializer<Role> = RoleSerializer

View File

@@ -1,17 +1,14 @@
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.manager.common.RolesManagerRole
import dev.inmo.postssystem.features.roles.server.RolesStorageWriteServerRoutesConfigurator
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
class RolesManagerRolesStorageServerRoutesConfigurator(
storage: RolesStorage<RolesManagerRole>,
unifiedRouter: UnifiedRouter
storage: RolesStorage<RolesManagerRole>
) : ApplicationRoutingConfigurator.Element by RolesStorageWriteServerRoutesConfigurator(
storage,
RolesManagerRole.serializer(),
RolesManagerRolesChecker.key,
unifiedRouter = unifiedRouter
RolesManagerRolesChecker.key
)

View File

@@ -12,8 +12,7 @@ class RolesStorageWriteServerRoutesConfigurator<T : Role>(
private val storage: WriteRolesStorage<T>,
private val serializer: KSerializer<T>,
private val includeAuthKey: String,
private val excludeAuthKey: String = includeAuthKey,
private val unifiedRouter: UnifiedRouter
private val excludeAuthKey: String = includeAuthKey
) : ApplicationRoutingConfigurator.Element {
override fun Route.invoke() {
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.server.principal
import dev.inmo.postssystem.features.auth.server.tokens.AuthTokensService
import dev.inmo.postssystem.features.common.server.sessions.ApplicationAuthenticationConfigurator
import dev.inmo.postssystem.features.common.server.ApplicationAuthenticationConfigurator
import dev.inmo.postssystem.features.roles.common.Role
import dev.inmo.postssystem.features.roles.common.RolesStorage
import io.ktor.http.HttpStatusCode

View File

@@ -11,8 +11,7 @@ import kotlinx.serialization.builtins.serializer
class RolesStorageReadServerRoutesConfigurator<T : Role>(
private val storage: ReadRolesStorage<T>,
private val serializer: KSerializer<T>,
private val unifiedRouter: UnifiedRouter
private val serializer: KSerializer<T>
) : ApplicationRoutingConfigurator.Element {
private val userRolesSerializer = ListSerializer(serializer)
override fun Route.invoke() {

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import kotlinx.serialization.builtins.serializer
class SimplePublicatorServiceClient(
baseUrl: String,
private val unifiedRequester: UnifiedRequester
private val client: HttpClient
) : SimplePublicatorService {
private val fullUrl = buildStandardUrl(
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.postssystem.features.common.common.singleWithRandomQualifier
import dev.inmo.postssystem.features.common.server.sessions.ServerModuleLoader
import dev.inmo.postssystem.features.common.server.ServerModuleLoader
import kotlinx.serialization.json.JsonObject
import org.koin.core.module.Module

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package dev.inmo.postssystem.targets.telegram.loader.server
import dev.inmo.postssystem.features.common.common.singleWithRandomQualifier
import dev.inmo.postssystem.features.common.server.sessions.ServerModuleLoader
import dev.inmo.postssystem.features.common.server.ServerModuleLoader
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
import dev.inmo.postssystem.features.publication.server.PublicationTarget
import dev.inmo.postssystem.targets.telegram.content.polls.common.PollContent