Add UserSearch
All checks were successful
SteamWarCI Build successful

This commit is contained in:
2025-10-25 13:55:03 +02:00
parent 0665c55b11
commit a76d61201c
8 changed files with 96 additions and 43 deletions

View File

@ -21,6 +21,9 @@ dependencies {
implementation("com.github.ajalt.mordant:mordant:3.0.2") implementation("com.github.ajalt.mordant:mordant:3.0.2")
implementation("com.github.ajalt.mordant:mordant-coroutines:3.0.2") implementation("com.github.ajalt.mordant:mordant-coroutines:3.0.2")
implementation("ch.qos.logback:logback-core:1.5.19")
implementation("ch.qos.logback:logback-classic:1.5.19")
implementation("org.mariadb.jdbc:mariadb-java-client:3.3.1") implementation("org.mariadb.jdbc:mariadb-java-client:3.3.1")
implementation("org.jetbrains.exposed:exposed-core:1.0.0-rc-2") implementation("org.jetbrains.exposed:exposed-core:1.0.0-rc-2")
implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0-rc-2") implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0-rc-2")

View File

@ -8,10 +8,11 @@ import de.steamwar.commands.database.InfoCommand
import de.steamwar.commands.database.ResetCommand import de.steamwar.commands.database.ResetCommand
import de.steamwar.commands.user.UserCommand import de.steamwar.commands.user.UserCommand
import de.steamwar.commands.user.UserInfoCommand import de.steamwar.commands.user.UserInfoCommand
import de.steamwar.commands.user.UserSearchCommand
fun main(args: Array<String>) = SteamWar() fun main(args: Array<String>) = SteamWar()
.subcommands( .subcommands(
DatabaseCommand().subcommands(InfoCommand(), ResetCommand()), DatabaseCommand().subcommands(InfoCommand(), ResetCommand()),
UserCommand().subcommands(UserInfoCommand()) UserCommand().subcommands(UserInfoCommand(), UserSearchCommand())
) )
.main(args) .main(args)

View File

@ -1,26 +1,7 @@
package de.steamwar.commands.user package de.steamwar.commands.user
import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.CliktError
import com.github.ajalt.clikt.core.findOrSetObject
import com.github.ajalt.clikt.parameters.arguments.argument
import de.steamwar.db.schema.SteamwarUser
import de.steamwar.db.schema.SteamwarUserTable
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
class UserCommand: CliktCommand("user") { class UserCommand: CliktCommand("user") {
val userId by argument() override fun run() = Unit
val user by findOrSetObject("user") {
transaction {
SteamwarUser.find { (SteamwarUserTable.id eq userId.toIntOrNull()) or (SteamwarUserTable.username eq userId) }
.firstOrNull()
?.let { return@transaction it } ?: throw CliktError("User not found!")
}
}
override fun run() {
user.id
}
} }

View File

