Add syntax highlighting and config

This commit is contained in:
kageru 2019-09-29 08:17:31 +02:00
parent 00111efb27
commit 624320b8b1
10 changed files with 112 additions and 22 deletions

3
.gitignore vendored

@ -1,3 +1,4 @@
.gradle/
*gradle*
build/
kodeshare.log
.idea/

@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.3.50"
id("com.github.johnrengelman.shadow") version "5.1.0" apply true
application
}
@ -30,6 +31,7 @@ dependencies {
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("com.uchuhimo:konf-core:0.20.0")
}
tasks.withType<KotlinCompile> {

1
kodeshare.properties Normal file

@ -0,0 +1 @@
database.password=12345

@ -8,6 +8,7 @@ import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import moe.kageru.kodeshare.Routes.createRoutes
import org.joda.time.DateTime
@KtorExperimentalLocationsAPI
@ExperimentalStdlibApi
@ -21,4 +22,4 @@ fun main() {
}.start(wait = true)
}
data class Paste(val content: String, val html: String?)
data class Paste(val content: String, val created: DateTime)

@ -5,29 +5,33 @@ 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.MultiPartData.Empty.readPart
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.http.content.readAllParts
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.receive
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.pages.PastePage
import moe.kageru.kodeshare.persistence.PasteDao
import org.joda.time.DateTime
@KtorExperimentalLocationsAPI
@ExperimentalStdlibApi
object Routes {
fun Routing.createRoutes() {
get("/") {
call.respond(Homepage.content)
call.respond(HttpStatusCode.OK, Homepage.content)
}
get("/style.css") {
call.respondText(Css.default, ContentType.Text.CSS)
@ -38,6 +42,9 @@ object Routes {
get<PasteRequest> { req ->
call.handleGet(req)
}
get<RawPasteRequest> { req ->
call.handleRaw(req)
}
}
@ExperimentalStdlibApi
@ -48,13 +55,13 @@ object Routes {
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.")
@ -65,10 +72,16 @@ object Routes {
private suspend fun ApplicationCall.respondToUpload(content: String): Long? {
content.ifBlank {
respondText("Empty pastes are not allowed", ContentType.Text.Any, HttpStatusCode.BadRequest)
Log.info("Rejecting blank paste")
respond(HttpStatusCode.BadRequest, "Empty pastes are not allowed")
return null
}
val id = PasteDao.insert(Paste(content, null)).id
if (content.length > 1 * 1024 * 1024) {
Log.info("Rejecting paste over 1MB")
respond(HttpStatusCode.BadRequest, "Pastes are limited to 1MB each")
return null
}
val id = PasteDao.insert(Paste(content, DateTime.now())).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.
@ -81,11 +94,26 @@ object Routes {
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.OK,
PastePage.build(paste.content)
)
} ?: respond(HttpStatusCode.NotFound, "nothing found for id $id")
}
private suspend fun ApplicationCall.handleRaw(req: RawPasteRequest) {
val id = req.id
Log.info("Retrieving raw paste $id")
PasteDao.select(id)?.data?.let { paste ->
respondText(paste.content, ContentType.Text.Plain)
} ?: respond(HttpStatusCode.NotFound, "nothing found for id $id")
}
}
@KtorExperimentalLocationsAPI
@Location("/r/{id}")
data class RawPasteRequest(val id: Long)
@KtorExperimentalLocationsAPI
@Location("/{id}")
data class PasteRequest(val id: Long)

@ -0,0 +1,15 @@
package moe.kageru.kodeshare.config
import com.uchuhimo.konf.Config
import com.uchuhimo.konf.ConfigSpec
val config = Config { addSpec(DatabaseSpec) }
.from.properties.file("kodeshare.properties")
.from.env()
object DatabaseSpec : ConfigSpec() {
val port by optional(3306)
val password by required<String>()
val user by optional("kodeshare")
val database by optional("kode")
}

@ -5,21 +5,22 @@ import kotlinx.css.properties.ms
import kotlinx.css.properties.transition
object Css {
private val accent1 = Color("#cd7400")
private val accent2 = Color("#ed7a00")
private val accent1 = Color("#e6db74")
private val accent2 = Color("#a6e22e")
private val fontcolor = Color.lightGrey
private val bgcolor = Color("#23241f")
val default = CSSBuilder().apply {
body {
fontFamily = "Hack, Fira Code, Noto Mono, monospace"
fontSize = 13.pt
textAlign = TextAlign.center
margin = "auto"
backgroundColor = Color.black
backgroundColor = bgcolor
color = fontcolor
}
textarea {
fontFamily = "Hack, Fira Code, Noto Mono, monospace"
backgroundColor = Color.black
backgroundColor = bgcolor
color = Color.white
fontSize = 13.pt
borderColor = accent1
@ -30,6 +31,14 @@ object Css {
minWidth = 70.pct
maxWidth = 100.pct
}
// this doesn’t inherit the style from anything else for some reason
rule(".hljs") {
width = 100.pct
height = 100.pct
textAlign = TextAlign.left
fontFamily = "Hack, Fira Code, Noto Mono, monospace"
fontSize = 13.pt
}
rule("input[type=\"submit\"]") {
backgroundColor = accent1
borderColor = accent1
@ -44,7 +53,8 @@ object Css {
}
rule("input[type=\"submit\"]:hover") {
backgroundColor = Color.transparent
color = accent1
borderColor = accent2
color = accent2
}
rule("textarea:focus") {
borderColor = accent2

@ -8,6 +8,9 @@ object Homepage {
val content = HtmlContent(HttpStatusCode.OK) {
head {
link(rel = "stylesheet", href = "/style.css", type = "text/css")
unsafe {
+"<style>*{transition-duration: 400ms;}</style>"
}
}
body {
h1 { +"kodeshare - yet another paste service" }

@ -0,0 +1,27 @@
package moe.kageru.kodeshare.pages
import io.ktor.html.HtmlContent
import io.ktor.http.HttpStatusCode
import kotlinx.html.*
object PastePage {
fun build(content: String) = HtmlContent(HttpStatusCode.OK) {
head {
link(rel = "stylesheet", href = "/style.css", type = "text/css")
link(
rel = "stylesheet",
href = "https://p.kageru.moe/static/hljs.css",
type = "text/css"
)
script(src = "https://p.kageru.moe/static/hl.js") {}
unsafe { +"<script>hljs.initHighlightingOnLoad();</script>" }
}
body {
pre {
code {
+content
}
}
}
}
}

@ -1,6 +1,8 @@
package moe.kageru.kodeshare.persistence
import moe.kageru.kodeshare.Paste
import moe.kageru.kodeshare.config.DatabaseSpec
import moe.kageru.kodeshare.config.config
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import org.mariadb.jdbc.MariaDbDataSource
@ -13,20 +15,20 @@ object PasteDao {
fun insert(paste: Paste): Transient<Paste> = transaction {
val id = PasteTable.insert {
it[content] = paste.content
it[html] = paste.html
it[created] = paste.created
}[PasteTable.id]
Transient(id, paste)
}
init {
val source = MariaDbDataSource().apply {
userName = "kodeshare"
setPassword("12345")
databaseName = "kode"
userName = config[DatabaseSpec.user]
setPassword(config[DatabaseSpec.password])
databaseName = config[DatabaseSpec.database]
port = config[DatabaseSpec.port]
}
Database.connect(source)
transaction {
SchemaUtils.drop(PasteTable)
SchemaUtils.create(PasteTable)
}
}
@ -36,14 +38,14 @@ private fun ResultRow.toPaste() = Transient(
get(PasteTable.id),
Paste(
content = get(PasteTable.content),
html = get(PasteTable.html)
created = get(PasteTable.created)
)
)
private object PasteTable : Table() {
val id = long("id").primaryKey().autoIncrement()
val id = long("id").primaryKey().autoIncrement().index()
val content = text("content")
val html = text("html").nullable()
val created = datetime("created").index()
}
/*