forked from SteamWar/SteamWar
Compare commits
1 Commits
24d8ec7301
...
api-v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
a8204a181b
|
@@ -198,7 +198,7 @@ class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
|
|||||||
|
|
||||||
var discordId by SteamwarUserTable.discordId
|
var discordId by SteamwarUserTable.discordId
|
||||||
|
|
||||||
private val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) }
|
val punishments by lazy { Punishment.getPunishmentsOfPlayer(id.value) }
|
||||||
private val perms by lazy { UserPerm.getPerms(id.value) }
|
private val perms by lazy { UserPerm.getPerms(id.value) }
|
||||||
private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix }
|
private val prefix by lazy { perms.firstOrNull { UserPerm.prefixes.containsKey(it) }?.let { UserPerm.prefixes[it]} ?: UserPerm.emptyPrefix }
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class Team(id: EntityID<Int>) : IntEntity(id) {
|
|||||||
var deleted by TeamTable.deleted
|
var deleted by TeamTable.deleted
|
||||||
private set
|
private set
|
||||||
val members by lazy { useDb { SteamwarUserTable.select(SteamwarUserTable.id).where { SteamwarUserTable.team eq teamId }.map { it[SteamwarUserTable.id].value } } }
|
val members by lazy { useDb { SteamwarUserTable.select(SteamwarUserTable.id).where { SteamwarUserTable.team eq teamId }.map { it[SteamwarUserTable.id].value } } }
|
||||||
|
val membersUser by lazy { useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.toList() } }
|
||||||
|
|
||||||
fun size() = useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.count().toInt() }
|
fun size() = useDb { SteamwarUser.find { SteamwarUserTable.team eq teamId }.count().toInt() }
|
||||||
fun disband(user: SteamwarUser) = useDb {
|
fun disband(user: SteamwarUser) = useDb {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class TeamTeilnahme(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getEvents(teamId: Int) = useDb {
|
fun getEvents(teamId: Int) = useDb {
|
||||||
find { TeamTeilnahmeTable.teamId eq teamId }.map { Event.byId(it.eventId.value) }.toSet()
|
find { TeamTeilnahmeTable.teamId eq teamId }.mapNotNull { Event.byId(it.eventId.value) }.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|||||||
@@ -47,61 +47,67 @@ data class UsernamePassword(val name: String, val password: String, val keepLogg
|
|||||||
|
|
||||||
fun Route.configureAuth() {
|
fun Route.configureAuth() {
|
||||||
route("/auth") {
|
route("/auth") {
|
||||||
val client = HttpClient(Java) {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
val request = call.receive<UsernamePassword>()
|
|
||||||
|
|
||||||
SteamwarUser.clear()
|
|
||||||
val user = SteamwarUser.get(request.name)
|
|
||||||
val valid = user?.verifyPassword(request.password) ?: false
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
|
|
||||||
call.sessions.set(SWUserSession(user.getId()))
|
|
||||||
call.respond(ResponseUser(user))
|
|
||||||
}
|
|
||||||
|
|
||||||
delete {
|
delete {
|
||||||
call.sessions.clear<SWUserSession>()
|
call.sessions.clear<SWUserSession>()
|
||||||
call.respond(HttpStatusCode.NoContent)
|
call.respond(HttpStatusCode.NoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
route("/discord") {
|
configureDiscordAuth()
|
||||||
post {
|
configurePasswordAuth()
|
||||||
val token = call.receiveText()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val res = client.get("https://discord.com/api/v10/oauth2/@me") {
|
fun Route.configurePasswordAuth() {
|
||||||
headers {
|
post {
|
||||||
append("Authorization", "Bearer $token")
|
val request = call.receive<UsernamePassword>()
|
||||||
}
|
|
||||||
}
|
|
||||||
val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject
|
|
||||||
val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content
|
|
||||||
|
|
||||||
if (discordId == null) {
|
SteamwarUser.clear()
|
||||||
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid"))
|
val user = SteamwarUser.get(request.name)
|
||||||
return@post
|
val valid = user?.verifyPassword(request.password) ?: false
|
||||||
}
|
|
||||||
|
|
||||||
SteamwarUser.clear()
|
if (!valid) {
|
||||||
val user = SteamwarUser.get(discordId.toLong())
|
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid username or password", "invalid"))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
if (user == null) {
|
call.sessions.set(SWUserSession(user.getId()))
|
||||||
call.respond(HttpStatusCode.Forbidden, ResponseError("Discord account not linked", "not_linked"))
|
call.respond(ResponseUser(user))
|
||||||
return@post
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
call.sessions.set(SWUserSession(user.getId()))
|
fun Route.configureDiscordAuth() {
|
||||||
call.respond(ResponseUser(user))
|
val client = HttpClient(Java) {
|
||||||
}
|
install(ContentNegotiation) {
|
||||||
|
json()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
post("/discord") {
|
||||||
|
val token = call.receiveText()
|
||||||
|
|
||||||
|
val res = client.get("https://discord.com/api/v10/oauth2/@me") {
|
||||||
|
headers {
|
||||||
|
append("Authorization", "Bearer $token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val resJson = Json.parseToJsonElement(res.bodyAsText()).jsonObject
|
||||||
|
val discordId = resJson["user"]?.jsonObject["id"]?.jsonPrimitive?.content
|
||||||
|
|
||||||
|
if (discordId == null) {
|
||||||
|
call.respond(HttpStatusCode.Forbidden, ResponseError("Invalid Discord token", "invalid"))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
SteamwarUser.clear()
|
||||||
|
val user = SteamwarUser.get(discordId.toLong())
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.Forbidden, ResponseError("Discord account not linked", "not_linked"))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
call.sessions.set(SWUserSession(user.getId()))
|
||||||
|
call.respond(ResponseUser(user))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ data class ResponseUser(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long)
|
data class ResponseUserList(val entries: List<ResponseUser>, val rows: Long)
|
||||||
|
|
||||||
private fun Query.addUserFilter(
|
fun Query.addUserFilter(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
uuid: UUID? = null,
|
uuid: UUID? = null,
|
||||||
team: Set<Int>? = null,
|
team: Set<Int>? = null,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import io.ktor.server.request.*
|
|||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.v1.core.ResultRow
|
||||||
import java.sql.Timestamp
|
import java.sql.Timestamp
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ data class ResponseEventFight(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class ResponseTeam(val id: Int, val name: String, val kuerzel: String, val color: String) {
|
data class ResponseTeam(val id: Int, val name: String, val kuerzel: String, val color: String) {
|
||||||
constructor(team: Team) : this(team.teamId, team.teamName, team.teamKuerzel, team.teamColor)
|
constructor(team: Team) : this(team.teamId, team.teamName, team.teamKuerzel, team.teamColor)
|
||||||
|
|
||||||
|
constructor(row: ResultRow) : this(row[TeamTable.id].value, row[TeamTable.name], row[TeamTable.kuerzel], row[TeamTable.color])
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -19,6 +19,12 @@
|
|||||||
|
|
||||||
package de.steamwar.routes
|
package de.steamwar.routes
|
||||||
|
|
||||||
|
import de.steamwar.routes.v2.configureAuthV2
|
||||||
|
import de.steamwar.routes.v2.configureGameModeRoutes
|
||||||
|
import de.steamwar.routes.v2.configureSchematicsV2Route
|
||||||
|
import de.steamwar.routes.v2.configureSteamWarRoute
|
||||||
|
import de.steamwar.routes.v2.configureTeamRoutes
|
||||||
|
import de.steamwar.routes.v2.configureUsersRouteV2
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.auth.*
|
import io.ktor.server.auth.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
@@ -34,6 +40,17 @@ fun Application.configureRoutes() {
|
|||||||
configureSchematic()
|
configureSchematic()
|
||||||
configureAuth()
|
configureAuth()
|
||||||
configureAuditLog()
|
configureAuditLog()
|
||||||
|
route("/v2") {
|
||||||
|
configureAuditLog()
|
||||||
|
configurePage()
|
||||||
|
configureEventsRoute()
|
||||||
|
configureAuthV2()
|
||||||
|
configureTeamRoutes()
|
||||||
|
configureSteamWarRoute()
|
||||||
|
configureUsersRouteV2()
|
||||||
|
configureSchematicsV2Route()
|
||||||
|
configureGameModeRoutes()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,8 +43,31 @@ import java.util.*
|
|||||||
import java.util.zip.GZIPInputStream
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ResponseSchematic(val name: String, val id: Int, val type: String?, val owner: Int, val item: String, val lastUpdate: Long, val rank: Int, val replaceColor: Boolean, val allowReplay: Boolean) {
|
data class ResponseSchematic(
|
||||||
constructor(node: SchematicNode) : this(node.name, node.getId(), node.schemtype?.name(), node.owner, node.item, node.lastUpdate.time, node.rank, node.replaceColor(), node.allowReplay())
|
val name: String,
|
||||||
|
val id: Int,
|
||||||
|
val type: String?,
|
||||||
|
val owner: Int,
|
||||||
|
val item: String,
|
||||||
|
val lastUpdate: Long,
|
||||||
|
val replaceColor: Boolean,
|
||||||
|
val allowReplay: Boolean,
|
||||||
|
val members: List<ResponseUser>
|
||||||
|
) {
|
||||||
|
constructor(node: SchematicNode) : this(
|
||||||
|
node.name,
|
||||||
|
node.getId(),
|
||||||
|
node.schemtype.name(),
|
||||||
|
node.owner,
|
||||||
|
node.item,
|
||||||
|
node.lastUpdate.time,
|
||||||
|
node.replaceColor(),
|
||||||
|
node.allowReplay(),
|
||||||
|
node.members.map {
|
||||||
|
ResponseUser(
|
||||||
|
SteamwarUser.byId(it.member)!!
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -72,9 +95,12 @@ fun Route.configureSchematic() {
|
|||||||
val node = call.receiveSchematic() ?: return@get
|
val node = call.receiveSchematic() ?: return@get
|
||||||
|
|
||||||
val user = call.principal<SWAuthPrincipal>()?.user
|
val user = call.principal<SWAuthPrincipal>()?.user
|
||||||
if(user != null && !node.accessibleByUser(user)) {
|
if (user != null && !node.accessibleByUser(user)) {
|
||||||
call.respond(HttpStatusCode.Forbidden)
|
call.respond(HttpStatusCode.Forbidden)
|
||||||
SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
|
SWException.log(
|
||||||
|
"User ${user.userName} tried to download schematic ${node.name} without permission",
|
||||||
|
user.id.toString()
|
||||||
|
)
|
||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,8 +109,15 @@ fun Route.configureSchematic() {
|
|||||||
return@get
|
return@get
|
||||||
}
|
}
|
||||||
|
|
||||||
call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"")
|
call.response.header(
|
||||||
call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
|
"Content-Disposition",
|
||||||
|
"attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\""
|
||||||
|
)
|
||||||
|
call.respondBytes(
|
||||||
|
data.schemData(false).readAllBytes(),
|
||||||
|
contentType = ContentType.Application.OctetStream,
|
||||||
|
status = HttpStatusCode.OK
|
||||||
|
)
|
||||||
}
|
}
|
||||||
get("/info") {
|
get("/info") {
|
||||||
val node = call.receiveSchematic() ?: return@get
|
val node = call.receiveSchematic() ?: return@get
|
||||||
@@ -100,18 +133,22 @@ fun Route.configureSchematic() {
|
|||||||
val schemName = file.name.substringBeforeLast(".")
|
val schemName = file.name.substringBeforeLast(".")
|
||||||
|
|
||||||
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
|
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
|
||||||
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
call.respond(
|
||||||
error = "INVALID_NAME"
|
HttpStatusCode.BadRequest, ResponseError(
|
||||||
))
|
error = "INVALID_NAME"
|
||||||
|
)
|
||||||
|
)
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
|
|
||||||
val schemType = file.name.substringAfterLast(".")
|
val schemType = file.name.substringAfterLast(".")
|
||||||
|
|
||||||
if (schemType != "schem" && schemType != "schematic") {
|
if (schemType != "schem" && schemType != "schematic") {
|
||||||
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
call.respond(
|
||||||
error = "INVALID_SUFFIX"
|
HttpStatusCode.BadRequest, ResponseError(
|
||||||
))
|
error = "INVALID_SUFFIX"
|
||||||
|
)
|
||||||
|
)
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,22 +183,27 @@ fun Route.configureSchematic() {
|
|||||||
.value
|
.value
|
||||||
|
|
||||||
if (fawe.equals("2.12.3-SNAPSHOT")) {
|
if (fawe.equals("2.12.3-SNAPSHOT")) {
|
||||||
SWException.log("Schematic with Bugged Version Uploaded", """
|
SWException.log(
|
||||||
|
"Schematic with Bugged Version Uploaded", """
|
||||||
Schematic=$schemName
|
Schematic=$schemName
|
||||||
User=${user.userName}
|
User=${user.userName}
|
||||||
Id=${user.id}
|
Id=${user.id}
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeData.saveFromStream(node, content.inputStream(), version)
|
NodeData.saveFromStream(node, content.inputStream(), version)
|
||||||
|
|
||||||
call.respond(ResponseSchematic(node))
|
call.respond(ResponseSchematic(node))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
call.respond(
|
||||||
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
|
HttpStatusCode.BadRequest, ResponseError(
|
||||||
))
|
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +220,7 @@ suspend fun ApplicationCall.receiveSchematic(fieldName: String = "code", delete:
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) {
|
if (dl.timestamp.toInstant().plus(Duration.of(5, ChronoUnit.MINUTES)).isBefore(Instant.now())) {
|
||||||
respond(HttpStatusCode.Gone)
|
respond(HttpStatusCode.Gone)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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.v2
|
||||||
|
|
||||||
|
import de.steamwar.plugins.SWUserSession
|
||||||
|
import de.steamwar.routes.configureDiscordAuth
|
||||||
|
import de.steamwar.routes.configurePasswordAuth
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.delete
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import io.ktor.server.sessions.clear
|
||||||
|
import io.ktor.server.sessions.sessions
|
||||||
|
|
||||||
|
fun Route.configureAuthV2() {
|
||||||
|
route("/auth") {
|
||||||
|
delete {
|
||||||
|
call.sessions.clear<SWUserSession>()
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
configureDiscordAuth()
|
||||||
|
route("/legacy") {
|
||||||
|
configurePasswordAuth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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.v2
|
||||||
|
|
||||||
|
import de.steamwar.ResponseError
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import org.bspfsystems.yamlconfiguration.file.YamlConfiguration
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.io.nameWithoutExtension
|
||||||
|
|
||||||
|
fun Route.configureGameModeRoutes() {
|
||||||
|
route("/gamemodes") {
|
||||||
|
get {
|
||||||
|
call.respond(
|
||||||
|
File("/configs/GameModes/").listFiles()!!
|
||||||
|
.filter { it.name.endsWith(".yml") && !it.name.endsWith(".kits.yml") }
|
||||||
|
.map { it.nameWithoutExtension })
|
||||||
|
}
|
||||||
|
get("/{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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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.v2
|
||||||
|
|
||||||
|
import com.sun.tools.jdeprscan.Main.call
|
||||||
|
import de.steamwar.ResponseError
|
||||||
|
import de.steamwar.plugins.SWAuthPrincipal
|
||||||
|
import de.steamwar.plugins.SWPermissionCheck
|
||||||
|
import de.steamwar.routes.ResponseSchematic
|
||||||
|
import de.steamwar.routes.ResponseSchematicType
|
||||||
|
import de.steamwar.routes.UploadSchematic
|
||||||
|
import de.steamwar.routes.nbt
|
||||||
|
import de.steamwar.routes.receiveSchematic
|
||||||
|
import de.steamwar.sql.NodeData
|
||||||
|
import de.steamwar.sql.NodeData.SchematicFormat
|
||||||
|
import de.steamwar.sql.NodeDownload
|
||||||
|
import de.steamwar.sql.SWException
|
||||||
|
import de.steamwar.sql.SchematicNode
|
||||||
|
import de.steamwar.sql.SchematicType
|
||||||
|
import dev.dewy.nbt.tags.collection.CompoundTag
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.ApplicationCall
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.authentication
|
||||||
|
import io.ktor.server.auth.principal
|
||||||
|
import io.ktor.server.request.receive
|
||||||
|
import io.ktor.server.response.header
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondBytes
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.post
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.util.Base64
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
|
data class ListedSchematicNode(val name: String, val id: Int, val type: String)
|
||||||
|
|
||||||
|
fun Route.configureSchematicsV2Route() {
|
||||||
|
route("/schematics") {
|
||||||
|
get("/types") {
|
||||||
|
call.respond(SchematicType.values().filter { !it.check() }
|
||||||
|
.map { ResponseSchematicType(it.name(), it.toDB()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/list/{path...}") {
|
||||||
|
val path = call.parameters.getAll("path")?.joinToString("/") ?: "/"
|
||||||
|
|
||||||
|
val user = call.authentication.principal<SWAuthPrincipal>()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.Unauthorized)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val node = SchematicNode.getNodeFromPath(user.user, path)
|
||||||
|
call.respond(SchematicNode.list(user.user, node?.id?.value).map { ListedSchematicNode(it.name, it.id.value, it.schemtype.toDB()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/download/{code}") {
|
||||||
|
val node = call.receiveSchematic() ?: return@get
|
||||||
|
|
||||||
|
val user = call.principal<SWAuthPrincipal>()?.user
|
||||||
|
if(user != null && !node.accessibleByUser(user)) {
|
||||||
|
call.respond(HttpStatusCode.Forbidden)
|
||||||
|
SWException.log("User ${user.userName} tried to download schematic ${node.name} without permission", user.id.toString())
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = NodeData.getLatest(node) ?: run {
|
||||||
|
call.respond(HttpStatusCode.InternalServerError)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.response.header("Content-Disposition", "attachment; filename=\"${node.name}${data.nodeFormat.fileEnding}\"")
|
||||||
|
call.respondBytes(data.schemData(false).readAllBytes(), contentType = ContentType.Application.OctetStream, status = HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/upload") {
|
||||||
|
val user = call.principal<SWAuthPrincipal>()?.user
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.Unauthorized)
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = call.receive<UploadSchematic>()
|
||||||
|
val schemName = file.name.substringBeforeLast(".")
|
||||||
|
|
||||||
|
if (SchematicNode.invalidSchemName(arrayOf(schemName))) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
||||||
|
error = "INVALID_NAME"
|
||||||
|
))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
val schemType = file.name.substringAfterLast(".")
|
||||||
|
|
||||||
|
if (schemType != "schem" && schemType != "schematic") {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
||||||
|
error = "INVALID_SUFFIX"
|
||||||
|
))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = SchematicNode.getSchematicNode(user.getId(), schemName, null as Int?)
|
||||||
|
if (node == null) {
|
||||||
|
node = SchematicNode.createSchematic(user.getId(), schemName, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val content = Base64.getDecoder().decode(file.content)
|
||||||
|
|
||||||
|
var schem = nbt.fromStream(DataInputStream(BufferedInputStream(GZIPInputStream(content.inputStream()))))
|
||||||
|
|
||||||
|
if (schem.size() == 1) schem = schem.first() as CompoundTag
|
||||||
|
|
||||||
|
val version = schem.let {
|
||||||
|
if (it.contains("Materials"))
|
||||||
|
return@let SchematicFormat.MCEDIT
|
||||||
|
else if (it.contains("Blocks"))
|
||||||
|
return@let SchematicFormat.SPONGE_V3
|
||||||
|
else
|
||||||
|
return@let SchematicFormat.SPONGE_V2
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == SchematicFormat.SPONGE_V3) {
|
||||||
|
try {
|
||||||
|
val fawe = schem.getCompound("Metadata")
|
||||||
|
.getCompound("WorldEdit")
|
||||||
|
.getString("Version")
|
||||||
|
.value
|
||||||
|
|
||||||
|
if (fawe.equals("2.12.3-SNAPSHOT")) {
|
||||||
|
SWException.log("Schematic with Bugged Version Uploaded", """
|
||||||
|
Schematic=$schemName
|
||||||
|
User=${user.userName}
|
||||||
|
Id=${user.id}
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeData.saveFromStream(node, content.inputStream(), version)
|
||||||
|
|
||||||
|
call.respond(ResponseSchematic(node))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError(
|
||||||
|
error = e.message ?: "GENERIC", code = "UPLOAD_ERROR"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
route("/{id}") {
|
||||||
|
install(SWPermissionCheck) {
|
||||||
|
mustAuth = true
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
val node = call.receiveSchem() ?: return@get
|
||||||
|
call.respond(ResponseSchematic(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/download") {
|
||||||
|
val node = call.receiveSchem() ?: return@post
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK, NodeDownload.getLink(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun ApplicationCall.receiveSchem(): SchematicNode? {
|
||||||
|
val schemId = parameters["id"]?.toIntOrNull()
|
||||||
|
if (schemId == null) {
|
||||||
|
respond(HttpStatusCode.BadRequest)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val schem = SchematicNode.getSchematicNode(schemId)
|
||||||
|
if (schem == null) {
|
||||||
|
respond(HttpStatusCode.NotFound)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(principal<SWAuthPrincipal>()?.user?.let { schem.accessibleByUser(it) } ?: false)) {
|
||||||
|
respond(HttpStatusCode.Forbidden)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return schem
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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.v2
|
||||||
|
|
||||||
|
import de.steamwar.ResponseError
|
||||||
|
import de.steamwar.routes.ResponseUser
|
||||||
|
import de.steamwar.sql.SteamwarUser
|
||||||
|
import de.steamwar.sql.UserPerm
|
||||||
|
import de.steamwar.util.fetchData
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
|
fun Route.configureSteamWarRoute() {
|
||||||
|
route("/steamwar") {
|
||||||
|
get {
|
||||||
|
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) } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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.v2
|
||||||
|
|
||||||
|
import de.steamwar.routes.ResponseEvent
|
||||||
|
import de.steamwar.routes.ResponseTeam
|
||||||
|
import de.steamwar.routes.ResponseUser
|
||||||
|
import de.steamwar.sql.SteamwarUser
|
||||||
|
import de.steamwar.sql.Team
|
||||||
|
import de.steamwar.sql.TeamTable
|
||||||
|
import de.steamwar.sql.TeamTeilnahme
|
||||||
|
import de.steamwar.sql.internal.useDb
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.v1.core.like
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.Query
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.andWhere
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
|
|
||||||
|
fun Query.addTeamFilter(teamName: String?, teamKuerzel: String?): Query {
|
||||||
|
teamName?.let { andWhere { TeamTable.name like "%$it%" } }
|
||||||
|
teamKuerzel?.let { andWhere { TeamTable.kuerzel like "%$it%" } }
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.configureTeamRoutes() {
|
||||||
|
route("/teams") {
|
||||||
|
get {
|
||||||
|
val teamName = call.request.queryParameters["name"]
|
||||||
|
val teamKuerzel = call.request.queryParameters["kuerzel"]
|
||||||
|
|
||||||
|
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
|
||||||
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
|
||||||
|
|
||||||
|
call.respond(useDb {
|
||||||
|
TeamTable.selectAll().addTeamFilter(teamName, teamKuerzel).limit(limit).offset((page * limit).toLong())
|
||||||
|
.map { ResponseTeam(it) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/{team}") {
|
||||||
|
@Serializable
|
||||||
|
data class TeamResponse(
|
||||||
|
val team: ResponseTeam,
|
||||||
|
val members: List<ResponseUser>,
|
||||||
|
val leaders: List<ResponseUser>,
|
||||||
|
val events: List<ResponseEvent>
|
||||||
|
)
|
||||||
|
|
||||||
|
val team = call.parameters["team"]?.toIntOrNull()?.let { Team.byId(it) }
|
||||||
|
if (team == null) {
|
||||||
|
call.respond(HttpStatusCode.NotFound)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(useDb {
|
||||||
|
TeamResponse(
|
||||||
|
ResponseTeam(team),
|
||||||
|
team.membersUser.map { ResponseUser(it) },
|
||||||
|
team.membersUser.filter { it.leader }.map { ResponseUser(it) },
|
||||||
|
TeamTeilnahme.getEvents(team.teamId).map { ResponseEvent(it) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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.v2
|
||||||
|
|
||||||
|
import de.steamwar.ResponseError
|
||||||
|
import de.steamwar.data.getCachedSkin
|
||||||
|
import de.steamwar.plugins.SWAuthPrincipal
|
||||||
|
import de.steamwar.plugins.SWPermissionCheck
|
||||||
|
import de.steamwar.routes.ResponseTeam
|
||||||
|
import de.steamwar.routes.ResponseUser
|
||||||
|
import de.steamwar.routes.ResponseUserList
|
||||||
|
import de.steamwar.routes.UserStats
|
||||||
|
import de.steamwar.routes.addUserFilter
|
||||||
|
import de.steamwar.routes.catchException
|
||||||
|
import de.steamwar.sql.Punishment
|
||||||
|
import de.steamwar.sql.SteamwarUser
|
||||||
|
import de.steamwar.sql.SteamwarUserTable
|
||||||
|
import de.steamwar.sql.Team
|
||||||
|
import de.steamwar.sql.UserPerm
|
||||||
|
import de.steamwar.sql.internal.useDb
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.application.ApplicationCall
|
||||||
|
import io.ktor.server.application.call
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.principal
|
||||||
|
import io.ktor.server.request.receive
|
||||||
|
import io.ktor.server.response.header
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondFile
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.delete
|
||||||
|
import io.ktor.server.routing.get
|
||||||
|
import io.ktor.server.routing.post
|
||||||
|
import io.ktor.server.routing.put
|
||||||
|
import io.ktor.server.routing.route
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
|
import java.sql.Timestamp
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ResponsePunishment(
|
||||||
|
val id: Int,
|
||||||
|
val type: Punishment.PunishmentType,
|
||||||
|
val reason: String,
|
||||||
|
val issuer: ResponseUser,
|
||||||
|
val startTime: Long,
|
||||||
|
val endTime: Long,
|
||||||
|
val perma: Boolean,
|
||||||
|
val active: Boolean
|
||||||
|
) {
|
||||||
|
constructor(punishment: Punishment) : this(
|
||||||
|
punishment.id.value,
|
||||||
|
punishment.type,
|
||||||
|
punishment.reason,
|
||||||
|
ResponseUser(SteamwarUser.byId(punishment.punisher)!!),
|
||||||
|
punishment.startTime.toInstant().toEpochMilli(),
|
||||||
|
punishment.endTime.toInstant().toEpochMilli(),
|
||||||
|
punishment.perma,
|
||||||
|
punishment.isCurrent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CreatePunishment(
|
||||||
|
val type: Punishment.PunishmentType,
|
||||||
|
val reason: String,
|
||||||
|
val perma: Boolean,
|
||||||
|
val endTime: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Route.configureUsersRouteV2() {
|
||||||
|
get("/users/{user}/skin") {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val skin = getCachedSkin(user.uuid.toString())
|
||||||
|
call.response.header("X-Cache", if (skin.second) "HIT" else "MISS")
|
||||||
|
call.response.header("Cache-Control", "public, max-age=604800")
|
||||||
|
call.respondFile(skin.first)
|
||||||
|
}
|
||||||
|
get("/users/{user}/stats") {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val authUser = call.principal<SWAuthPrincipal>()
|
||||||
|
|
||||||
|
if (authUser == null || !(authUser.user.id == user.id || authUser.user.hasPerm(UserPerm.MODERATION))) {
|
||||||
|
call.respond(HttpStatusCode.Forbidden)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(UserStats(user))
|
||||||
|
}
|
||||||
|
route("/users") {
|
||||||
|
install(SWPermissionCheck) {
|
||||||
|
permission = UserPerm.MODERATION
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
val name = call.request.queryParameters["name"]
|
||||||
|
val uuid = call.request.queryParameters["uuid"]?.let { catchException { UUID.fromString(it) } }
|
||||||
|
val team = call.request.queryParameters.getAll("team")?.map { it.toInt() }?.toSet()
|
||||||
|
|
||||||
|
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 100
|
||||||
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
|
||||||
|
|
||||||
|
val includePerms = call.request.queryParameters["includePerms"]?.toBoolean() ?: false
|
||||||
|
val includeId = call.request.queryParameters["includeId"]?.toBoolean() ?: false
|
||||||
|
|
||||||
|
call.respond(
|
||||||
|
useDb {
|
||||||
|
ResponseUserList(
|
||||||
|
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).limit(limit)
|
||||||
|
.offset((page * limit).toLong())
|
||||||
|
.map { ResponseUser(it, includeId, includePerms) },
|
||||||
|
SteamwarUserTable.selectAll().addUserFilter(name, uuid, team).count()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
route("/{user}") {
|
||||||
|
get {
|
||||||
|
@Serializable
|
||||||
|
data class UserResponse(val user: ResponseUser, val team: ResponseTeam)
|
||||||
|
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.NotFound)
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(
|
||||||
|
useDb {
|
||||||
|
UserResponse(ResponseUser(user), ResponseTeam(Team.byId(user.team)))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
put("/prefix/{prefix}") {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@put
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefix = call.parameters["prefix"]?.let { name -> UserPerm.entries.find { it.name == name } }
|
||||||
|
if (prefix == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid prefix"))
|
||||||
|
return@put
|
||||||
|
}
|
||||||
|
|
||||||
|
user.perms().filter { it.name.startsWith("PREFIX_") }.forEach {
|
||||||
|
UserPerm.removePerm(user, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix != UserPerm.PREFIX_NONE) {
|
||||||
|
UserPerm.addPerm(user, prefix)
|
||||||
|
}
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
|
||||||
|
route("/perms") {
|
||||||
|
get {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
call.respond(user.perms())
|
||||||
|
}
|
||||||
|
route("/{perm}") {
|
||||||
|
put {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@put
|
||||||
|
}
|
||||||
|
|
||||||
|
val perm = call.parameters["perm"]?.let { name -> UserPerm.entries.find { it.name == name } }
|
||||||
|
if (perm == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid perm"))
|
||||||
|
return@put
|
||||||
|
}
|
||||||
|
|
||||||
|
UserPerm.addPerm(user, perm)
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
delete {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@delete
|
||||||
|
}
|
||||||
|
|
||||||
|
val perm = call.parameters["perm"]?.let { name -> UserPerm.entries.find { it.name == name } }
|
||||||
|
if (perm == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid perm"))
|
||||||
|
return@delete
|
||||||
|
}
|
||||||
|
|
||||||
|
UserPerm.removePerm(user, perm)
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
route("/punishments") {
|
||||||
|
get {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@get
|
||||||
|
}
|
||||||
|
|
||||||
|
val punishments = user.punishments.toList()
|
||||||
|
|
||||||
|
call.respond(punishments.map { ResponsePunishment(it.second) })
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
val user = call.receiveUser()
|
||||||
|
if (user == null) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, ResponseError("Invalid user"))
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
val punishment = call.receive<CreatePunishment>()
|
||||||
|
val punisher = call.principal<SWAuthPrincipal>()?.user ?: return@post
|
||||||
|
|
||||||
|
user.punish(punishment.type, Timestamp.from(Instant.ofEpochMilli(punishment.endTime)), punishment.reason, punisher.getId(), punishment.perma)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ApplicationCall.receiveUser(): SteamwarUser? {
|
||||||
|
val userString = parameters["user"] ?: return null
|
||||||
|
|
||||||
|
if (userString == "me") {
|
||||||
|
return principal<SWAuthPrincipal>()?.user
|
||||||
|
}
|
||||||
|
|
||||||
|
userString.toIntOrNull()?.let { return SteamwarUser.byId(it) }
|
||||||
|
userString.let { catchException { UUID.fromString(it) } }?.let { return SteamwarUser.get(it) }
|
||||||
|
return null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user