/* * 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 . */ package de.steamwar.routes import de.steamwar.ResponseError import de.steamwar.data.getCachedSkin import de.steamwar.plugins.SWAuthPrincipal import de.steamwar.plugins.SWPermissionCheck import de.steamwar.sql.SchematicType import de.steamwar.sql.SteamwarUser import de.steamwar.sql.Team import de.steamwar.sql.UserPerm import de.steamwar.sql.loadSchematicTypes import de.steamwar.util.fetchData import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.serialization.Serializable import org.bspfsystems.yamlconfiguration.file.YamlConfiguration import java.io.File import java.net.InetSocketAddress import java.util.UUID @Serializable data class ResponseSchematicType(val name: String, val db: String) @Serializable data class ResponseUser(val name: String, val uuid: String, val prefix: String, val perms: List) { constructor(user: SteamwarUser) : this(user.userName, user.uuid.toString(), user.prefix().chatPrefix, user.perms().filter { !it.name.startsWith("PREFIX_") }.map { it.name }) { synchronized(cache) { cache[user.id] = this } } companion object { private val cache = mutableMapOf() fun get(id: Int): ResponseUser { synchronized(cache) { return cache[id] ?: ResponseUser(SteamwarUser.get(id)).also { cache[id] = it } } } fun clearCache() { synchronized(cache) { cache.clear() } } } } fun Route.configureDataRoutes() { route("/data") { route("/admin") { install(SWPermissionCheck) { mustAuth = true permission = UserPerm.MODERATION } get("/users") { call.respond(SteamwarUser.getAll().map { ResponseUser(it) }) } get("/teams") { call.respond(Team.getAll().map { ResponseTeam(it) }) } get("/schematicTypes") { val types = mutableListOf() loadSchematicTypes(types, mutableMapOf()) call.respond(types.filter { !it.check() }.map { ResponseSchematicType(it.name(), it.toDB()) }) } get("/gamemodes") { call.respond( File("/configs/GameModes/").listFiles()!! .filter { it.name.endsWith(".yml") && !it.name.endsWith(".kits.yml") } .map { it.nameWithoutExtension }) } get("/gamemodes/{gamemode}/maps") { val gamemode = call.parameters["gamemode"] if (gamemode == null) { call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid gamemode")) return@get } val file = File("/configs/GameModes/$gamemode.yml") if (!file.exists()) { call.respond(HttpStatusCode.NotFound, ResponseError("Gamemode not found")) return@get } call.respond(YamlConfiguration.loadConfiguration(file).getStringList("Server.Maps")) } } get("/server") { try { val server = fetchData(InetSocketAddress("steamwar.de", 25565), 100) call.respond(server) } catch (e: Exception) { e.printStackTrace() call.respond(HttpStatusCode.InternalServerError, ResponseError(e.message ?: "Unknown error")) return@get } } get("/team") { call.respond( listOf(UserPerm.PREFIX_ADMIN, UserPerm.PREFIX_DEVELOPER, UserPerm.PREFIX_MODERATOR, UserPerm.PREFIX_SUPPORTER, UserPerm.PREFIX_BUILDER) .associateWith { SteamwarUser.getUsersWithPerm(it) } .mapKeys { UserPerm.prefixes[it.key]!!.chatPrefix } .mapValues { it.value.map { ResponseUser(it) } } ) } get("/skin/{uuid}") { val uuid = call.parameters["uuid"] if (uuid == null || catchException { UUID.fromString(uuid) } == null) { call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid UUID")) return@get } val skin = getCachedSkin(uuid) call.response.header("X-Cache", if (skin.second) "HIT" else "MISS") call.response.header("Cache-Control", "public, max-age=604800") call.respondFile(skin.first) } route("/me") { install(SWPermissionCheck) get { call.respond(ResponseUser(call.principal()!!.user)) } } } } inline fun catchException(yield: () -> T): T? = try { yield() } catch (e: Exception) { e.printStackTrace() null }