Files
SteamWar/WebsiteBackend/src/de/steamwar/routes/Page.kt
T
2024-08-18 11:15:54 +02:00

251 lines
9.9 KiB
Kotlin

/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.routes
import de.steamwar.config
import de.steamwar.plugins.SWAuthPrincipal
import de.steamwar.plugins.SWPermissionCheck
import de.steamwar.sql.UserPerm
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.java.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.reflect.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.*
import java.util.Base64
val pathPageIdMap = mutableMapOf<String, Int>()
var pageId = 1
@Serializable
data class Identity(val name: String, val email: String)
@Serializable
data class PageResponseList(
val path: String,
val name: String,
val sha: String,
val downloadUrl: String,
val id: Int
) {
constructor(res: JsonObject, id: Int) : this(
res["path"]?.jsonPrimitive?.content!!,
res["name"]?.jsonPrimitive?.content!!,
res["sha"]?.jsonPrimitive?.content!!,
res["download_url"]?.jsonPrimitive?.content!!,
id
)
}
@Serializable
data class PageResponse(
val path: String,
val name: String,
val sha: String,
val downloadUrl: String,
val content: String,
val size: Int,
val id: Int,
) {
constructor(res: JsonObject, id: Int) : this(
res["path"]?.jsonPrimitive?.content!!,
res["name"]?.jsonPrimitive?.content!!,
res["sha"]?.jsonPrimitive?.content!!,
res["download_url"]?.jsonPrimitive?.content!!,
res["content"]?.jsonPrimitive?.content!!,
res["size"]?.jsonPrimitive?.int!!,
id
)
}
@Serializable
data class CreatePageRequest(val path: String, val slug: String?, val title: String?)
@Serializable
data class CreateBranchRequest(val branch: String)
@Serializable
data class UpdatePageRequest(val content: String, val sha: String, val message: String)
@Serializable
data class MergeBranchRequest(val branch: String, val message: String)
@Serializable
data class DeletePageRequest(val sha: String, val message: String)
fun Route.configurePage() {
val client = HttpClient(Java) {
install(ContentNegotiation) {
json()
}
defaultRequest {
url("https://steamwar.de/devlabs/api/v1/")
header("Authorization", "token " + config.giteaToken)
}
}
route("page") {
install(SWPermissionCheck) {
permission = UserPerm.MODERATION
}
get {
val branch = call.request.queryParameters["branch"] ?: "master"
val filesToCheck = mutableListOf("src/content")
val files = mutableListOf<PageResponseList>()
while (filesToCheck.isNotEmpty()) {
val path = filesToCheck.removeAt(0)
val res = client.get("repos/SteamWar/Website/contents/$path?ref=$branch")
val fileJson = Json.parseToJsonElement(res.bodyAsText())
if (fileJson is JsonArray) {
fileJson.forEach {
val obj = it.jsonObject
if (obj["type"]?.jsonPrimitive?.content == "dir") {
filesToCheck.add(obj["path"]?.jsonPrimitive?.content!!)
} else if (obj["type"]?.jsonPrimitive?.content == "file" && (obj["name"]?.jsonPrimitive?.content?.endsWith(".md") == true || obj["name"]?.jsonPrimitive?.content?.endsWith(".json") == true)) {
files.add(PageResponseList(obj, pathPageIdMap.computeIfAbsent(obj["path"]?.jsonPrimitive?.content!!) { pageId++ }))
}
}
} else {
files.add(PageResponseList(fileJson.jsonObject, pathPageIdMap.computeIfAbsent(fileJson.jsonObject["path"]?.jsonPrimitive?.content!!) { pageId++ }))
}
}
call.respond(files)
}
get("branch") {
val res = client.get("repos/SteamWar/Website/branches")
call.respond(res.status, Json.parseToJsonElement(res.bodyAsText()).jsonArray.map { it.jsonObject["name"]?.jsonPrimitive?.content!! })
}
post("branch") {
@Serializable
data class CreateGiteaBranchRequest(val new_branch_name: String, val old_branch_name: String)
val branch = call.receive<CreateBranchRequest>().branch
val res = client.post("repos/SteamWar/Website/branches") {
contentType(ContentType.Application.Json)
setBody(CreateGiteaBranchRequest(branch, "master"))
}
@Serializable
data class CreateGiteaMergeRequest(val base: String, val head: String, val title: String)
client.post("repos/SteamWar/Website/pulls") {
contentType(ContentType.Application.Json)
setBody(CreateGiteaMergeRequest("master", branch, "Merge branch $branch"))
}
call.respond(res.status)
}
delete("branch") {
val branch = call.receive<CreateBranchRequest>().branch
val res = client.delete("repos/SteamWar/Website/branches/$branch")
call.respond(res.status)
}
post {
@Serializable
data class CreateGiteaPageRequest(val message: String, val content: String, val branch: String, val author: Identity)
val req = call.receive<CreatePageRequest>()
if(req.path.startsWith("src/content/")) {
call.respond(HttpStatusCode.BadRequest, "Invalid path")
return@post
}
val res = client.post("repos/SteamWar/Website/contents/src/content/${req.path}") {
contentType(ContentType.Application.Json)
setBody(CreateGiteaPageRequest(
"Create page ${req.path}",
Base64.getEncoder().encodeToString("""
---
title: ${req.title ?: "[Enter Title]"}
description: [Enter Description]
slug: ${req.slug ?: "[Enter Slug]"}
---
# ${req.path}
""".trimIndent().toByteArray()),
call.request.queryParameters["branch"] ?: "master",
Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de"
)))
}
call.respond(res.status)
}
get("{id}") {
val id = call.parameters["id"]?.toIntOrNull() ?: return@get call.respond(HttpStatusCode.BadRequest, "Invalid id")
val path = pathPageIdMap.entries.find { it.value == id }?.key ?: return@get call.respond(HttpStatusCode.NotFound, "Page not found")
val branch = call.request.queryParameters["branch"] ?: "master"
val res = client.get("repos/SteamWar/Website/contents/$path?ref=$branch")
val fileJson = Json.parseToJsonElement(res.bodyAsText())
if (fileJson is JsonArray) {
return@get call.respond(HttpStatusCode.BadRequest, "Invalid id")
}
val file = PageResponse(fileJson.jsonObject, id)
call.respond(file)
}
delete("{id}") {
val data = call.receive<DeletePageRequest>()
val path = pathPageIdMap.entries.find { it.value == call.parameters["id"]?.toIntOrNull() }?.key ?: return@delete call.respond(HttpStatusCode.NotFound, "Page not found")
val branch = call.request.queryParameters["branch"] ?: "master"
@Serializable
data class DeleteGiteaPageRequest(val sha: String, val message: String, val branch: String, val author: Identity)
val res = client.delete("repos/SteamWar/Website/contents/$path") {
contentType(ContentType.Application.Json)
setBody(DeleteGiteaPageRequest(data.sha, data.message, branch, Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de")))
}
call.respond(res.status)
}
put("{id}") {
@Serializable
data class UpdateGiteaPageRequest(val content: String, val sha: String, val message: String, val branch: String, val author: Identity)
val data = call.receive<UpdatePageRequest>()
val path = pathPageIdMap.entries.find { it.value == call.parameters["id"]?.toIntOrNull() }?.key ?: return@put call.respond(HttpStatusCode.NotFound, "Page not found")
val res = client.put("repos/SteamWar/Website/contents/$path") {
contentType(ContentType.Application.Json)
setBody(UpdateGiteaPageRequest(data.content, data.sha, data.message, (call.request.queryParameters["branch"] ?: "master"), Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de")))
}
call.respond(res.status)
}
}
}