Files
SteamWar/WebsiteBackend/src/de/steamwar/routes/AuditLog.kt
T
2026-05-16 23:08:09 +02:00

207 lines
7.9 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.plugins.SWPermissionCheck
import de.steamwar.sql.AuditLog
import de.steamwar.sql.AuditLogTable
import de.steamwar.sql.SteamwarUserTable
import de.steamwar.sql.UserPerm
import de.steamwar.sql.internal.useDb
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.core.*
import org.jetbrains.exposed.v1.jdbc.Query
import org.jetbrains.exposed.v1.jdbc.andWhere
import org.jetbrains.exposed.v1.jdbc.select
import org.jetbrains.exposed.v1.jdbc.selectAll
import java.time.Instant
fun Route.configureAuditLog() {
route("/auditlog") {
install(SWPermissionCheck) {
mustAuth = true
permission = UserPerm.MODERATION
}
get {
val text = call.request.queryParameters["fullText"]
val actionText = call.request.queryParameters["actionText"]
val serverText = call.request.queryParameters["server"]
val actor = call.request.queryParameters.getAll("actor")?.map { it.toInt() }?.toSet()
val actionType =
call.request.queryParameters.getAll("actionType")?.map { AuditLog.Type.valueOf(it) }?.toSet()
val timeGreater = call.request.queryParameters["timeGreater"]?.let { Instant.ofEpochMilli(it.toLong()) }
val timeLess = call.request.queryParameters["timeLess"]?.let { Instant.ofEpochMilli(it.toLong()) }
val serverOwner = call.request.queryParameters.getAll("serverOwner")?.map { it.toInt() }?.toSet()
val velocity = call.request.queryParameters["velocity"]?.toBoolean()
val page = call.request.queryParameters["page"]?.toIntOrNull()
?: 0
val limit = call.request.queryParameters["limit"]?.toIntOrNull()
?: 100
val sorting = call.request.queryParameters["sorting"]
?: "DESC"
call.respond(
PagedAuditLog.filter(
actionText,
serverText,
text,
actor,
actionType,
timeGreater,
timeLess,
serverOwner,
velocity,
page,
limit,
sorting
)
)
}
}
}
@Serializable
data class PagedAuditLog(val rows: Long, val entries: List<AuditLogEntry>) {
companion object {
fun filter(
actionText: String? = null,
serverText: String? = null,
fullText: String? = null,
actor: Set<Int>? = null,
actionType: Set<AuditLog.Type>? = null,
timeGreater: Instant? = null,
timeLess: Instant? = null,
serverOwner: Set<Int>? = null,
velocity: Boolean? = null,
page: Int = 0,
limit: Int = 100,
sorting: String = "DESC"
) =
useDb {
val actorTable = SteamwarUserTable.alias("actor")
val serverOwnerTable = SteamwarUserTable.alias("serverOwner")
val query = AuditLogTable.join(
actorTable,
JoinType.INNER,
onColumn = actorTable[SteamwarUserTable.id],
otherColumn = AuditLogTable.actor
)
.join(
serverOwnerTable,
JoinType.LEFT,
onColumn = serverOwnerTable[SteamwarUserTable.id],
otherColumn = AuditLogTable.serverOwner
)
.select(
actorTable[SteamwarUserTable.username], serverOwnerTable[SteamwarUserTable.username],
*AuditLogTable.columns.toTypedArray()
)
PagedAuditLog(
AuditLogTable.selectAll().addAuditLogFilters(
actionText,
serverText,
fullText,
actor,
actionType,
timeGreater,
timeLess,
serverOwner,
velocity
).count(),
query
.addAuditLogFilters(
actionText,
serverText,
fullText,
actor,
actionType,
timeGreater,
timeLess,
serverOwner,
velocity
)
.limit(limit)
.offset((page * limit).toLong())
.orderBy(AuditLogTable.time, SortOrder.valueOf(sorting.uppercase()))
.map {
AuditLogEntry(
it[AuditLogTable.id].value,
it[AuditLogTable.time].toEpochMilli(),
it[AuditLogTable.server],
it[serverOwnerTable[SteamwarUserTable.username]],
it[actorTable[SteamwarUserTable.username]],
it[AuditLogTable.action],
it[AuditLogTable.actionText]
)
},
)
}
private fun Query.addAuditLogFilters(
actionText: String? = null,
serverText: String? = null,
fullText: String? = null,
actor: Set<Int>? = null,
actionType: Set<AuditLog.Type>? = null,
timeGreater: Instant? = null,
timeLess: Instant? = null,
serverOwner: Set<Int>? = null,
velocity: Boolean? = null,
): Query {
actionText?.let {
andWhere { (AuditLogTable.actionText like "%$it%") }
}
serverText?.let {
andWhere { (AuditLogTable.server like "%$it%") }
}
fullText?.let {
andWhere { (AuditLogTable.actionText like "%$it%") or (AuditLogTable.server like "%$it%") }
}
actor?.let { andWhere { AuditLogTable.actor inList actor } }
actionType?.let { andWhere { AuditLogTable.action inList actionType } }
timeGreater?.let { andWhere { AuditLogTable.time greater timeGreater } }
timeLess?.let { andWhere { AuditLogTable.time less timeLess } }
serverOwner?.let { andWhere { AuditLogTable.serverOwner inList serverOwner } }
if (velocity == true) andWhere { AuditLogTable.serverOwner.isNull() }
return this
}
}
}
@Serializable
data class AuditLogEntry(
val id: Int,
val time: Long,
val server: String,
val serverOwner: String?,
val actor: String,
val actionType: AuditLog.Type,
val actionText: String
)