Add web upload
This commit is contained in:
parent
e80c97e450
commit
00111efb27
@ -17,13 +17,18 @@ 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")
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -20,6 +20,10 @@ object Log {
|
||||
fun info(message: String) {
|
||||
log.info(message)
|
||||
}
|
||||
|
||||
fun warn(message: String) {
|
||||
log.warning(message)
|
||||
}
|
||||
}
|
||||
|
||||
private class LogFormatter : Formatter() {
|
||||
|
@ -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)
|
56
src/main/kotlin/moe/kageru/kodeshare/pages/Css.kt
Normal file
56
src/main/kotlin/moe/kageru/kodeshare/pages/Css.kt
Normal 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()
|
||||
}
|
30
src/main/kotlin/moe/kageru/kodeshare/pages/Homepage.kt
Normal file
30
src/main/kotlin/moe/kageru/kodeshare/pages/Homepage.kt
Normal 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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ object PasteDao {
|
||||
|
||||
init {
|
||||
val source = MariaDbDataSource().apply {
|
||||
userName = "kodepaste"
|
||||
userName = "kodeshare"
|
||||
setPassword("12345")
|
||||
databaseName = "kode"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user