init commit (true)

This commit is contained in:
AK
2026-07-23 19:24:47 +03:00
parent c8ff305aa6
commit f79a26d3fb
62 changed files with 125 additions and 683 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.
@@ -0,0 +1,2 @@
#Wed Jul 22 21:44:56 MSK 2026
gradle.version=9.5.1
Binary file not shown.
Binary file not shown.
View File
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+6
View File
@@ -0,0 +1,6 @@
<component name="ArtifactManager">
<artifact type="jar" name="client-jvm-1.0.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/client/build/libs</output-path>
<root id="archive" name="client-jvm-1.0.0-SNAPSHOT.jar" />
</artifact>
</component>
+6
View File
@@ -0,0 +1,6 @@
<component name="ArtifactManager">
<artifact type="jar" name="core-jvm-1.0.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/core/build/libs</output-path>
<root id="archive" name="core-jvm-1.0.0-SNAPSHOT.jar" />
</artifact>
</component>
+10
View File
@@ -0,0 +1,10 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>
+5
View File
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/client" />
<option value="$PROJECT_DIR$/core" />
<option value="$PROJECT_DIR$/server" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="externalSystemId" value="Gradle" />
<option name="version" value="2.4.0" />
</component>
</project>
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="corretto-25" project-jdk-type="JavaSDK" />
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+3 -25
View File
@@ -13,28 +13,6 @@ Here's a list of features included in this project:
| Name | Description |
|------|-------------|
| [kotlinx.serialization](https://start.ktor.io/p/io.ktor/server-kotlinx-serialization) | Handles JSON serialization using kotlinx.serialization library |
| [Content Negotiation](https://start.ktor.io/p/io.ktor/server-content-negotiation) | Provides automatic content conversion according to Content-Type and Accept headers |
| [gRPC](https://start.ktor.io/p/org.jetbrains/kotlinx-rpc-grpc) | Adds gRPC services to your Ktor server with kotlinx.rpc |
| [Rate Limiting](https://start.ktor.io/p/io.github.flaxoos/server-rate-limiting) | Manage request rate limiting as you see fit |
| [Kafka](https://start.ktor.io/p/io.github.flaxoos/server-kafka) | Adds Kafka support to your application |
| [Exposed](https://start.ktor.io/p/org.jetbrains/server-exposed) | Adds Exposed database to your application |
| [Dependency Injection](https://start.ktor.io/p/io.ktor/server-di) | Enables dependency injection for your server |
| [OpenTelemetry](https://start.ktor.io/p/io.opentelemetry.instrumentation/server-open-telemetry) | Instruments applications with distributed tracing, metrics, and logging for comprehensive observability |
| [Sessions](https://start.ktor.io/p/io.ktor/server-sessions) | Adds support for persistent sessions through cookies or headers |
| [Authentication](https://start.ktor.io/p/io.ktor/server-auth) | Provides extension point for handling the Authorization header |
| [Authentication Basic](https://start.ktor.io/p/io.ktor/server-auth-basic) | Handles 'Basic' username / password authentication scheme |
| [Swagger](https://start.ktor.io/p/io.ktor/server-swagger) | Serves Swagger UI for your project |
## Structure
This project includes the following modules:
| Path | Description |
|------|-------------|
| | null |
| client | null |
| core | null |
| server | null |
## Building & Running
To build or run the project, use one of the following tasks:
@@ -42,9 +20,9 @@ To build or run the project, use one of the following tasks:
| Task | Description |
|------|-------------|
| `./gradlew :server:test` | Run the tests |
| `./gradlew :server:build` | Build the project |
| `./gradlew :server:run` | Run the server |
| `./gradlew test` | Run the tests |
| `./gradlew build` | Build the project |
| `./gradlew run` | Run the server |
If the server starts successfully, you'll see the following output:
```
+20 -7
View File
@@ -1,12 +1,25 @@
plugins {
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.kotlinx.rpc.grpc) apply false
alias(libs.plugins.kotlin.jvm)
alias(ktorLibs.plugins.ktor)
}
subprojects {
group = "tech.aklivtsov"
version = "1.0.0-SNAPSHOT"
group = "com.example"
version = "1.0.0-SNAPSHOT"
application {
mainClass = "io.ktor.server.netty.EngineMain"
}
kotlin {
jvmToolchain(21)
}
dependencies {
implementation(ktorLibs.server.config.yaml)
implementation(ktorLibs.server.core)
implementation(ktorLibs.server.netty)
implementation(libs.logback.classic)
testImplementation(kotlin("test"))
testImplementation(ktorLibs.server.testHost)
}
-23
View File
@@ -1,23 +0,0 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlinx.rpc.grpc)
}
rpc {
protoc()
}
kotlin {
jvm()
sourceSets {
commonMain.dependencies {
implementation(ktorLibs.client.core)
implementation(libs.kotlinx.rpc.grpc.client)
implementation(project(":core"))
}
}
}
@@ -1,22 +0,0 @@
package tech.aklivtsov
import kotlinx.rpc.grpc.client.GrpcClient
import kotlinx.rpc.withService
suspend fun main() {
val client = GrpcClient("localhost", GRPC_PORT) {
credentials = plaintext()
}
val service = client.withService<SampleService>()
val response = service.greeting(
ClientGreeting {
name = "World"
}
)
println("Response: ${response.content}")
client.shutdown()
client.awaitTermination()
}
@@ -1,25 +0,0 @@
package tech.aklivtsov
import io.ktor.client.HttpClient
import io.ktor.client.*
import io.ktor.http.*
import io.opentelemetry.instrumentation.ktor.v3_0.KtorClientTelemetry
val httpClient = HttpClient {
val openTelemetry = getOpenTelemetry(serviceName = "opentelemetry-ktor-sample-client")
install(KtorClientTelemetry) {
setOpenTelemetry(openTelemetry)
capturedRequestHeaders(HttpHeaders.Accept)
attributesExtractor {
onStart {
attributes.put("start-time", System.currentTimeMillis())
}
onEnd {
attributes.put("end-time", System.currentTimeMillis())
}
}
}
}
-27
View File
@@ -1,27 +0,0 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlinx.rpc.grpc)
alias(libs.plugins.kotlin.serialization)
}
rpc {
protoc()
}
kotlin {
jvm()
sourceSets {
commonMain.dependencies {
api(libs.opentelemetry.exporterOtlp)
api(libs.opentelemetry.ktorInstrumentation)
api(libs.opentelemetry.sdkAutoconfigure)
api(libs.opentelemetry.semconv)
implementation(libs.kotlinx.rpc.grpc.core)
implementation(libs.kotlinx.rpc.protobufCore)
}
}
}
@@ -1,15 +0,0 @@
package tech.aklivtsov
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk
import io.opentelemetry.semconv.ServiceAttributes
fun getOpenTelemetry(serviceName: String): OpenTelemetry {
System.setProperty("otel.metrics.exporter", "none")
return AutoConfiguredOpenTelemetrySdk.builder().addResourceCustomizer { oldResource, _ ->
oldResource.toBuilder()
.putAll(oldResource.attributes)
.put(ServiceAttributes.SERVICE_NAME, serviceName)
.build()
}.build().openTelemetrySdk
}
-3
View File
@@ -1,3 +0,0 @@
package tech.aklivtsov
const val GRPC_PORT = 8081
-15
View File
@@ -1,15 +0,0 @@
syntax = "proto3";
package tech.aklivtsov;
message ClientGreeting {
string name = 1;
}
message ServerGreeting {
string content = 2;
}
service SampleService {
rpc greeting(ClientGreeting) returns (ServerGreeting);
}
-26
View File
@@ -1,36 +1,10 @@
[versions]
exposed = "1.3.0"
flaxoos = "2.3.0"
grpc-netty = "1.82.1"
h2 = "2.4.240"
h2-r2dbc = "1.1.0.RELEASE"
kotlin = "2.4.0"
kotlinx-rpc-grpc = "0.11.0-grpc-188"
logback = "1.5.35"
opentelemetry = "1.63.0"
opentelemetry-ktor = "2.29.0-alpha"
opentelemetry-semconv = "1.42.0"
[libraries]
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-r2dbc = { module = "org.jetbrains.exposed:exposed-r2dbc", version.ref = "exposed" }
flaxoos-ktor-server-kafka = { module = "io.github.flaxoos:ktor-server-kafka", version.ref = "flaxoos" }
flaxoos-ktor-server-rateLimiting = { module = "io.github.flaxoos:ktor-server-rate-limiting", version.ref = "flaxoos" }
grpc-netty = { module = "io.grpc:grpc-netty", version.ref = "grpc-netty" }
h2database-h2 = { module = "com.h2database:h2", version.ref = "h2" }
h2database-r2dbc = { module = "io.r2dbc:r2dbc-h2", version.ref = "h2-r2dbc" }
kotlinx-rpc-grpc-client = { module = "org.jetbrains.kotlinx:kotlinx-rpc-grpc-client", version.ref = "kotlinx-rpc-grpc" }
kotlinx-rpc-grpc-core = { module = "org.jetbrains.kotlinx:kotlinx-rpc-grpc-core", version.ref = "kotlinx-rpc-grpc" }
kotlinx-rpc-grpc-ktorServer = { module = "org.jetbrains.kotlinx:kotlinx-rpc-grpc-ktor-server", version.ref = "kotlinx-rpc-grpc" }
kotlinx-rpc-protobufCore = { module = "org.jetbrains.kotlinx:kotlinx-rpc-protobuf-core", version.ref = "kotlinx-rpc-grpc" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
opentelemetry-exporterOtlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
opentelemetry-ktorInstrumentation = { module = "io.opentelemetry.instrumentation:opentelemetry-ktor-3.0", version.ref = "opentelemetry-ktor" }
opentelemetry-sdkAutoconfigure = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure", version.ref = "opentelemetry" }
opentelemetry-semconv = { module = "io.opentelemetry.semconv:opentelemetry-semconv", version.ref = "opentelemetry-semconv" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlinx-rpc-grpc = { id = "org.jetbrains.kotlinx.rpc.plugin", version.ref = "kotlinx-rpc-grpc" }
-46
View File
@@ -1,46 +0,0 @@
plugins {
alias(libs.plugins.kotlin.jvm)
alias(ktorLibs.plugins.ktor)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlinx.rpc.grpc)
}
application {
mainClass = "io.ktor.server.netty.EngineMain"
}
rpc {
protoc()
}
kotlin {
jvmToolchain(21)
}
dependencies {
implementation(ktorLibs.client.core)
implementation(ktorLibs.serialization.kotlinx.json)
implementation(ktorLibs.server.auth)
implementation(ktorLibs.server.config.yaml)
implementation(ktorLibs.server.contentNegotiation)
implementation(ktorLibs.server.core)
implementation(ktorLibs.server.di)
implementation(ktorLibs.server.netty)
implementation(ktorLibs.server.routingOpenapi)
implementation(ktorLibs.server.sessions)
implementation(ktorLibs.server.swagger)
implementation(libs.exposed.core)
implementation(libs.exposed.r2dbc)
implementation(libs.flaxoos.ktor.server.kafka)
implementation(libs.flaxoos.ktor.server.rateLimiting)
implementation(libs.grpc.netty)
implementation(libs.h2database.h2)
implementation(libs.h2database.r2dbc)
implementation(libs.kotlinx.rpc.grpc.ktorServer)
implementation(libs.logback.classic)
implementation(project(":core"))
testImplementation(kotlin("test"))
testImplementation(ktorLibs.server.testHost)
testImplementation(libs.kotlinx.rpc.grpc.client)
}
-11
View File
@@ -1,11 +0,0 @@
package tech.aklivtsov
import io.ktor.server.application.*
import io.ktor.server.plugins.di.*
// The contents of the `install` function will be used for the project template
fun Application.configureDependencyInjection() {
dependencies {
provide { GreetingService { "Hello, World!" } }
}
}
-55
View File
@@ -1,55 +0,0 @@
package tech.aklivtsov
import io.ktor.server.application.*
import io.github.flaxoos.ktor.server.plugins.kafka.Kafka
import io.github.flaxoos.ktor.server.plugins.kafka.MessageTimestampType
import io.github.flaxoos.ktor.server.plugins.kafka.TopicName
import io.github.flaxoos.ktor.server.plugins.kafka.admin
import io.github.flaxoos.ktor.server.plugins.kafka.common
import io.github.flaxoos.ktor.server.plugins.kafka.consumer
import io.github.flaxoos.ktor.server.plugins.kafka.consumerConfig
import io.github.flaxoos.ktor.server.plugins.kafka.consumerRecordHandler
import io.github.flaxoos.ktor.server.plugins.kafka.producer
import io.github.flaxoos.ktor.server.plugins.kafka.registerSchemas
import io.github.flaxoos.ktor.server.plugins.kafka.topic
import io.ktor.client.HttpClient
import io.ktor.server.application.Application
import io.ktor.server.application.install
fun Application.configureDatabases() {
install(Kafka) {
schemaRegistryUrl = "my.schemaRegistryUrl"
val myTopic = TopicName.named("my-topic")
topic(myTopic) {
partitions = 1
replicas = 1
configs {
messageTimestampType = MessageTimestampType.CreateTime
}
}
common { // <-- Define common properties
bootstrapServers = listOf("my-kafka")
retries = 1
clientId = "my-client-id"
}
admin { } // <-- Creates an admin client
producer { // <-- Creates a producer
clientId = "my-client-id"
}
consumer { // <-- Creates a consumer
groupId = "my-group-id"
clientId = "my-client-id-override" //<-- Override common properties
}
consumerConfig {
consumerRecordHandler(myTopic) { record ->
// Do something with record
}
}
registerSchemas {
using { // <-- optionally provide a client, by default CIO is used
HttpClient()
}
// MyRecord::class at myTopic // <-- Will register schema upon startup
}
}
}
-58
View File
@@ -1,58 +0,0 @@
package tech.aklivtsov
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.*
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.put
import io.ktor.server.routing.routing
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
suspend fun Application.configureExposed() {
val database = R2dbcDatabase.connect(
url = "r2dbc:h2:file:///./h2",
user = "root",
password = "",
)
val userService = ExposedUserService(database).also {
it.createSchema()
}
routing {
// Create user
post("/users") {
val user = call.receive<ExposedUser>()
val id = userService.create(user)
call.respond(HttpStatusCode.Created, id)
}
// Read user
get("/users/{id}") {
val id = call.parameters["id"]?.toUInt() ?: throw IllegalArgumentException("Invalid ID")
val user = userService.read(id)
if (user != null) {
call.respond(HttpStatusCode.OK, user)
} else {
call.respond(HttpStatusCode.NotFound)
}
}
// Update user
put("/users/{id}") {
val id = call.parameters["id"]?.toUInt() ?: throw IllegalArgumentException("Invalid ID")
val user = call.receive<ExposedUser>()
userService.update(id, user)
call.respond(HttpStatusCode.NoContent)
}
// Delete user
delete("/users/{id}") {
val id = call.parameters["id"]?.toUInt() ?: throw IllegalArgumentException("Invalid ID")
userService.delete(id)
call.respond(HttpStatusCode.NoContent)
}
}
}
@@ -1,5 +0,0 @@
package tech.aklivtsov
fun interface GreetingService {
fun sayHello(): String
}
-13
View File
@@ -1,13 +0,0 @@
package tech.aklivtsov
import io.ktor.server.application.Application
import kotlinx.rpc.grpc.ktor.server.grpc
import kotlinx.rpc.registerService
fun Application.configureGrpc() {
grpc(GRPC_PORT) {
services {
registerService<SampleService> { SampleServiceImpl() }
}
}
}
-19
View File
@@ -1,19 +0,0 @@
package tech.aklivtsov
import io.ktor.server.application.*
import io.ktor.server.plugins.swagger.*
import io.ktor.server.routing.*
fun Application.configureHttp() {
routing {
swaggerUI(path = "openapi") {
/*
Documentation source configuration goes here.
This can be from file (documentation.yaml), or it can be served dynamically from your sources using the
`describe {}` API on routes. When `openApi` enabled in Gradle, these calls will be automatically injected
based on your code and comments.
*/
}
}
}
-6
View File
@@ -1,6 +0,0 @@
package tech.aklivtsov
import kotlinx.serialization.Serializable
@Serializable
data class MySession(val count: Int = 0)
-21
View File
@@ -1,21 +0,0 @@
package tech.aklivtsov
import io.github.flaxoos.ktor.server.plugins.ratelimiter.*
import io.github.flaxoos.ktor.server.plugins.ratelimiter.implementations.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
import kotlin.time.Duration.Companion.seconds
fun Application.configureRateLimiting() {
routing {
route("/"){
install(RateLimiting) {
rateLimiter {
type = TokenBucket::class
capacity = 100
rate = 10.seconds
}
}
}
}
}
-35
View File
@@ -1,35 +0,0 @@
package tech.aklivtsov
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.auth.*
import io.ktor.server.sessions.*
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello, World!")
}
authenticate("myauth1") {
get("/protected/route/basic") {
val principal = call.principal<UserIdPrincipal>()!!
call.respondText("Hello ${principal.name}")
}
}
authenticate("myauth2") {
get("/protected/route/form") {
val principal = call.principal<UserIdPrincipal>()!!
call.respondText("Hello ${principal.name}")
}
}
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}
get("/session/increment") {
val session = call.sessions.get<MySession>() ?: MySession()
call.sessions.set(session.copy(count = session.count + 1))
call.respondText("Counter is ${session.count}. Refresh to increment.")
}
}
}
@@ -1,7 +0,0 @@
package tech.aklivtsov
class SampleServiceImpl : SampleService {
override suspend fun greeting(name: ClientGreeting): ServerGreeting {
return ServerGreeting { content = "Hello, ${name.name}!" }
}
}
-34
View File
@@ -1,34 +0,0 @@
package tech.aklivtsov
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.response.*
import io.ktor.server.sessions.*
fun Application.configureSecurity() {
authentication {
basic(name = "myauth1") {
realm = "Ktor Server"
validate { credentials ->
if (credentials.name == credentials.password) {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
form(name = "myauth2") {
userParamName = "user"
passwordParamName = "password"
challenge {
/**/
}
}
}
install(Sessions) {
cookie<MySession>("MY_SESSION") {
cookie.extensions["SameSite"] = "lax"
}
}
}
-12
View File
@@ -1,12 +0,0 @@
package tech.aklivtsov
import io.ktor.server.application.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
}
}
@@ -1,29 +0,0 @@
package tech.aklivtsov
import io.ktor.http.*
import io.ktor.server.request.*
import io.ktor.server.application.*
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.ktor.v3_0.KtorServerTelemetry
fun Application.configureOpenTelemetry() {
install(KtorServerTelemetry) {
setOpenTelemetry(getOpenTelemetry(serviceName = "ktor-sample"))
capturedRequestHeaders(HttpHeaders.UserAgent)
spanKindExtractor {
if (httpMethod == HttpMethod.Post) {
SpanKind.PRODUCER
} else {
SpanKind.CLIENT
}
}
attributesExtractor {
onStart {
attributes.put("start-time", System.currentTimeMillis())
}
onEnd {
attributes.put("end-time", System.currentTimeMillis())
}
}
}
}
-57
View File
@@ -1,57 +0,0 @@
package tech.aklivtsov
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.singleOrNull
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.core.*
import org.jetbrains.exposed.v1.core.dao.id.UIntIdTable
import org.jetbrains.exposed.v1.r2dbc.*
import org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
@Serializable
data class ExposedUser(val name: String, val age: Int)
class ExposedUserService(val database: R2dbcDatabase) {
object Users : UIntIdTable() {
val name = varchar("name", length = 50)
val age = integer("age")
}
suspend fun createSchema() {
suspendTransaction(database) {
SchemaUtils.create(Users)
}
}
suspend fun create(user: ExposedUser): UInt = suspendTransaction(database) {
val newRecord = Users.insert {
it[name] = user.name
it[age] = user.age
}
newRecord[Users.id].value
}
suspend fun read(id: UInt): ExposedUser? {
return suspendTransaction(database) {
Users.selectAll()
.where { Users.id eq id }
.map { ExposedUser(it[Users.name], it[Users.age]) }
.singleOrNull()
}
}
suspend fun update(id: UInt, user: ExposedUser) {
suspendTransaction(database) {
Users.update({ Users.id eq id }) {
it[name] = user.name
it[age] = user.age
}
}
}
suspend fun delete(id: UInt) {
suspendTransaction(database) { Users.deleteWhere { Users.id.eq(id) } }
}
}
@@ -1,15 +0,0 @@
ktor:
deployment:
port: 8080
application:
modules:
- tech.aklivtsov.HttpKt.configureHttp
- tech.aklivtsov.SerializationKt.configureSerialization
- tech.aklivtsov.DatabasesKt.configureDatabases
- tech.aklivtsov.SecurityKt.configureSecurity
- tech.aklivtsov.GrpcKt.configureGrpc
- tech.aklivtsov.RateLimitingKt.configureRateLimiting
- tech.aklivtsov.ExposedKt.configureExposed
- tech.aklivtsov.DIKt.configureDependencyInjection
- tech.aklivtsov.ServerOpenTelemetryKt.configureOpenTelemetry
- tech.aklivtsov.RoutingKt.configureRouting
@@ -1,23 +0,0 @@
openapi: "3.0.3"
info:
title: "Application API"
description: "Application API"
version: "1.0.0"
servers:
- url: "http://0.0.0.0:8080"
paths:
/:
get:
description: "Hello World!"
responses:
"200":
description: "OK"
content:
text/plain:
schema:
type: "string"
examples:
Example#1:
value: "Hello World!"
components:
schemas: {}
-40
View File
@@ -1,40 +0,0 @@
package tech.aklivtsov
import io.ktor.server.testing.testApplication
import kotlinx.rpc.grpc.client.GrpcClient
import kotlinx.rpc.grpc.ktor.server.grpc
import kotlinx.rpc.registerService
import kotlinx.rpc.withService
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.minutes
class ApplicationRpcTest {
@Test
fun testRpc() = testApplication {
application {
grpc(8081) {
services {
registerService<SampleService> { SampleServiceImpl() }
}
}
}
startApplication()
val client = GrpcClient("localhost", 8081) {
credentials = plaintext()
}
val response = client.withService<SampleService>().greeting(
ClientGreeting {
name = "Alex"
}
)
assertEquals("Hello, Alex!", response.content, "Wrong response message")
client.shutdown()
client.awaitTermination(1.minutes)
}
}
-7
View File
@@ -2,8 +2,6 @@ pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://redirector.kotlinlang.org/maven/kxrpc-grpc")
maven("https://packages.confluent.io/maven")
}
}
@@ -14,8 +12,6 @@ plugins {
dependencyResolutionManagement {
repositories {
mavenCentral()
maven("https://redirector.kotlinlang.org/maven/kxrpc-grpc")
maven("https://packages.confluent.io/maven")
}
versionCatalogs {
create("ktorLibs").from("io.ktor:ktor-version-catalog:3.5.0")
@@ -24,6 +20,3 @@ dependencyResolutionManagement {
rootProject.name = "UserService"
include(":client")
include(":core")
include(":server")
+13
View File
@@ -0,0 +1,13 @@
package com.example
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello, World!")
}
}
}
@@ -1,4 +1,4 @@
package tech.aklivtsov
package com.example
import io.ktor.server.engine.*
import io.ktor.server.application.*
+6
View File
@@ -0,0 +1,6 @@
ktor:
deployment:
port: 8080
application:
modules:
- com.example.RoutingKt.configureRouting
@@ -1,4 +1,4 @@
package tech.aklivtsov
package com.example
import io.ktor.client.request.get
import io.ktor.http.HttpStatusCode