diff --git a/build.gradle.kts b/build.gradle.kts index c8f2811..dbecb5e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,9 @@ dependencies { implementation("com.github.ajalt.mordant:mordant: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.jetbrains.exposed:exposed-core:1.0.0-rc-2") implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0-rc-2") diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index b125a7e..8ceb1ea 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -8,10 +8,11 @@ import de.steamwar.commands.database.InfoCommand import de.steamwar.commands.database.ResetCommand import de.steamwar.commands.user.UserCommand import de.steamwar.commands.user.UserInfoCommand +import de.steamwar.commands.user.UserSearchCommand fun main(args: Array) = SteamWar() .subcommands( DatabaseCommand().subcommands(InfoCommand(), ResetCommand()), - UserCommand().subcommands(UserInfoCommand()) + UserCommand().subcommands(UserInfoCommand(), UserSearchCommand()) ) .main(args) \ No newline at end of file diff --git a/src/main/kotlin/commands/user/UserCommand.kt b/src/main/kotlin/commands/user/UserCommand.kt index 12533fc..ba363b4 100644 --- a/src/main/kotlin/commands/user/UserCommand.kt +++ b/src/main/kotlin/commands/user/UserCommand.kt @@ -1,26 +1,7 @@ package de.steamwar.commands.user 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") { - val userId by argument() - 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 - } + override fun run() = Unit } \ No newline at end of file diff --git a/src/main/kotlin/commands/user/UserInfoCommand.kt b/src/main/kotlin/commands/user/UserInfoCommand.kt index aee0adf..b2feab7 100644 --- a/src/main/kotlin/commands/user/UserInfoCommand.kt +++ b/src/main/kotlin/commands/user/UserInfoCommand.kt @@ -1,28 +1,38 @@ package de.steamwar.commands.user 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 de.steamwar.db.findUser import de.steamwar.db.schema.Session 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.selectAll import org.jetbrains.exposed.v1.jdbc.transactions.transaction import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime -class UserInfoCommand: CliktCommand("info") { - val user by requireObject("user") +class UserInfoCommand : CliktCommand("info") { + val userId by argument().help("Id, Name, UUID or DiscordId") + val user by lazy { findUser(userId) ?: throw CliktError("User not found") } @OptIn(ExperimentalTime::class) override fun run() { 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 firstJoin = sessions.minByOrNull { it.first }?.first val lastJoin = sessions.maxByOrNull { it.second }?.second + val punishments = user.punishments + echo( table { body { @@ -34,11 +44,17 @@ class UserInfoCommand: CliktCommand("info") { row("Beigetreten am", firstJoin) row("Zuletzt gesehen am", lastJoin) row("Spielzeit", totalPlayed.toString() + "h") - row("Punishments", table { + row("Punishments", if (punishments.empty()) "Keine" else table { header { row("Typ", "Ersteller", "Von", "Bis", "Grund") } body { - user.punishments.map { - row(it.type, it.punisher.username, it.starttime.toString(), if (it.perma) "Perma" else it.endtime.toString(), it.reason) + punishments.map { + row( + it.type, + it.punisher.username, + it.starttime.toString(), + if (it.perma) "Perma" else it.endtime.toString(), + it.reason + ) } } }) diff --git a/src/main/kotlin/commands/user/UserSearchCommand.kt b/src/main/kotlin/commands/user/UserSearchCommand.kt new file mode 100644 index 0000000..eea354a --- /dev/null +++ b/src/main/kotlin/commands/user/UserSearchCommand.kt @@ -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() + + 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) } + } + }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/db/Database.kt b/src/main/kotlin/db/Database.kt index dc8c0e5..e889333 100644 --- a/src/main/kotlin/db/Database.kt +++ b/src/main/kotlin/db/Database.kt @@ -1,20 +1,27 @@ package de.steamwar.db +import com.github.ajalt.clikt.core.BaseCliktCommand 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.ExpressionWithColumnType import org.jetbrains.exposed.v1.core.Function import org.jetbrains.exposed.v1.core.IColumnType 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.WindowFunction import org.jetbrains.exposed.v1.core.WindowFunctionDefinition 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.transactions.transaction import java.io.File import java.sql.ResultSet import java.util.Properties +import java.util.UUID import kotlin.time.ExperimentalTime import kotlin.time.Instant @@ -66,18 +73,11 @@ class Database { } } -class UnixTimestamp( - val expr: Expression, - columnType: IColumnType -) : Function(columnType), WindowFunction { - override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { - append("UNIX_TIMESTAMP(", expr, ")") - } - - override fun over(): WindowFunctionDefinition { - return WindowFunctionDefinition(columnType, this) - } +fun > BaseCliktCommand.findUser(query: String): SteamwarUser? = transaction { + 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 }) } + .firstOrNull() + ?.let { return@transaction it } } -@OptIn(ExperimentalTime::class) -fun ExpressionWithColumnType.unixTimestamp(): UnixTimestamp = UnixTimestamp(this, LongColumnType()) \ No newline at end of file +fun joinedOr(vararg expressions: Expression?): Op = + expressions.filterNotNull().reduce { acc, expression -> acc or expression } as Op \ No newline at end of file diff --git a/src/main/kotlin/db/schema/SteamwarUser.kt b/src/main/kotlin/db/schema/SteamwarUser.kt index c14be1a..09792a5 100644 --- a/src/main/kotlin/db/schema/SteamwarUser.kt +++ b/src/main/kotlin/db/schema/SteamwarUser.kt @@ -6,7 +6,7 @@ import org.jetbrains.exposed.v1.dao.IntEntity import org.jetbrains.exposed.v1.dao.IntEntityClass object SteamwarUserTable: IntIdTable("UserData") { - val uuid = uuid("uuid") + val uuid = varchar("uuid", 255) val username = varchar("username", 255) val team = integer("team").references(TeamTable.id) val leader = bool("leader") @@ -14,7 +14,7 @@ object SteamwarUserTable: IntIdTable("UserData") { val manualLocale = bool("manuallocale") val bedrock = bool("bedrock") val password = varchar("password", 1024) - val discordId = long("DiscordId") + val discordId = long("DiscordId").nullable() } class SteamwarUser(id: EntityID): IntEntity(id) { @@ -22,6 +22,7 @@ class SteamwarUser(id: EntityID): IntEntity(id) { var uuid by SteamwarUserTable.uuid var username by SteamwarUserTable.username + var teamId by SteamwarUserTable.team var team by Team referencedOn SteamwarUserTable.team var leader by SteamwarUserTable.leader var locale by SteamwarUserTable.locale diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..1ac3bfc --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file