Search by user ban_date, refactor of search

This commit is contained in:
nise.moe 2024-06-14 12:59:02 +02:00
parent 947f8cac02
commit 2b5ab1c054
8 changed files with 116 additions and 123 deletions

View File

@ -3,6 +3,7 @@ package com.nisemoe.nise
import com.nisemoe.generated.enums.JudgementType import com.nisemoe.generated.enums.JudgementType
import org.nisemoe.mari.judgements.Judgement import org.nisemoe.mari.judgements.Judgement
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
@ -20,6 +21,11 @@ class Format {
.format(targetFormatter) .format(targetFormatter)
} }
fun formatOffsetDateTime(dateTime: OffsetDateTime): String {
return LocalDateTime.ofInstant(dateTime.toInstant(), ZoneOffset.UTC)
.format(targetFormatter)
}
fun parseStringToDate(dateTimeStr: String): Date { fun parseStringToDate(dateTimeStr: String): Date {
val localDateTime = LocalDateTime.parse(dateTimeStr, targetFormatter) val localDateTime = LocalDateTime.parse(dateTimeStr, targetFormatter)
return Date.from(localDateTime.atZone(ZoneOffset.UTC).toInstant()) return Date.from(localDateTime.atZone(ZoneOffset.UTC).toInstant())

View File

@ -0,0 +1,87 @@
package com.nisemoe.nise.search
import org.jooq.Condition
import org.jooq.Field
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
class PredicateBuilder {
companion object {
fun buildBooleanCondition(field: Field<Boolean>, operator: String, value: Boolean): Condition {
return when (operator) {
"=" -> field.eq(value)
"!=" -> field.ne(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
fun buildGradeCondition(field: Field<String>, operator: String, value: String): Condition {
return when (value) {
"SS", "S", "A", "B", "C", "D" -> {
val valuesToMatch = when (value) {
"SS" -> listOf("Grade.SS", "Grade.SSH")
"S" -> listOf("Grade.S", "Grade.SH")
else -> listOf("Grade.$value")
}
when (operator) {
"=" -> field.`in`(valuesToMatch)
"!=" -> field.notIn(valuesToMatch)
else -> throw IllegalArgumentException("Invalid operator")
}
}
else -> throw IllegalArgumentException("Invalid grade value")
}
}
fun buildNumberCondition(field: Field<Double>, operator: String, value: Double): Condition {
return when (operator) {
"=" -> field.eq(value)
">" -> field.gt(value)
"<" -> field.lt(value)
">=" -> field.ge(value)
"<=" -> field.le(value)
"!=" -> field.ne(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
fun buildStringCondition(field: Field<String>, operator: String, value: String): Condition {
return when (operator.lowercase()) {
"=" -> field.eq(value)
"contains" -> field.containsIgnoreCase(value)
"like" -> field.likeIgnoreCase(
// Escape special characters for LIKE if needed
value.replace("%", "\\%").replace("_", "\\_")
)
else -> throw IllegalArgumentException("Invalid operator")
}
}
fun buildDatetimeCondition(field: Field<String>, operator: String, value: String): Condition {
if(field.type == OffsetDateTime::class.java) {
val fieldOffset = field as Field<OffsetDateTime>
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm")
val parsedDate = OffsetDateTime.parse(value, formatter.withZone(ZoneOffset.UTC))
return when (operator.lowercase()) {
"before" -> fieldOffset.lessThan(parsedDate)
"after" -> fieldOffset.greaterThan(parsedDate)
else -> throw IllegalArgumentException("Invalid operator")
}
}
return when (operator.lowercase()) {
"before" -> field.lessThan(value)
"after" -> field.greaterThan(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
}
}

View File

@ -63,6 +63,7 @@ class ScoreSearchController(
val user_count_50: Long?, val user_count_50: Long?,
val user_count_miss: Long?, val user_count_miss: Long?,
val user_is_banned: Boolean?, val user_is_banned: Boolean?,
val user_approx_ban_date: String?,
// Score fields // Score fields
val id: Int?, val id: Int?,

View File

@ -35,6 +35,7 @@ class ScoreSearchSchemaController(
InternalSchemaField("user_count_50", "50s", Category.user, Type.number, false, "number of 50 hits", databaseField = USERS.COUNT_50), InternalSchemaField("user_count_50", "50s", Category.user, Type.number, false, "number of 50 hits", databaseField = USERS.COUNT_50),
InternalSchemaField("user_count_miss", "Misses", Category.user, Type.number, false, "missed hits", databaseField = USERS.COUNT_MISS), InternalSchemaField("user_count_miss", "Misses", Category.user, Type.number, false, "missed hits", databaseField = USERS.COUNT_MISS),
InternalSchemaField("user_is_banned", "Is Banned", Category.user, Type.boolean, false, "is the user banned?", databaseField = USERS.IS_BANNED), InternalSchemaField("user_is_banned", "Is Banned", Category.user, Type.boolean, false, "is the user banned?", databaseField = USERS.IS_BANNED),
InternalSchemaField("user_approx_ban_date", "Approx. ban date", Category.user, Type.datetime, false, "approximate date the user was banned at.", databaseField = USERS.APPROX_BAN_DATE),
// Score fields // Score fields
InternalSchemaField("is_banned", "Banned", Category.score, Type.boolean, false, "has to score been deleted?", databaseField = SCORES.IS_BANNED), InternalSchemaField("is_banned", "Banned", Category.score, Type.boolean, false, "has to score been deleted?", databaseField = SCORES.IS_BANNED),

View File

@ -5,6 +5,11 @@ import com.nisemoe.generated.tables.references.SCORES
import com.nisemoe.generated.tables.references.USERS import com.nisemoe.generated.tables.references.USERS
import com.nisemoe.nise.Format import com.nisemoe.nise.Format
import com.nisemoe.nise.osu.Mod import com.nisemoe.nise.osu.Mod
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildBooleanCondition
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildDatetimeCondition
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildGradeCondition
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildNumberCondition
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildStringCondition
import com.nisemoe.nise.service.AuthService import com.nisemoe.nise.service.AuthService
import org.jooq.* import org.jooq.*
import org.jooq.impl.DSL import org.jooq.impl.DSL
@ -47,6 +52,7 @@ class ScoreSearchService(
USERS.COUNT_50, USERS.COUNT_50,
USERS.COUNT_MISS, USERS.COUNT_MISS,
USERS.IS_BANNED, USERS.IS_BANNED,
USERS.APPROX_BAN_DATE,
// Scores fields // Scores fields
SCORES.ID, SCORES.ID,
@ -155,6 +161,7 @@ class ScoreSearchService(
user_count_50 = it.get(USERS.COUNT_50), user_count_50 = it.get(USERS.COUNT_50),
user_count_miss = it.get(USERS.COUNT_MISS), user_count_miss = it.get(USERS.COUNT_MISS),
user_is_banned = it.get(USERS.IS_BANNED), user_is_banned = it.get(USERS.IS_BANNED),
user_approx_ban_date = it.get(USERS.APPROX_BAN_DATE)?.let { it1 -> Format.formatOffsetDateTime(it1) },
// Score fields // Score fields
id = it.get(SCORES.ID), id = it.get(SCORES.ID),
@ -260,64 +267,4 @@ class ScoreSearchService(
} }
return databaseField.databaseField!! return databaseField.databaseField!!
} }
private fun buildBooleanCondition(field: Field<Boolean>, operator: String, value: Boolean): Condition {
return when (operator) {
"=" -> field.eq(value)
"!=" -> field.ne(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
private fun buildGradeCondition(field: Field<String>, operator: String, value: String): Condition {
return when (value) {
"SS", "S", "A", "B", "C", "D" -> {
val valuesToMatch = when (value) {
"SS" -> listOf("Grade.SS", "Grade.SSH")
"S" -> listOf("Grade.S", "Grade.SH")
else -> listOf("Grade.$value")
}
when (operator) {
"=" -> field.`in`(valuesToMatch)
"!=" -> field.notIn(valuesToMatch)
else -> throw IllegalArgumentException("Invalid operator")
}
}
else -> throw IllegalArgumentException("Invalid grade value")
}
}
private fun buildNumberCondition(field: Field<Double>, operator: String, value: Double): Condition {
return when (operator) {
"=" -> field.eq(value)
">" -> field.gt(value)
"<" -> field.lt(value)
">=" -> field.ge(value)
"<=" -> field.le(value)
"!=" -> field.ne(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
private fun buildStringCondition(field: Field<String>, operator: String, value: String): Condition {
return when (operator.lowercase()) {
"=" -> field.eq(value)
"contains" -> field.containsIgnoreCase(value)
"like" -> field.likeIgnoreCase(
// Escape special characters for LIKE if needed
value.replace("%", "\\%").replace("_", "\\_")
)
else -> throw IllegalArgumentException("Invalid operator")
}
}
private fun buildDatetimeCondition(field: Field<String>, operator: String, value: String): Condition {
return when (operator.lowercase()) {
"before" -> field.lessThan(value)
"after" -> field.greaterThan(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
} }

View File

@ -61,7 +61,8 @@ class UserSearchController(
val count_100: Long?, val count_100: Long?,
val count_50: Long?, val count_50: Long?,
val count_miss: Long?, val count_miss: Long?,
val is_banned: Boolean? val is_banned: Boolean?,
val approx_ban_date: String?,
) )
data class SearchRequest( data class SearchRequest(

View File

@ -33,6 +33,7 @@ class UserSearchSchemaController(
InternalSchemaField("count_50", "50s", Category.user, Type.number, false, "number of 50 hits", databaseField = USERS.COUNT_50), InternalSchemaField("count_50", "50s", Category.user, Type.number, false, "number of 50 hits", databaseField = USERS.COUNT_50),
InternalSchemaField("count_miss", "Misses", Category.user, Type.number, false, "missed hits", databaseField = USERS.COUNT_MISS), InternalSchemaField("count_miss", "Misses", Category.user, Type.number, false, "missed hits", databaseField = USERS.COUNT_MISS),
InternalSchemaField("is_banned", "Is Banned", Category.user, Type.boolean, false, "is the user banned?", databaseField = USERS.IS_BANNED), InternalSchemaField("is_banned", "Is Banned", Category.user, Type.boolean, false, "is the user banned?", databaseField = USERS.IS_BANNED),
InternalSchemaField("approx_ban_date", "Approx. ban date", Category.user, Type.datetime, false, "approximate date the user was banned at.", databaseField = USERS.APPROX_BAN_DATE),
) )
} }

View File

@ -3,11 +3,17 @@ package com.nisemoe.nise.search.user
import com.nisemoe.generated.tables.references.SCORES import com.nisemoe.generated.tables.references.SCORES
import com.nisemoe.generated.tables.references.USERS import com.nisemoe.generated.tables.references.USERS
import com.nisemoe.nise.Format import com.nisemoe.nise.Format
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildBooleanCondition
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildDatetimeCondition
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildGradeCondition
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildNumberCondition
import com.nisemoe.nise.search.PredicateBuilder.Companion.buildStringCondition
import org.jooq.* import org.jooq.*
import org.jooq.impl.DSL import org.jooq.impl.DSL
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Service @Service
class UserSearchService( class UserSearchService(
private val dslContext: DSLContext private val dslContext: DSLContext
@ -41,7 +47,8 @@ class UserSearchService(
USERS.COUNT_100, USERS.COUNT_100,
USERS.COUNT_50, USERS.COUNT_50,
USERS.COUNT_MISS, USERS.COUNT_MISS,
USERS.IS_BANNED USERS.IS_BANNED,
USERS.APPROX_BAN_DATE
) )
val query = dslContext val query = dslContext
@ -93,7 +100,8 @@ class UserSearchService(
count_100 = it.get(USERS.COUNT_100), count_100 = it.get(USERS.COUNT_100),
count_50 = it.get(USERS.COUNT_50), count_50 = it.get(USERS.COUNT_50),
count_miss = it.get(USERS.COUNT_MISS), count_miss = it.get(USERS.COUNT_MISS),
is_banned = it.get(USERS.IS_BANNED) is_banned = it.get(USERS.IS_BANNED),
approx_ban_date = it.get(USERS.APPROX_BAN_DATE)?.let { it1 -> Format.formatOffsetDateTime(it1) }
) )
} }
@ -154,63 +162,4 @@ class UserSearchService(
return databaseField.databaseField!! return databaseField.databaseField!!
} }
private fun buildBooleanCondition(field: Field<Boolean>, operator: String, value: Boolean): Condition {
return when (operator) {
"=" -> field.eq(value)
"!=" -> field.ne(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
private fun buildGradeCondition(field: Field<String>, operator: String, value: String): Condition {
return when (value) {
"SS", "S", "A", "B", "C", "D" -> {
val valuesToMatch = when (value) {
"SS" -> listOf("Grade.SS", "Grade.SSH")
"S" -> listOf("Grade.S", "Grade.SH")
else -> listOf("Grade.$value")
}
when (operator) {
"=" -> field.`in`(valuesToMatch)
"!=" -> field.notIn(valuesToMatch)
else -> throw IllegalArgumentException("Invalid operator")
}
}
else -> throw IllegalArgumentException("Invalid grade value")
}
}
private fun buildNumberCondition(field: Field<Double>, operator: String, value: Double): Condition {
return when (operator) {
"=" -> field.eq(value)
">" -> field.gt(value)
"<" -> field.lt(value)
">=" -> field.ge(value)
"<=" -> field.le(value)
"!=" -> field.ne(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
private fun buildStringCondition(field: Field<String>, operator: String, value: String): Condition {
return when (operator.lowercase()) {
"=" -> field.eq(value)
"contains" -> field.containsIgnoreCase(value)
"like" -> field.likeIgnoreCase(
// Escape special characters for LIKE if needed
value.replace("%", "\\%").replace("_", "\\_")
)
else -> throw IllegalArgumentException("Invalid operator")
}
}
private fun buildDatetimeCondition(field: Field<String>, operator: String, value: String): Condition {
return when (operator.lowercase()) {
"before" -> field.lessThan(value)
"after" -> field.greaterThan(value)
else -> throw IllegalArgumentException("Invalid operator")
}
}
} }