forked from SteamWar/SteamWar
291 lines
12 KiB
Kotlin
291 lines
12 KiB
Kotlin
/*
|
|
* This file is a part of the SteamWar software.
|
|
*
|
|
* Copyright (C) 2025 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.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 kotlinx.serialization.Serializable
|
|
import kotlinx.serialization.json.*
|
|
import java.time.Instant
|
|
import java.time.LocalDate
|
|
import java.time.format.DateTimeFormatter
|
|
import java.util.Base64
|
|
import java.util.Date
|
|
|
|
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 AddImageRequest(val name: String, val data: 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)
|
|
|
|
@Serializable
|
|
data class CreateGiteaPageRequest(val message: String, val content: String, val branch: String, val author: Identity)
|
|
|
|
fun Route.configurePage() {
|
|
val client = HttpClient(Java) {
|
|
install(ContentNegotiation) {
|
|
json()
|
|
}
|
|
defaultRequest {
|
|
url("https://git.steamwar.de/api/v1/")
|
|
header("Authorization", "token " + config.giteaToken)
|
|
}
|
|
}
|
|
|
|
suspend fun filesInDirectory(path: String, branch: String = "master", fileFilter: (name: String) -> Boolean = { true }): List<PageResponseList> {
|
|
val filesToCheck = mutableListOf(path)
|
|
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" && fileFilter(obj["name"]!!.jsonPrimitive.content)) {
|
|
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++ }))
|
|
}
|
|
}
|
|
|
|
return files
|
|
}
|
|
|
|
route("page") {
|
|
install(SWPermissionCheck) {
|
|
permission = UserPerm.MODERATION
|
|
}
|
|
get {
|
|
val branch = call.request.queryParameters["branch"] ?: "master"
|
|
|
|
call.respond(filesInDirectory("/src/content", branch) {
|
|
it.endsWith(".md") || it.endsWith(".json")
|
|
})
|
|
}
|
|
post {
|
|
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((
|
|
if (req.path.endsWith(".md")) """
|
|
---
|
|
title: ${req.title?.removeSuffix(".md") ?: "Enter Title"}
|
|
description: Enter Description
|
|
key: ${req.slug?.lowercase()?.removeSuffix(".md") ?: "Enter Slug"}
|
|
created: ${LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)}
|
|
tags:
|
|
- test
|
|
---
|
|
|
|
# ${req.path}
|
|
""" else "{}"
|
|
).trimIndent().toByteArray()),
|
|
call.request.queryParameters["branch"] ?: "master",
|
|
Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de"
|
|
)))
|
|
}
|
|
call.respond(res.status)
|
|
}
|
|
route("branch") {
|
|
get {
|
|
val res = client.get("repos/SteamWar/Website/branches")
|
|
call.respond(res.status, Json.parseToJsonElement(res.bodyAsText()).jsonArray.map { it.jsonObject["name"]?.jsonPrimitive?.content!! })
|
|
}
|
|
post {
|
|
@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 {
|
|
val branch = call.receive<CreateBranchRequest>().branch
|
|
val res = client.delete("repos/SteamWar/Website/branches/$branch")
|
|
call.respond(res.status)
|
|
}
|
|
}
|
|
route("{id}") {
|
|
get {
|
|
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 {
|
|
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 {
|
|
@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)
|
|
}
|
|
}
|
|
route("images") {
|
|
get {
|
|
val branch = call.request.queryParameters["branch"] ?: "master"
|
|
|
|
call.respond(filesInDirectory("/src/images", branch))
|
|
}
|
|
post {
|
|
val req = call.receive<AddImageRequest>()
|
|
|
|
client.post("repos/SteamWar/Website/contents/src/images/${req.name}") {
|
|
contentType(ContentType.Application.Json)
|
|
setBody(CreateGiteaPageRequest(
|
|
"Add Image ${req.name}",
|
|
req.data,
|
|
call.request.queryParameters["branch"] ?: "master",
|
|
Identity(call.principal<SWAuthPrincipal>()!!.user.userName, "admin-tool@steamwar.de"
|
|
)))
|
|
}
|
|
|
|
call.respond(HttpStatusCode.Created)
|
|
}
|
|
}
|
|
}
|
|
} |