Initial commit
This commit is contained in:
36 files changed
+5204
No files matched your search
@@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
kotlin("plugin.serialization")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
listOf(
|
||||
linuxX64(),
|
||||
mingwX64()
|
||||
).forEach { it.binaries.executable { entryPoint = "snippets.main" } }
|
||||
|
||||
val ktorVersion = "3.5.2"
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation("io.ktor:ktor-server-core:${ktorVersion}")
|
||||
implementation("io.ktor:ktor-server-cio:${ktorVersion}")
|
||||
implementation("io.ktor:ktor-server-content-negotiation:${ktorVersion}")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:${ktorVersion}")
|
||||
implementation("io.ktor:ktor-server-cors:${ktorVersion}")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.9.1")
|
||||
}
|
||||
|
||||
linuxX64Main.dependencies {
|
||||
|
||||
}
|
||||
|
||||
mingwX64Main.dependencies {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/8/27
|
||||
*/
|
||||
|
||||
|
||||
package snippets
|
||||
|
||||
import kotlinx.io.files.Path
|
||||
import kotlinx.io.files.SystemFileSystem
|
||||
|
||||
val dataDir = Path("./data/snippets/")
|
||||
.apply { if (!SystemFileSystem.exists(this)) SystemFileSystem.createDirectories(this) }
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/8/27
|
||||
*/
|
||||
|
||||
|
||||
package snippets.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DirStats(
|
||||
val fileCount: Long,
|
||||
val totalBytes: Long,
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/8/27
|
||||
*/
|
||||
|
||||
|
||||
package snippets.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SnippetContent(
|
||||
val content: String,
|
||||
val filename: String,
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/8/27
|
||||
*/
|
||||
|
||||
|
||||
package snippets.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Serializable
|
||||
data class SnippetMeta(
|
||||
val createdAt: Instant,
|
||||
val filename: String,
|
||||
val content: String,
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/8/27
|
||||
*/
|
||||
|
||||
|
||||
package snippets
|
||||
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.cio.CIO
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import snippets.routing.registerSnippetsRouting
|
||||
import snippets.routing.registerStatusRouting
|
||||
|
||||
fun main() {
|
||||
embeddedServer(CIO, port = 6868, module = Application::module)
|
||||
.start(true)
|
||||
}
|
||||
|
||||
fun Application.module() {
|
||||
registerSnippetsRouting()
|
||||
registerStatusRouting()
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/8/27
|
||||
*/
|
||||
|
||||
|
||||
package snippets.routing
|
||||
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.plugins.cors.routing.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.io.Buffer
|
||||
import kotlinx.io.buffered
|
||||
import kotlinx.io.files.Path
|
||||
import kotlinx.io.files.SystemFileSystem
|
||||
import kotlinx.io.readString
|
||||
import snippets.data.SnippetContent
|
||||
import snippets.data.SnippetMeta
|
||||
import snippets.dataDir
|
||||
import snippets.util.encodeJson
|
||||
import snippets.util.fromJson
|
||||
import kotlin.time.Clock
|
||||
|
||||
fun Application.registerSnippetsRouting() {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
|
||||
install(CORS) {
|
||||
allowMethod(HttpMethod.Options)
|
||||
allowMethod(HttpMethod.Get)
|
||||
allowMethod(HttpMethod.Post)
|
||||
allowMethod(HttpMethod.Put)
|
||||
allowMethod(HttpMethod.Delete)
|
||||
|
||||
allowHeader(HttpHeaders.ContentType)
|
||||
allowHeader(HttpHeaders.Authorization)
|
||||
allowHeader(HttpHeaders.AccessControlAllowOrigin)
|
||||
|
||||
anyHost()
|
||||
}
|
||||
|
||||
routing {
|
||||
post("/api/v1/snippets") {
|
||||
val payload = call.receive<SnippetContent>()
|
||||
val snippetId = generateShortId()
|
||||
val meta = SnippetMeta(Clock.System.now(), payload.filename, payload.content)
|
||||
.encodeJson().toByteArray()
|
||||
val snippetPath = Path(dataDir, snippetId).apply { SystemFileSystem.createDirectories(this) }
|
||||
SystemFileSystem.sink(Path(snippetPath, "meta.json")).use {
|
||||
val buffer = Buffer().apply { write(meta) }
|
||||
it.write(buffer, meta.size.toLong())
|
||||
}
|
||||
call.respond(status = HttpStatusCode.Created, message = mapOf("snippet_id" to snippetId))
|
||||
}
|
||||
|
||||
get("/{snippetId}") {
|
||||
val mode = call.queryParameters["mode"] ?: "serialized"
|
||||
val snippetId = call.parameters["snippetId"] ?: return@get call.respond(HttpStatusCode.BadRequest)
|
||||
val snippetPath = Path(dataDir, snippetId)
|
||||
|
||||
if (!SystemFileSystem.exists(snippetPath)) {
|
||||
call.respond(HttpStatusCode.NotFound)
|
||||
return@get
|
||||
}
|
||||
|
||||
val meta = SystemFileSystem.source(Path(snippetPath, "meta.json"))
|
||||
.use { it.buffered().readString().fromJson<SnippetMeta>() }
|
||||
if (mode == "raw") call.respond(meta.content) else call.respond(meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun generateShortId(length: Int = 6): String {
|
||||
val charPool = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
return (1..length)
|
||||
.map { charPool.random() }
|
||||
.joinToString("")
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/8/27
|
||||
*/
|
||||
|
||||
|
||||
package snippets.routing
|
||||
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.*
|
||||
import kotlinx.io.files.Path
|
||||
import kotlinx.io.files.SystemFileSystem
|
||||
import snippets.data.DirStats
|
||||
import snippets.dataDir
|
||||
|
||||
fun Application.registerStatusRouting() {
|
||||
routing {
|
||||
get("/api/v1/status") {
|
||||
call.respond(HttpStatusCode.OK, getDirStats(dataDir))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getDirStats(dirPath: Path): DirStats {
|
||||
if (!SystemFileSystem.exists(dirPath)) return DirStats(0, 0)
|
||||
var count = 0L
|
||||
var bytes = 0L
|
||||
SystemFileSystem.list(dirPath).forEach { path ->
|
||||
val metadata = SystemFileSystem.metadataOrNull(path) ?: return@forEach
|
||||
when {
|
||||
metadata.isRegularFile -> {
|
||||
count++
|
||||
bytes += metadata.size
|
||||
}
|
||||
|
||||
metadata.isDirectory -> {
|
||||
val subStats = getDirStats(path)
|
||||
count += subStats.fileCount
|
||||
bytes += subStats.totalBytes
|
||||
}
|
||||
}
|
||||
}
|
||||
return DirStats(count, bytes)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/8/27
|
||||
*/
|
||||
|
||||
|
||||
package snippets.util
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
val json: Json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
classDiscriminator = "_json_type_"
|
||||
encodeDefaults = true
|
||||
coerceInputValues = true
|
||||
decodeEnumsCaseInsensitive = true
|
||||
isLenient = true
|
||||
}
|
||||
|
||||
inline fun <reified T> T.encodeJson(): String {
|
||||
return json.encodeToString(this)
|
||||
}
|
||||
|
||||
inline fun <reified T> String.fromJson(): T {
|
||||
return json.decodeFromString<T>(this)
|
||||
}
|
||||
Reference in New Issue
Block a user