@ -1,28 +1,38 @@
package de.steamwar.commands.user package de.steamwar.commands.user
import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.requireObject import com.github.ajalt.clikt.core.CliktError
import com.github.ajalt.clikt.core.findOrSetObject
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.help
import com.github.ajalt.mordant.table.table import com.github.ajalt.mordant.table.table
import de.steamwar.db.findUser
import de.steamwar.db.schema.Session import de.steamwar.db.schema.Session
import de.steamwar.db.schema.SteamwarUser import de.steamwar.db.schema.SteamwarUser
import de.steamwar.db.schema.SteamwarUserTable
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.jdbc.selectAll import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import kotlin.time.DurationUnit import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
class UserInfoCommand : CliktCommand("info") { class UserInfoCommand : CliktCommand("info") {
val user by requireObject<SteamwarUser>("user") val userId by argument().help("Id, Name, UUID or DiscordId")
val user by lazy { findUser(userId) ?: throw CliktError("User not found") }
@OptIn(ExperimentalTime::class) @OptIn(ExperimentalTime::class)
override fun run() { override fun run() {
transaction { transaction {
val sessions = Session.selectAll().where { Session.user eq user.id.value }.map { it[Session.start] to it[Session.end] } val sessions =
Session.selectAll().where { Session.user eq user.id.value }.map { it[Session.start] to it[Session.end] }
val totalPlayed = sessions.map { it.second - it.first }.sumOf { it.toDouble(DurationUnit.HOURS) } val totalPlayed = sessions.map { it.second - it.first }.sumOf { it.toDouble(DurationUnit.HOURS) }
val firstJoin = sessions.minByOrNull { it.first }?.first val firstJoin = sessions.minByOrNull { it.first }?.first
val lastJoin = sessions.maxByOrNull { it.second }?.second val lastJoin = sessions.maxByOrNull { it.second }?.second
val punishments = user.punishments
echo( echo(
table { table {
body { body {
@ -34,11 +44,17 @@ class UserInfoCommand: CliktCommand("info") {
row("Beigetreten am", firstJoin) row("Beigetreten am", firstJoin)
row("Zuletzt gesehen am", lastJoin) row("Zuletzt gesehen am", lastJoin)
row("Spielzeit", totalPlayed.toString() + "h") row("Spielzeit", totalPlayed.toString() + "h")
row("Punishments", table { row("Punishments", if (punishments.empty()) "Keine" else table {
header { row("Typ", "Ersteller", "Von", "Bis", "Grund") } header { row("Typ", "Ersteller", "Von", "Bis", "Grund") }
body { body {
user.punishments.map { punishments.map {
row(it.type, it.punisher.username, it.starttime.toString(), if (it.perma) "Perma" else it.endtime.toString(), it.reason) row(
it.type,
it.punisher.username,
it.starttime.toString(),
if (it.perma) "Perma" else it.endtime.toString(),
it.reason
)
} }
} }
}) })

View File

@ -0,0 +1,40 @@
package de.steamwar.commands.user
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.help
import com.github.ajalt.mordant.table.table
import de.steamwar.db.joinedOr
import de.steamwar.db.schema.SteamwarUser
import de.steamwar.db.schema.SteamwarUserTable
import de.steamwar.db.schema.Team
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.like
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
class UserSearchCommand : CliktCommand("search") {
val query by argument().help("Name, Id, UUID or DiscordId")
override fun help(context: Context): String = "Search for users"
override fun run() = transaction {
val users = SteamwarUser.find {
joinedOr(
SteamwarUserTable.username like "%$query%",
SteamwarUserTable.uuid like "%$query%",
query.toLongOrNull()?.let { SteamwarUserTable.discordId eq it },
query.toIntOrNull()?.let { SteamwarUserTable.id eq it }
)
}
val teams = mutableMapOf<Int, Team>()
echo(table {
header { row("Id", "Username", "UUID", "Team", "DiscordId") }
body {
users.map { row(it.id.value, it.username, it.uuid, teams.computeIfAbsent(it.teamId) { _ -> it.team }.name, it.discordId) }
}
})
}
}

View File

@ -1,20 +1,27 @@
package de.steamwar.db package de.steamwar.db
import com.github.ajalt.clikt.core.BaseCliktCommand
import com.github.ajalt.clikt.core.CliktError import com.github.ajalt.clikt.core.CliktError
import de.steamwar.db.schema.SteamwarUser
import de.steamwar.db.schema.SteamwarUserTable
import org.jetbrains.exposed.v1.core.Expression import org.jetbrains.exposed.v1.core.Expression
import org.jetbrains.exposed.v1.core.ExpressionWithColumnType import org.jetbrains.exposed.v1.core.ExpressionWithColumnType
import org.jetbrains.exposed.v1.core.Function import org.jetbrains.exposed.v1.core.Function
import org.jetbrains.exposed.v1.core.IColumnType import org.jetbrains.exposed.v1.core.IColumnType
import org.jetbrains.exposed.v1.core.LongColumnType import org.jetbrains.exposed.v1.core.LongColumnType
import org.jetbrains.exposed.v1.core.Op
import org.jetbrains.exposed.v1.core.QueryBuilder import org.jetbrains.exposed.v1.core.QueryBuilder
import org.jetbrains.exposed.v1.core.WindowFunction import org.jetbrains.exposed.v1.core.WindowFunction
import org.jetbrains.exposed.v1.core.WindowFunctionDefinition import org.jetbrains.exposed.v1.core.WindowFunctionDefinition
import org.jetbrains.exposed.v1.core.append import org.jetbrains.exposed.v1.core.append
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.jdbc.Database import org.jetbrains.exposed.v1.jdbc.Database
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.io.File import java.io.File
import java.sql.ResultSet import java.sql.ResultSet
import java.util.Properties import java.util.Properties
import java.util.UUID
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlin.time.Instant import kotlin.time.Instant
@ -66,18 +73,11 @@ class Database {
} }
} }
class UnixTimestamp<T: Any, in S>( fun <T: BaseCliktCommand<T>> BaseCliktCommand<T>.findUser(query: String): SteamwarUser? = transaction {
val expr: Expression<in S>, SteamwarUser.find { joinedOr(query.toIntOrNull()?.let { SteamwarUserTable.id eq it }, (SteamwarUserTable.username eq query), SteamwarUserTable.uuid eq query, query.toLongOrNull()?.let { SteamwarUserTable.discordId eq it }) }
columnType: IColumnType<T> .firstOrNull()
) : Function<T?>(columnType), WindowFunction<T?> { ?.let { return@transaction it }
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
append("UNIX_TIMESTAMP(", expr, ")")
} }
override fun over(): WindowFunctionDefinition<T?> { fun joinedOr(vararg expressions: Expression<Boolean>?): Op<Boolean> =
return WindowFunctionDefinition(columnType, this) expressions.filterNotNull().reduce { acc, expression -> acc or expression } as Op<Boolean>
}
}
@OptIn(ExperimentalTime::class)
fun ExpressionWithColumnType<Instant>.unixTimestamp(): UnixTimestamp<Long, Instant> = UnixTimestamp(this, LongColumnType())

View File

@ -6,7 +6,7 @@ import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass import org.jetbrains.exposed.v1.dao.IntEntityClass
object SteamwarUserTable: IntIdTable("UserData") { object SteamwarUserTable: IntIdTable("UserData") {
val uuid = uuid("uuid") val uuid = varchar("uuid", 255)
val username = varchar("username", 255) val username = varchar("username", 255)
val team = integer("team").references(TeamTable.id) val team = integer("team").references(TeamTable.id)
val leader = bool("leader") val leader = bool("leader")
@ -14,7 +14,7 @@ object SteamwarUserTable: IntIdTable("UserData") {
val manualLocale = bool("manuallocale") val manualLocale = bool("manuallocale")
val bedrock = bool("bedrock") val bedrock = bool("bedrock")
val password = varchar("password", 1024) val password = varchar("password", 1024)
val discordId = long("DiscordId") val discordId = long("DiscordId").nullable()
} }
class SteamwarUser(id: EntityID<Int>): IntEntity(id) { class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
@ -22,6 +22,7 @@ class SteamwarUser(id: EntityID<Int>): IntEntity(id) {
var uuid by SteamwarUserTable.uuid var uuid by SteamwarUserTable.uuid
var username by SteamwarUserTable.username var username by SteamwarUserTable.username
var teamId by SteamwarUserTable.team
var team by Team referencedOn SteamwarUserTable.team var team by Team referencedOn SteamwarUserTable.team
var leader by SteamwarUserTable.leader var leader by SteamwarUserTable.leader
var locale by SteamwarUserTable.locale var locale by SteamwarUserTable.locale

View File

@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>