full reborn

This commit is contained in:
2021-11-24 13:52:27 +06:00
parent 0ac6b0a4df
commit 6a6a197041
246 changed files with 4327 additions and 6952 deletions

21
server/Makefile Normal file
View File

@@ -0,0 +1,21 @@
#!make
# include .env
export $(shell sed 's/=.*//' .env)
.ONESHELL:
.PHONY: clean build upPostgres composeBuild runExample addTestUserAndAuth startTestPostgres
clean:
../gradlew clean
build:
../gradlew build distTar
startTestServer:
../gradlew run --args="test.config.json"
startTestPostgres:
sudo docker-compose up
addTestUserAndAuth:
docker-compose exec test_postgres psql test -U test -c "INSERT INTO Users VALUES (-1, 'test', 'test', 'test');INSERT INTO UsersAuthentications VALUES ('test', -1);"

25
server/build.gradle Normal file
View File

@@ -0,0 +1,25 @@
plugins {
id "org.jetbrains.kotlin.jvm"
id "org.jetbrains.kotlin.plugin.serialization"
id "application"
}
version = null
application {
mainClass = 'dev.inmo.postssystem.server.EntranceKt'
}
dependencies {
api project(":postssystem.features.common.server")
api project(":postssystem.features.status.server")
api project(":postssystem.features.files.server")
api project(":postssystem.features.users.server")
api project(":postssystem.features.auth.server")
api project(":postssystem.features.roles.server")
api project(":postssystem.features.roles.manager.server")
api "io.ktor:ktor-server-netty:$ktor_version"
api "io.ktor:ktor-websockets:$ktor_version"
api "org.jetbrains.exposed:exposed-jdbc:$kotlin_exposed_version"
api "org.postgresql:postgresql:$psql_version"
}

11
server/docker-compose.yml Normal file
View File

@@ -0,0 +1,11 @@
version: "3.4"
services:
test_postgres:
image: postgres
environment:
POSTGRES_USER: "test"
POSTGRES_PASSWORD: "test"
POSTGRES_DB: "test"
ports:
- "8090:5432"

View File

@@ -0,0 +1,10 @@
package dev.inmo.postssystem.server
import dev.inmo.postssystem.features.common.common.Milliseconds
import kotlinx.serialization.Serializable
import java.util.concurrent.TimeUnit
@Serializable
data class AuthConfig(
val sessionAge: Milliseconds = TimeUnit.DAYS.toMillis(1)
)

View File

@@ -0,0 +1,29 @@
package dev.inmo.postssystem.server
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import io.ktor.application.call
import io.ktor.http.content.files
import io.ktor.http.content.static
import io.ktor.response.respondRedirect
import io.ktor.routing.Route
import io.ktor.routing.get
import java.io.File
class ClientStaticRoutingConfiguration(
clientStatic: String?
) : ApplicationRoutingConfigurator.Element {
private val staticFile = clientStatic ?.let { File(clientStatic).takeIf { it.exists() } }
override fun Route.invoke() {
staticFile ?.let {
static("client") {
files(it)
get {
call.respondRedirect("client/index.html")
}
}
get {
call.respondRedirect("client")
}
}
}
}

View File

@@ -0,0 +1,21 @@
package dev.inmo.postssystem.server
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.io.File
@Serializable
data class Config(
val host: String = "0.0.0.0",
val port: Int = 8080,
@SerialName("database")
val databaseConfig: DatabaseConfig = DatabaseConfig(),
@SerialName("auth")
val authConfig: AuthConfig = AuthConfig(),
val clientStatic: String? = null,
val filesFolder: String,
val debugMode: Boolean = false
) {
val filesFolderFile: File
get() = File(filesFolder)
}

View File

