Add web upload

This commit is contained in:
kageru 2019-09-22 18:26:28 +02:00
parent e80c97e450
commit 00111efb27
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
7 changed files with 154 additions and 31 deletions

View File

@ -17,16 +17,21 @@ val ktorVersion = "1.2.4"
repositories {
mavenCentral()
jcenter()
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-locations:$ktorVersion")
implementation("io.ktor:ktor-server-core:$ktorVersion")
implementation("io.ktor:ktor-html-builder:$ktorVersion")
implementation("org.jetbrains.exposed:exposed:0.17.3")
implementation("org.jetbrains:kotlin-css-jvm:1.0.0-pre.83-kotlin-1.3.50")
implementation("org.mariadb.jdbc:mariadb-java-client:2.4.4")
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
}

View File

@ -2,12 +2,14 @@ package moe.kageru.kodeshare
import io.ktor.application.install
import io.ktor.features.DefaultHeaders
import io.ktor.locations.KtorExperimentalLocationsAPI
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
@KtorExperimentalLocationsAPI
@ExperimentalStdlibApi
fun main() {
embeddedServer(Netty, 9092) {

View File

@ -20,6 +20,10 @@ object Log {
fun info(message: String) {
log.info(message)
}
fun warn(message: String) {
log.warning(message)
}
}
private class LogFormatter : Formatter() {

View File

@ -3,63 +3,89 @@ package moe.kageru.kodeshare
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
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.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
import io.ktor.locations.get
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.response.respondRedirect
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.get
import io.ktor.routing.post
import moe.kageru.kodeshare.pages.Css
import moe.kageru.kodeshare.pages.Homepage
import moe.kageru.kodeshare.persistence.PasteDao
@KtorExperimentalLocationsAPI
@ExperimentalStdlibApi
object Routes {
fun Routing.createRoutes() {
get("/") {
call.respondText("Hello, world!", ContentType.Text.Html)
call.respond(Homepage.content)
}
get("/style.css") {
call.respondText(Css.default, ContentType.Text.CSS)
}
post("/") {
save(call)
call.handlePost()
}
get<PasteRequest> { req ->
respondGet(call, req)
call.handleGet(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")
@ExperimentalStdlibApi
private suspend fun ApplicationCall.handlePost() {
receiveMultipart().forEachPart { part ->
when (part) {
is PartData.FileItem -> {
val content = part.streamProvider().use { it.readBytes().decodeToString() }
respondToUpload(content)?.let { id ->
Log.info("Saving new file paste with ID $id")
} ?: Log.info("Invalid upload: $content")
}
is PartData.FormItem -> {
val content = part.value
respondToUpload(content)?.let { id ->
Log.info("Saving new text paste with ID $id")
} ?: Log.info("Invalid upload: $content")
}
is PartData.BinaryItem -> {
Log.warn("Received binary item from upload form. This shouldn’t happen.")
}
}
}
}
private suspend fun ApplicationCall.respondToUpload(content: String): Long? {
content.ifBlank {
respondText("Empty pastes are not allowed", ContentType.Text.Any, HttpStatusCode.BadRequest)
return null
}
val id = PasteDao.insert(Paste(content, null)).id
// This will show the URL in a terminal when uploading via curl,
// while also redirecting browser uploads to the newly created paste.
// May seem odd to return code 302, but it seems to be the only way.
response.headers.append(HttpHeaders.Location, "$id")
respond(HttpStatusCode.Found, "$id")
return id
}
private suspend fun ApplicationCall.handleGet(req: PasteRequest) {
val id = req.id
Log.info("Retrieving paste $id")
PasteDao.select(id)?.data?.let { paste ->
respondText(paste.html ?: paste.content, ContentType.Text.Plain)
} ?: respond(HttpStatusCode.NotFound, "nothing found for id $id")
}
}
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")
}
@KtorExperimentalLocationsAPI
@Location("/{id}")
data class PasteRequest(val id: Long)
data class PasteRequest(val id: Long)

View File

@ -0,0 +1,56 @@
package moe.kageru.kodeshare.pages
import kotlinx.css.*
import kotlinx.css.properties.ms
import kotlinx.css.properties.transition
object Css {
private val accent1 = Color("#cd7400")
private val accent2 = Color("#ed7a00")
private val fontcolor = Color.lightGrey
val default = CSSBuilder().apply {
body {
fontFamily = "Hack, Fira Code, Noto Mono, monospace"
fontSize = 13.pt
textAlign = TextAlign.center
margin = "auto"
backgroundColor = Color.black
color = fontcolor
}
textarea {
fontFamily = "Hack, Fira Code, Noto Mono, monospace"
backgroundColor = Color.black
color = Color.white
fontSize = 13.pt
borderColor = accent1
borderWidth = 3.px
borderRadius = 8.px
borderStyle = BorderStyle.solid
padding = "5px"
minWidth = 70.pct
maxWidth = 100.pct
}
rule("input[type=\"submit\"]") {
backgroundColor = accent1
borderColor = accent1
borderWidth = 2.px
borderRadius = 5.px
borderStyle = BorderStyle.solid
color = Color.black
fontWeight = FontWeight.w600
padding = "5px 15px"
cursor = Cursor.pointer
transition(duration = 500.ms)
}
rule("input[type=\"submit\"]:hover") {
backgroundColor = Color.transparent
color = accent1
}
rule("textarea:focus") {
borderColor = accent2
}
rule("::selection") {
color = accent1
}
}.toString()
}

View File

@ -0,0 +1,30 @@
package moe.kageru.kodeshare.pages
import io.ktor.html.HtmlContent
import io.ktor.http.HttpStatusCode
import kotlinx.html.*
object Homepage {
val content = HtmlContent(HttpStatusCode.OK) {
head {
link(rel = "stylesheet", href = "/style.css", type = "text/css")
}
body {
h1 { +"kodeshare - yet another paste service" }
form("/", encType = FormEncType.multipartFormData, method = FormMethod.post) {
acceptCharset = "utf-8"
p {
label { +"Enter or paste your text here " }
}
textArea {
name = "input"
rows = "20"
cols = "100"
}
p {
submitInput { value = "Upload" }
}
}
}
}
}

View File

@ -20,7 +20,7 @@ object PasteDao {
init {
val source = MariaDbDataSource().apply {
userName = "kodepaste"
userName = "kodeshare"
setPassword("12345")
databaseName = "kode"
}