Add web upload
This commit is contained in:
parent
e80c97e450
commit
00111efb27
@ -17,13 +17,18 @@ val ktorVersion = "1.2.4"
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
|
||||||
|
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-locations:$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.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")
|
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.application.install
|
||||||
import io.ktor.features.DefaultHeaders
|
import io.ktor.features.DefaultHeaders
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.Locations
|
import io.ktor.locations.Locations
|
||||||
import io.ktor.routing.routing
|
import io.ktor.routing.routing
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.netty.Netty
|
import io.ktor.server.netty.Netty
|
||||||
import moe.kageru.kodeshare.Routes.createRoutes
|
import moe.kageru.kodeshare.Routes.createRoutes
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(Netty, 9092) {
|
embeddedServer(Netty, 9092) {
|
||||||
|
@ -20,6 +20,10 @@ object Log {
|
|||||||
fun info(message: String) {
|
fun info(message: String) {
|
||||||
log.info(message)
|
log.info(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun warn(message: String) {
|
||||||
|
log.warning(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LogFormatter : Formatter() {
|
private class LogFormatter : Formatter() {
|
||||||
|
@ -3,63 +3,89 @@ package moe.kageru.kodeshare
|
|||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.http.content.PartData
|
import io.ktor.http.content.PartData
|
||||||
import io.ktor.http.content.forEachPart
|
import io.ktor.http.content.forEachPart
|
||||||
import io.ktor.http.content.streamProvider
|
import io.ktor.http.content.streamProvider
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.Location
|
import io.ktor.locations.Location
|
||||||
import io.ktor.locations.get
|
import io.ktor.locations.get
|
||||||
import io.ktor.request.receiveMultipart
|
import io.ktor.request.receiveMultipart
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.response.respondRedirect
|
||||||
import io.ktor.response.respondText
|
import io.ktor.response.respondText
|
||||||
import io.ktor.routing.Routing
|
import io.ktor.routing.Routing
|
||||||
import io.ktor.routing.get
|
import io.ktor.routing.get
|
||||||
import io.ktor.routing.post
|
import io.ktor.routing.post
|
||||||
|
import moe.kageru.kodeshare.pages.Css
|
||||||
|
import moe.kageru.kodeshare.pages.Homepage
|
||||||
import moe.kageru.kodeshare.persistence.PasteDao
|
import moe.kageru.kodeshare.persistence.PasteDao
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
object Routes {
|
object Routes {
|
||||||
fun Routing.createRoutes() {
|
fun Routing.createRoutes() {
|
||||||
get("/") {
|
get("/") {
|
||||||
call.respondText("Hello, world!", ContentType.Text.Html)
|
call.respond(Homepage.content)
|
||||||
|
}
|
||||||
|
get("/style.css") {
|
||||||
|
call.respondText(Css.default, ContentType.Text.CSS)
|
||||||
}
|
}
|
||||||
post("/") {
|
post("/") {
|
||||||
save(call)
|
call.handlePost()
|
||||||
}
|
}
|
||||||
get<PasteRequest> { req ->
|
get<PasteRequest> { req ->
|
||||||
respondGet(call, req)
|
call.handleGet(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
suspend fun save(call: ApplicationCall) {
|
private suspend fun ApplicationCall.handlePost() {
|
||||||
call.receiveMultipart().forEachPart { part ->
|
receiveMultipart().forEachPart { part ->
|
||||||
when(part) {
|
when (part) {
|
||||||
is PartData.FileItem -> {
|
is PartData.FileItem -> {
|
||||||
Log.info("new file")
|
val content = part.streamProvider().use { it.readBytes().decodeToString() }
|
||||||
val content = part.streamProvider().use { it.readAllBytes().decodeToString() }
|
respondToUpload(content)?.let { id ->
|
||||||
val id = PasteDao.insert(Paste(content = content, html = null)).id
|
Log.info("Saving new file paste with ID $id")
|
||||||
call.respond(HttpStatusCode.Created, "$id")
|
} ?: Log.info("Invalid upload: $content")
|
||||||
Log.info("Saving new paste with ID $id")
|
}
|
||||||
}
|
is PartData.FormItem -> {
|
||||||
is PartData.FormItem -> {
|
val content = part.value
|
||||||
Log.info("form item")
|
respondToUpload(content)?.let { id ->
|
||||||
}
|
Log.info("Saving new text paste with ID $id")
|
||||||
is PartData.BinaryItem -> {
|
} ?: Log.info("Invalid upload: $content")
|
||||||
Log.info("binary item")
|
}
|
||||||
|
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) {
|
@KtorExperimentalLocationsAPI
|
||||||
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}")
|
@Location("/{id}")
|
||||||
data class PasteRequest(val id: Long)
|
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 {
|
init {
|
||||||
val source = MariaDbDataSource().apply {
|
val source = MariaDbDataSource().apply {
|
||||||
userName = "kodepaste"
|
userName = "kodeshare"
|
||||||
setPassword("12345")
|
setPassword("12345")
|
||||||
databaseName = "kode"
|
databaseName = "kode"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user