commit e80c97e4500c5f88b7547cfae4859220f5587f08 Author: kageru Date: Thu Sep 19 22:04:23 2019 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..736c56d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.gradle/ +build/ +kodeshare.log diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..0548c93 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,32 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.3.50" + application +} + +group = "moe.kageru" +version = "0.1.0" + +val mainClass = "moe.kageru.kodeshare.KodeshareKt" +application { + mainClassName = mainClass +} +val ktorVersion = "1.2.4" + +repositories { + mavenCentral() + jcenter() +} + +dependencies { + implementation(kotlin("stdlib-jdk8")) + implementation("io.ktor:ktor-server-netty:$ktorVersion") + implementation("io.ktor:ktor-locations:$ktorVersion") + implementation("org.jetbrains.exposed:exposed:0.17.3") + implementation("org.mariadb.jdbc:mariadb-java-client:2.4.4") +} + +tasks.withType { + kotlinOptions.jvmTarget = "1.8" +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..d0e8d17 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'kodeshare' + diff --git a/src/main/kotlin/moe/kageru/kodeshare/Kodeshare.kt b/src/main/kotlin/moe/kageru/kodeshare/Kodeshare.kt new file mode 100644 index 0000000..25d90dd --- /dev/null +++ b/src/main/kotlin/moe/kageru/kodeshare/Kodeshare.kt @@ -0,0 +1,22 @@ +package moe.kageru.kodeshare + +import io.ktor.application.install +import io.ktor.features.DefaultHeaders +import io.ktor.locations.Locations +import io.ktor.routing.routing +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import moe.kageru.kodeshare.Routes.createRoutes + +@ExperimentalStdlibApi +fun main() { + embeddedServer(Netty, 9092) { + install(DefaultHeaders) + install(Locations) + routing { + createRoutes() + } + }.start(wait = true) +} + +data class Paste(val content: String, val html: String?) \ No newline at end of file diff --git a/src/main/kotlin/moe/kageru/kodeshare/Log.kt b/src/main/kotlin/moe/kageru/kodeshare/Log.kt new file mode 100644 index 0000000..60176dd --- /dev/null +++ b/src/main/kotlin/moe/kageru/kodeshare/Log.kt @@ -0,0 +1,32 @@ +package moe.kageru.kodeshare + +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.logging.FileHandler +import java.util.logging.Formatter +import java.util.logging.LogRecord +import java.util.logging.Logger + +object Log { + private val log: Logger by lazy { + val log = Logger.getGlobal() + val fh = FileHandler("kodeshare.log", true) + val formatter = LogFormatter() + fh.formatter = formatter + log.addHandler(fh) + return@lazy log + } + + fun info(message: String) { + log.info(message) + } +} + +private class LogFormatter : Formatter() { + private val timeFormatter: DateTimeFormatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()) + + override fun format(record: LogRecord): String { + return "[${record.level}] ${timeFormatter.format(record.instant)}: ${record.message}\n" + } +} diff --git a/src/main/kotlin/moe/kageru/kodeshare/Routes.kt b/src/main/kotlin/moe/kageru/kodeshare/Routes.kt new file mode 100644 index 0000000..8b4d903 --- /dev/null +++ b/src/main/kotlin/moe/kageru/kodeshare/Routes.kt @@ -0,0 +1,65 @@ +package moe.kageru.kodeshare + +import io.ktor.application.ApplicationCall +import io.ktor.application.call +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.content.PartData +import io.ktor.http.content.forEachPart +import io.ktor.http.content.streamProvider +import io.ktor.locations.Location +import io.ktor.locations.get +import io.ktor.request.receiveMultipart +import io.ktor.response.respond +import io.ktor.response.respondText +import io.ktor.routing.Routing +import io.ktor.routing.get +import io.ktor.routing.post +import moe.kageru.kodeshare.persistence.PasteDao + +@ExperimentalStdlibApi +object Routes { + fun Routing.createRoutes() { + get("/") { + call.respondText("Hello, world!", ContentType.Text.Html) + } + post("/") { + save(call) + } + get { req -> + respondGet(call, req) + } + } +} + +@ExperimentalStdlibApi +suspend fun save(call: ApplicationCall) { + call.receiveMultipart().forEachPart { part -> + when(part) { + is PartData.FileItem -> { + Log.info("new file") + val content = part.streamProvider().use { it.readAllBytes().decodeToString() } + val id = PasteDao.insert(Paste(content = content, html = null)).id + call.respond(HttpStatusCode.Created, "$id") + Log.info("Saving new paste with ID $id") + } + is PartData.FormItem -> { + Log.info("form item") + } + is PartData.BinaryItem -> { + Log.info("binary item") + } + } + } +} + +suspend fun respondGet(call: ApplicationCall, req: PasteRequest) { + val id = req.id + Log.info("Retrieving paste $id") + PasteDao.select(id)?.data?.let { paste -> + call.respondText(paste.html ?: paste.content, ContentType.Text.Html) + } ?: call.respond(HttpStatusCode.NotFound, "nothing found for id $id") +} + +@Location("/{id}") +data class PasteRequest(val id: Long) \ No newline at end of file diff --git a/src/main/kotlin/moe/kageru/kodeshare/persistence/PasteDao.kt b/src/main/kotlin/moe/kageru/kodeshare/persistence/PasteDao.kt new file mode 100644 index 0000000..4993ab1 --- /dev/null +++ b/src/main/kotlin/moe/kageru/kodeshare/persistence/PasteDao.kt @@ -0,0 +1,54 @@ +package moe.kageru.kodeshare.persistence + +import moe.kageru.kodeshare.Paste +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.transaction +import org.mariadb.jdbc.MariaDbDataSource + +object PasteDao { + fun select(id: Long): Transient? = transaction { + PasteTable.select { PasteTable.id.eq(id) }.firstOrNull() + }?.toPaste() + + fun insert(paste: Paste): Transient = transaction { + val id = PasteTable.insert { + it[content] = paste.content + it[html] = paste.html + }[PasteTable.id] + Transient(id, paste) + } + + init { + val source = MariaDbDataSource().apply { + userName = "kodepaste" + setPassword("12345") + databaseName = "kode" + } + Database.connect(source) + transaction { + SchemaUtils.drop(PasteTable) + SchemaUtils.create(PasteTable) + } + } +} + +private fun ResultRow.toPaste() = Transient( + get(PasteTable.id), + Paste( + content = get(PasteTable.content), + html = get(PasteTable.html) + ) +) + +private object PasteTable : Table() { + val id = long("id").primaryKey().autoIncrement() + val content = text("content") + val html = text("html").nullable() +} + +/* + * Better have that one generic class + * after deciding to make everything else + * not generic because it would be overkill +*/ +data class Transient(val id: Long, val data: DATA)