@@ -0,0 +1,169 @@
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.common.getAllDistinct
import dev.inmo.postssystem.features.common.common.singleWithBinds
import dev.inmo.postssystem.features.common.server.sessions.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.KeyValuesUsersRolesOriginalRepo
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRole
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRoleStorage
import dev.inmo.postssystem.features.roles.manager.server.RolesManagerRolesChecker
import dev.inmo.postssystem.features.roles.manager.server.RolesManagerUsersRolesStorageServerRoutesConfigurator
import dev.inmo.postssystem.features.roles.server.*
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.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 io.ktor.application.featureOrNull
import io.ktor.application.log
import io.ktor.routing.Route
import io.ktor.routing.Routing
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.netty.Netty
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json
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.dsl.*
import java.io.File
private fun Route.print() {
println(this)
children.forEach {
it.print()
}
}
fun getDIModule(
vararg args: String,
baseScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
): Module {
val json = Json {
ignoreUnknownKeys = true
}
val config = json.decodeFromString(Config.serializer(), File(args.first()).readText())
val originalFilesMetasKeyValueRepoQualifier = StringQualifier("OriginalFilesMetaKV")
val filesMetasKeyValueRepoQualifier = StringQualifier("FilesMetaKV")
val filesFolderQualifier = StringQualifier("filesFolder")
val reviewVersionsQualifier = StringQualifier("reviewVersions")
val releaseVersionsQualifier = StringQualifier("releaseVersions")
val usersRolesKeyValueFactoryQualifier = StringQualifier("usersRolesKeyValueFactory")
return module {
single { json }
single<StringFormat> { get<Json>() }
singleWithBinds { config }
singleWithBinds { get<Config>().databaseConfig }
singleWithBinds { get<Config>().authConfig }
singleWithBinds(filesFolderQualifier) { get<Config>().filesFolderFile }
singleWithBinds { get<DatabaseConfig>().database }
singleWithBinds(originalFilesMetasKeyValueRepoQualifier) {
ExposedKeyValueRepo(get(), { text("fileid") }, { text("metaInfo") }, "FileIdsToMetas")
}
singleWithBinds(filesMetasKeyValueRepoQualifier) {
MetasKeyValueRepo(
get(),
get(originalFilesMetasKeyValueRepoQualifier)
)
}
single<FilesStorage> { DiskFilesStorage(get(filesFolderQualifier), get(filesMetasKeyValueRepoQualifier)) }
single<WriteFilesStorage> { WriteDistFilesStorage(get(filesFolderQualifier), get(filesMetasKeyValueRepoQualifier)) }
single<FullFilesStorage> { DefaultFullFilesStorage(get(), get()) }
singleWithBinds { ExposedUsersStorage(get()) }
singleWithBinds { exposedUsersAuthenticator(get(), get()) }
factory<KeyValuesUsersRolesOriginalRepo>(usersRolesKeyValueFactoryQualifier) { (tableName: String) ->
ExposedOneToManyKeyValueRepo(get(), { long("userId") }, { text("role") }, tableName)
}
single {
RolesManagerRoleStorage(get(usersRolesKeyValueFactoryQualifier) { ParametersHolder(mutableListOf("rolesManager")) })
}
single<UsersRolesStorage<RolesManagerRole>>(StringQualifier("RolesManagerRoleStorage")) { get<RolesManagerRoleStorage>() }
singleWithBinds {
UsersRolesStorageHolder(
RolesManagerRole::class,
get<RolesManagerRoleStorage>()
)
}
singleWithBinds<UsersRolesStorage<UserRole>> { UsersRolesAggregator(getAll()) }
// Roles checkers
single<RolesChecker<UserRole>>(StringQualifier(RolesManagerRolesChecker.key)) { RolesManagerRolesChecker }
factory<CoroutineScope> { baseScope.LinkedSupervisorScope() }
// Routing configurators
singleWithBinds { FilesRoutingConfigurator(get(), null) }
singleWithBinds { StatusRoutingConfigurator }
singleWithBinds { UsersStorageServerRoutesConfigurator(get()) }
singleWithBinds { UsersRolesStorageReadServerRoutesConfigurator<UserRole>(get(), UserRoleSerializer) }
singleWithBinds { RolesManagerUsersRolesStorageServerRoutesConfigurator(get()) }
singleWithBinds { ClientStaticRoutingConfiguration(get<Config>().clientStatic) }
singleWithBinds {
UsersRolesAuthenticationConfigurator<UserRole>(
get(),
get(),
getAll()
)
}
// Session and auth configurators
singleWithBinds { SessionAuthenticationConfigurator(get<AuthConfig>().sessionAge) }
singleWithBinds {
DefaultAuthTokensService(
get<Database>(),
get(),
get(),
get<AuthConfig>().sessionAge,
get()
)
}
singleWithBinds { AuthenticationRoutingConfigurator(get(), get()) }
if (config.debugMode) {
single<ApplicationRoutingConfigurator.Element>(StringQualifier("Tracer")) { ApplicationRoutingConfigurator.Element {(this as Routing).trace { application.log.trace(it.buildText()) } } }
}
singleWithBinds { ApplicationRoutingConfigurator(getAllDistinct()) }
singleWithBinds { ApplicationCachingHeadersConfigurator(getAllDistinct()) }
singleWithBinds { ApplicationSessionsConfigurator(getAllDistinct()) }
singleWithBinds { StatusPagesConfigurator(getAllDistinct()) }
singleWithBinds { ApplicationAuthenticationConfigurator(getAllDistinct()) }
singleWithBinds { WebSocketsConfigurator }
factory<ApplicationEngine> {
val config = get<Config>()
createKtorServer(
Netty,
config.host,
config.port
) {
getAllDistinct<KtorApplicationConfigurator>().also(::println).forEach {
it.apply { configure() }
}
if (config.debugMode) {
featureOrNull(Routing) ?.print()
}
}
}
}
}

View File

@@ -0,0 +1,26 @@
package dev.inmo.postssystem.server
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import org.jetbrains.exposed.sql.Database
const val defaultDatabaseParamsName = "defaultDatabase"
inline val Map<String, Any>.database: Database?
get() = (get(defaultDatabaseParamsName) as? DatabaseConfig) ?.database
@Serializable
data class DatabaseConfig(
val url: String = "jdbc:postgresql://localhost:5432/tablet",
val driver: String = org.postgresql.Driver::class.qualifiedName!!,
val username: String = "",
val password: String = ""
) {
@Transient
val database: Database = Database.connect(
url,
driver,
username,
password
)
}

View File

@@ -0,0 +1,10 @@
package dev.inmo.postssystem.server
import io.ktor.server.engine.ApplicationEngine
import org.koin.core.context.startKoin
fun main(vararg args: String) {
startKoin {
modules(getDIModule(*args))
}.koin.get<ApplicationEngine>().start(wait = true)
}

View File

@@ -0,0 +1,12 @@
package dev.inmo.postssystem.server
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.websocket.WebSockets
object WebSocketsConfigurator : KtorApplicationConfigurator {
override fun Application.configure() {
install(WebSockets)
}
}

10
server/test.config.json Normal file
View File

@@ -0,0 +1,10 @@
{
"database": {
"url": "jdbc:postgresql://127.0.0.1:8090/test",
"username": "test",
"password": "test"
},
"clientStatic": "../client/build/distributions",
"filesFolder": "/tmp/files",
"debugMode": true
}