Implemented slider end release times and keypress release times, along with a privileged auth system
This commit is contained in:
parent
1ce7d4c599
commit
01bd4e4948
@ -16,7 +16,7 @@ import org.jooq.ForeignKey
|
|||||||
import org.jooq.Name
|
import org.jooq.Name
|
||||||
import org.jooq.Record
|
import org.jooq.Record
|
||||||
import org.jooq.Records
|
import org.jooq.Records
|
||||||
import org.jooq.Row16
|
import org.jooq.Row17
|
||||||
import org.jooq.Schema
|
import org.jooq.Schema
|
||||||
import org.jooq.SelectField
|
import org.jooq.SelectField
|
||||||
import org.jooq.Table
|
import org.jooq.Table
|
||||||
@ -142,6 +142,11 @@ open class Users(
|
|||||||
*/
|
*/
|
||||||
val SYS_LAST_UPDATE: TableField<UsersRecord, LocalDateTime?> = createField(DSL.name("sys_last_update"), SQLDataType.LOCALDATETIME(6), this, "")
|
val SYS_LAST_UPDATE: TableField<UsersRecord, LocalDateTime?> = createField(DSL.name("sys_last_update"), SQLDataType.LOCALDATETIME(6), this, "")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The column <code>public.users.is_admin</code>.
|
||||||
|
*/
|
||||||
|
val IS_ADMIN: TableField<UsersRecord, Boolean?> = createField(DSL.name("is_admin"), SQLDataType.BOOLEAN.defaultValue(DSL.field(DSL.raw("false"), SQLDataType.BOOLEAN)), this, "")
|
||||||
|
|
||||||
private constructor(alias: Name, aliased: Table<UsersRecord>?): this(alias, null, null, aliased, null)
|
private constructor(alias: Name, aliased: Table<UsersRecord>?): this(alias, null, null, aliased, null)
|
||||||
private constructor(alias: Name, aliased: Table<UsersRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, aliased, parameters)
|
private constructor(alias: Name, aliased: Table<UsersRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, aliased, parameters)
|
||||||
|
|
||||||
@ -183,18 +188,18 @@ open class Users(
|
|||||||
override fun rename(name: Table<*>): Users = Users(name.getQualifiedName(), null)
|
override fun rename(name: Table<*>): Users = Users(name.getQualifiedName(), null)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Row16 type methods
|
// Row17 type methods
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
override fun fieldsRow(): Row16<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?> = super.fieldsRow() as Row16<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?>
|
override fun fieldsRow(): Row17<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?> = super.fieldsRow() as Row17<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience mapping calling {@link SelectField#convertFrom(Function)}.
|
* Convenience mapping calling {@link SelectField#convertFrom(Function)}.
|
||||||
*/
|
*/
|
||||||
fun <U> mapping(from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?) -> U): SelectField<U> = convertFrom(Records.mapping(from))
|
fun <U> mapping(from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?) -> U): SelectField<U> = convertFrom(Records.mapping(from))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience mapping calling {@link SelectField#convertFrom(Class,
|
* Convenience mapping calling {@link SelectField#convertFrom(Class,
|
||||||
* Function)}.
|
* Function)}.
|
||||||
*/
|
*/
|
||||||
fun <U> mapping(toType: Class<U>, from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?) -> U): SelectField<U> = convertFrom(toType, Records.mapping(from))
|
fun <U> mapping(toType: Class<U>, from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?) -> U): SelectField<U> = convertFrom(toType, Records.mapping(from))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import java.time.LocalDateTime
|
|||||||
|
|
||||||
import org.jooq.Field
|
import org.jooq.Field
|
||||||
import org.jooq.Record1
|
import org.jooq.Record1
|
||||||
import org.jooq.Record16
|
import org.jooq.Record17
|
||||||
import org.jooq.Row16
|
import org.jooq.Row17
|
||||||
import org.jooq.impl.UpdatableRecordImpl
|
import org.jooq.impl.UpdatableRecordImpl
|
||||||
|
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ import org.jooq.impl.UpdatableRecordImpl
|
|||||||
* This class is generated by jOOQ.
|
* This class is generated by jOOQ.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(Users.USERS), Record16<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?> {
|
open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(Users.USERS), Record17<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?> {
|
||||||
|
|
||||||
open var userId: Long?
|
open var userId: Long?
|
||||||
set(value): Unit = set(0, value)
|
set(value): Unit = set(0, value)
|
||||||
@ -85,6 +85,12 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
|||||||
set(value): Unit = set(15, value)
|
set(value): Unit = set(15, value)
|
||||||
get(): LocalDateTime? = get(15) as LocalDateTime?
|
get(): LocalDateTime? = get(15) as LocalDateTime?
|
||||||
|
|
||||||
|
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||||
|
@set:JvmName("setIsAdmin")
|
||||||
|
open var isAdmin: Boolean?
|
||||||
|
set(value): Unit = set(16, value)
|
||||||
|
get(): Boolean? = get(16) as Boolean?
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Primary key information
|
// Primary key information
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@ -92,11 +98,11 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
|||||||
override fun key(): Record1<Long?> = super.key() as Record1<Long?>
|
override fun key(): Record1<Long?> = super.key() as Record1<Long?>
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Record16 type implementation
|
// Record17 type implementation
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
override fun fieldsRow(): Row16<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?> = super.fieldsRow() as Row16<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?>
|
override fun fieldsRow(): Row17<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?> = super.fieldsRow() as Row17<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?>
|
||||||
override fun valuesRow(): Row16<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?> = super.valuesRow() as Row16<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?>
|
override fun valuesRow(): Row17<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?> = super.valuesRow() as Row17<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?>
|
||||||
override fun field1(): Field<Long?> = Users.USERS.USER_ID
|
override fun field1(): Field<Long?> = Users.USERS.USER_ID
|
||||||
override fun field2(): Field<String?> = Users.USERS.USERNAME
|
override fun field2(): Field<String?> = Users.USERS.USERNAME
|
||||||
override fun field3(): Field<LocalDateTime?> = Users.USERS.JOIN_DATE
|
override fun field3(): Field<LocalDateTime?> = Users.USERS.JOIN_DATE
|
||||||
@ -113,6 +119,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
|||||||
override fun field14(): Field<Long?> = Users.USERS.COUNT_300
|
override fun field14(): Field<Long?> = Users.USERS.COUNT_300
|
||||||
override fun field15(): Field<Long?> = Users.USERS.COUNT_50
|
override fun field15(): Field<Long?> = Users.USERS.COUNT_50
|
||||||
override fun field16(): Field<LocalDateTime?> = Users.USERS.SYS_LAST_UPDATE
|
override fun field16(): Field<LocalDateTime?> = Users.USERS.SYS_LAST_UPDATE
|
||||||
|
override fun field17(): Field<Boolean?> = Users.USERS.IS_ADMIN
|
||||||
override fun component1(): Long? = userId
|
override fun component1(): Long? = userId
|
||||||
override fun component2(): String? = username
|
override fun component2(): String? = username
|
||||||
override fun component3(): LocalDateTime? = joinDate
|
override fun component3(): LocalDateTime? = joinDate
|
||||||
@ -129,6 +136,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
|||||||
override fun component14(): Long? = count_300
|
override fun component14(): Long? = count_300
|
||||||
override fun component15(): Long? = count_50
|
override fun component15(): Long? = count_50
|
||||||
override fun component16(): LocalDateTime? = sysLastUpdate
|
override fun component16(): LocalDateTime? = sysLastUpdate
|
||||||
|
override fun component17(): Boolean? = isAdmin
|
||||||
override fun value1(): Long? = userId
|
override fun value1(): Long? = userId
|
||||||
override fun value2(): String? = username
|
override fun value2(): String? = username
|
||||||
override fun value3(): LocalDateTime? = joinDate
|
override fun value3(): LocalDateTime? = joinDate
|
||||||
@ -145,6 +153,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
|||||||
override fun value14(): Long? = count_300
|
override fun value14(): Long? = count_300
|
||||||
override fun value15(): Long? = count_50
|
override fun value15(): Long? = count_50
|
||||||
override fun value16(): LocalDateTime? = sysLastUpdate
|
override fun value16(): LocalDateTime? = sysLastUpdate
|
||||||
|
override fun value17(): Boolean? = isAdmin
|
||||||
|
|
||||||
override fun value1(value: Long?): UsersRecord {
|
override fun value1(value: Long?): UsersRecord {
|
||||||
set(0, value)
|
set(0, value)
|
||||||
@ -226,7 +235,12 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun values(value1: Long?, value2: String?, value3: LocalDateTime?, value4: String?, value5: Long?, value6: Long?, value7: Double?, value8: Double?, value9: Long?, value10: Long?, value11: Long?, value12: Long?, value13: Long?, value14: Long?, value15: Long?, value16: LocalDateTime?): UsersRecord {
|
override fun value17(value: Boolean?): UsersRecord {
|
||||||
|
set(16, value)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun values(value1: Long?, value2: String?, value3: LocalDateTime?, value4: String?, value5: Long?, value6: Long?, value7: Double?, value8: Double?, value9: Long?, value10: Long?, value11: Long?, value12: Long?, value13: Long?, value14: Long?, value15: Long?, value16: LocalDateTime?, value17: Boolean?): UsersRecord {
|
||||||
this.value1(value1)
|
this.value1(value1)
|
||||||
this.value2(value2)
|
this.value2(value2)
|
||||||
this.value3(value3)
|
this.value3(value3)
|
||||||
@ -243,13 +257,14 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
|||||||
this.value14(value14)
|
this.value14(value14)
|
||||||
this.value15(value15)
|
this.value15(value15)
|
||||||
this.value16(value16)
|
this.value16(value16)
|
||||||
|
this.value17(value17)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a detached, initialised UsersRecord
|
* Create a detached, initialised UsersRecord
|
||||||
*/
|
*/
|
||||||
constructor(userId: Long? = null, username: String? = null, joinDate: LocalDateTime? = null, country: String? = null, countryRank: Long? = null, rank: Long? = null, ppRaw: Double? = null, accuracy: Double? = null, playcount: Long? = null, totalScore: Long? = null, rankedScore: Long? = null, secondsPlayed: Long? = null, count_100: Long? = null, count_300: Long? = null, count_50: Long? = null, sysLastUpdate: LocalDateTime? = null): this() {
|
constructor(userId: Long? = null, username: String? = null, joinDate: LocalDateTime? = null, country: String? = null, countryRank: Long? = null, rank: Long? = null, ppRaw: Double? = null, accuracy: Double? = null, playcount: Long? = null, totalScore: Long? = null, rankedScore: Long? = null, secondsPlayed: Long? = null, count_100: Long? = null, count_300: Long? = null, count_50: Long? = null, sysLastUpdate: LocalDateTime? = null, isAdmin: Boolean? = null): this() {
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
this.username = username
|
this.username = username
|
||||||
this.joinDate = joinDate
|
this.joinDate = joinDate
|
||||||
@ -266,6 +281,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
|||||||
this.count_300 = count_300
|
this.count_300 = count_300
|
||||||
this.count_50 = count_50
|
this.count_50 = count_50
|
||||||
this.sysLastUpdate = sysLastUpdate
|
this.sysLastUpdate = sysLastUpdate
|
||||||
|
this.isAdmin = isAdmin
|
||||||
resetChangedOnNotNull()
|
resetChangedOnNotNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,6 +69,13 @@ data class ReplayPair(
|
|||||||
val statistics: ReplayPairStatistics
|
val statistics: ReplayPairStatistics
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class ReplayDataChart(
|
||||||
|
val title: String,
|
||||||
|
val tableSamples: Int,
|
||||||
|
val table: List<Triple<String, String, String>>,
|
||||||
|
val data: List<Pair<Double, Double>>
|
||||||
|
)
|
||||||
|
|
||||||
data class ReplayData(
|
data class ReplayData(
|
||||||
val replay_id: Long,
|
val replay_id: Long,
|
||||||
val user_id: Int,
|
val user_id: Int,
|
||||||
@ -121,7 +128,8 @@ data class ReplayData(
|
|||||||
val count_50: Int,
|
val count_50: Int,
|
||||||
val count_miss: Int,
|
val count_miss: Int,
|
||||||
|
|
||||||
val error_distribution: Map<Int, DistributionEntry>
|
val error_distribution: Map<Int, DistributionEntry>,
|
||||||
|
val charts: List<ReplayDataChart>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun calculateAccuracy(): Double {
|
fun calculateAccuracy(): Double {
|
||||||
|
|||||||
@ -4,13 +4,13 @@ import com.nisemoe.generated.tables.records.ScoresJudgementsRecord
|
|||||||
import com.nisemoe.generated.tables.references.*
|
import com.nisemoe.generated.tables.references.*
|
||||||
import com.nisemoe.nise.*
|
import com.nisemoe.nise.*
|
||||||
import com.nisemoe.nise.osu.Mod
|
import com.nisemoe.nise.osu.Mod
|
||||||
|
import com.nisemoe.nise.service.AuthService
|
||||||
import org.jooq.Condition
|
import org.jooq.Condition
|
||||||
import org.jooq.DSLContext
|
import org.jooq.DSLContext
|
||||||
import org.jooq.Record
|
import org.jooq.Record
|
||||||
import org.jooq.Result
|
import org.jooq.Result
|
||||||
import org.jooq.impl.DSL
|
import org.jooq.impl.DSL
|
||||||
import org.jooq.impl.DSL.avg
|
import org.jooq.impl.DSL.avg
|
||||||
import org.springframework.cache.annotation.Cacheable
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -18,7 +18,8 @@ import kotlin.math.roundToInt
|
|||||||
@Service
|
@Service
|
||||||
class ScoreService(
|
class ScoreService(
|
||||||
private val dslContext: DSLContext,
|
private val dslContext: DSLContext,
|
||||||
private val beatmapService: BeatmapService
|
private val beatmapService: BeatmapService,
|
||||||
|
private val authService: AuthService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -30,6 +31,75 @@ class ScoreService(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCharts(db: Record): List<ReplayDataChart> {
|
||||||
|
// We only return additional charts if the user is an admin.
|
||||||
|
if (!authService.isAdmin()) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slider end chart
|
||||||
|
val sliderEndData = db.get(SCORES.SLIDEREND_RELEASE_TIMES)!!
|
||||||
|
.filterNotNull()
|
||||||
|
val sliderFrequencyData: List<Pair<Double, Double>> = sliderEndData
|
||||||
|
.groupingBy { it }
|
||||||
|
.eachCount()
|
||||||
|
.map { (value, count) -> Pair(value, count / sliderEndData.size.toDouble() * 100) }
|
||||||
|
|
||||||
|
// Slider end table
|
||||||
|
val sliderEndTable = mutableListOf<Triple<String, String, String>>()
|
||||||
|
|
||||||
|
sliderEndTable.add(Triple(
|
||||||
|
"Median",
|
||||||
|
String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_MEDIAN)),
|
||||||
|
String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED))
|
||||||
|
))
|
||||||
|
sliderEndTable.add(Triple(
|
||||||
|
"Std. deviation",
|
||||||
|
String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION)),
|
||||||
|
String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED))
|
||||||
|
))
|
||||||
|
|
||||||
|
val sliderEndChart = ReplayDataChart(
|
||||||
|
title = "slider end release times",
|
||||||
|
tableSamples = 0,
|
||||||
|
table = sliderEndTable,
|
||||||
|
data = sliderFrequencyData
|
||||||
|
)
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
// Slider end chart
|
||||||
|
val keypressData = db.get(SCORES.KEYPRESSES_TIMES)!!
|
||||||
|
.filterNotNull()
|
||||||
|
val keypressFrequencyData: List<Pair<Double, Double>> = keypressData
|
||||||
|
.groupingBy { it }
|
||||||
|
.eachCount()
|
||||||
|
.map { (value, count) -> Pair(value, count / keypressData.size.toDouble() * 100) }
|
||||||
|
|
||||||
|
// Slider end table
|
||||||
|
val keypressTable = mutableListOf<Triple<String, String, String>>()
|
||||||
|
|
||||||
|
keypressTable.add(Triple(
|
||||||
|
"Median",
|
||||||
|
String.format("%.2f", db.get(SCORES.KEYPRESSES_MEDIAN)),
|
||||||
|
String.format("%.2f", db.get(SCORES.KEYPRESSES_MEDIAN_ADJUSTED))
|
||||||
|
))
|
||||||
|
keypressTable.add(Triple(
|
||||||
|
"Std. deviation",
|
||||||
|
String.format("%.2f", db.get(SCORES.KEYPRESSES_STANDARD_DEVIATION)),
|
||||||
|
String.format("%.2f", db.get(SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED))
|
||||||
|
))
|
||||||
|
|
||||||
|
val keypressChart = ReplayDataChart(
|
||||||
|
title = "keypress release times",
|
||||||
|
tableSamples = 0,
|
||||||
|
table = keypressTable,
|
||||||
|
data = keypressFrequencyData
|
||||||
|
)
|
||||||
|
|
||||||
|
return listOf(sliderEndChart, keypressChart)
|
||||||
|
}
|
||||||
|
|
||||||
fun getReplayData(replayId: Long): ReplayData? {
|
fun getReplayData(replayId: Long): ReplayData? {
|
||||||
val result = dslContext.select(DSL.asterisk())
|
val result = dslContext.select(DSL.asterisk())
|
||||||
.from(SCORES)
|
.from(SCORES)
|
||||||
@ -41,6 +111,7 @@ class ScoreService(
|
|||||||
val beatmapId = result.get(BEATMAPS.BEATMAP_ID, Int::class.java)
|
val beatmapId = result.get(BEATMAPS.BEATMAP_ID, Int::class.java)
|
||||||
val averageUR = beatmapService.getAverageUR(beatmapId = beatmapId, excludeReplayId = replayId)
|
val averageUR = beatmapService.getAverageUR(beatmapId = beatmapId, excludeReplayId = replayId)
|
||||||
val hitDistribution = this.getHitDistribution(scoreId = result.get(SCORES.ID, Int::class.java))
|
val hitDistribution = this.getHitDistribution(scoreId = result.get(SCORES.ID, Int::class.java))
|
||||||
|
val charts = this.getCharts(result)
|
||||||
|
|
||||||
val replayData = ReplayData(
|
val replayData = ReplayData(
|
||||||
replay_id = replayId,
|
replay_id = replayId,
|
||||||
@ -80,6 +151,7 @@ class ScoreService(
|
|||||||
error_coefficient_of_variation = result.get(SCORES.ERROR_COEFFICIENT_OF_VARIATION, Double::class.java),
|
error_coefficient_of_variation = result.get(SCORES.ERROR_COEFFICIENT_OF_VARIATION, Double::class.java),
|
||||||
error_kurtosis = result.get(SCORES.ERROR_KURTOSIS, Double::class.java),
|
error_kurtosis = result.get(SCORES.ERROR_KURTOSIS, Double::class.java),
|
||||||
error_skewness = result.get(SCORES.ERROR_SKEWNESS, Double::class.java),
|
error_skewness = result.get(SCORES.ERROR_SKEWNESS, Double::class.java),
|
||||||
|
charts = charts
|
||||||
)
|
)
|
||||||
this.loadComparableReplayData(replayData)
|
this.loadComparableReplayData(replayData)
|
||||||
return replayData
|
return replayData
|
||||||
|
|||||||
@ -1,18 +1,30 @@
|
|||||||
package com.nisemoe.nise.service
|
package com.nisemoe.nise.service
|
||||||
|
|
||||||
|
import com.nisemoe.generated.tables.references.USERS
|
||||||
|
import org.jooq.DSLContext
|
||||||
import org.springframework.security.authentication.AnonymousAuthenticationToken
|
import org.springframework.security.authentication.AnonymousAuthenticationToken
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User
|
import org.springframework.security.oauth2.core.user.DefaultOAuth2User
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class AuthService {
|
class AuthService(
|
||||||
|
private val dslContext: DSLContext
|
||||||
|
) {
|
||||||
|
|
||||||
data class UserInfo(
|
data class UserInfo(
|
||||||
val userId: Int,
|
val userId: Long,
|
||||||
val username: String
|
val username: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun isAdmin(): Boolean {
|
||||||
|
if(!this.isLoggedIn())
|
||||||
|
return false
|
||||||
|
|
||||||
|
val currentUser = this.getCurrentUser()
|
||||||
|
return dslContext.fetchExists(USERS, USERS.USER_ID.eq(currentUser.userId).and(USERS.IS_ADMIN))
|
||||||
|
}
|
||||||
|
|
||||||
fun isLoggedIn(): Boolean {
|
fun isLoggedIn(): Boolean {
|
||||||
val authentication = SecurityContextHolder.getContext().authentication
|
val authentication = SecurityContextHolder.getContext().authentication
|
||||||
return !(authentication == null || authentication is AnonymousAuthenticationToken || authentication.principal == null)
|
return !(authentication == null || authentication is AnonymousAuthenticationToken || authentication.principal == null)
|
||||||
@ -26,10 +38,9 @@ class AuthService {
|
|||||||
val userDetails = authentication.principal as DefaultOAuth2User
|
val userDetails = authentication.principal as DefaultOAuth2User
|
||||||
|
|
||||||
return UserInfo(
|
return UserInfo(
|
||||||
userId = userDetails.attributes["id"] as Int,
|
userId = (userDetails.attributes["id"] as Int).toLong(),
|
||||||
username = userDetails.attributes["username"] as String
|
username = userDetails.attributes["username"] as String
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE public.users
|
||||||
|
ADD COLUMN is_admin boolean DEFAULT false;
|
||||||
@ -26,5 +26,5 @@
|
|||||||
</div>
|
</div>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
<div class="text-center version">
|
<div class="text-center version">
|
||||||
v20240217
|
v20240218
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser';
|
|||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import {HttpClientModule } from "@angular/common/http";
|
import {HTTP_INTERCEPTORS, HttpClientModule} from "@angular/common/http";
|
||||||
import { ViewSuspiciousScoresComponent } from './view-suspicious-scores/view-suspicious-scores.component';
|
import { ViewSuspiciousScoresComponent } from './view-suspicious-scores/view-suspicious-scores.component';
|
||||||
import { ViewSimilarReplaysComponent } from './view-similar-replays/view-similar-replays.component';
|
import { ViewSimilarReplaysComponent } from './view-similar-replays/view-similar-replays.component';
|
||||||
import { HomeComponent } from './home/home.component';
|
import { HomeComponent } from './home/home.component';
|
||||||
@ -12,6 +12,7 @@ import {FormsModule} from "@angular/forms";
|
|||||||
import {NgOptimizedImage} from "@angular/common";
|
import {NgOptimizedImage} from "@angular/common";
|
||||||
import {rxStompServiceFactory} from "../corelib/stomp/stomp.factory";
|
import {rxStompServiceFactory} from "../corelib/stomp/stomp.factory";
|
||||||
import {RxStompService} from "../corelib/stomp/stomp.service";
|
import {RxStompService} from "../corelib/stomp/stomp.service";
|
||||||
|
import {NiseHttpInterceptor} from "../corelib/nise-http.interceptor";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -32,7 +33,8 @@ import {RxStompService} from "../corelib/stomp/stomp.service";
|
|||||||
{
|
{
|
||||||
provide: RxStompService,
|
provide: RxStompService,
|
||||||
useFactory: rxStompServiceFactory,
|
useFactory: rxStompServiceFactory,
|
||||||
}
|
},
|
||||||
|
{ provide: HTTP_INTERCEPTORS, useClass: NiseHttpInterceptor, multi: true }
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {Observable, Subscription} from "rxjs";
|
import {Observable, Subscription} from "rxjs";
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {LocalCacheService} from "../../corelib/local-cache.service";
|
import {LocalCacheService} from "../../corelib/service/local-cache.service";
|
||||||
import {RxStompService} from "../../corelib/stomp/stomp.service";
|
import {RxStompService} from "../../corelib/stomp/stomp.service";
|
||||||
import {Message} from "@stomp/stompjs/esm6";
|
import {Message} from "@stomp/stompjs/esm6";
|
||||||
import {ReplayData} from "../replays";
|
import {ReplayData} from "../replays";
|
||||||
|
|||||||
@ -1,3 +1,10 @@
|
|||||||
|
export interface ReplayDataChart {
|
||||||
|
title: string;
|
||||||
|
tableSamples: number;
|
||||||
|
table: Array<{ first: string, second: string, third: string }>;
|
||||||
|
data: Array<{ first: number, second: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReplayData {
|
export interface ReplayData {
|
||||||
replay_id: number;
|
replay_id: number;
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@ -51,6 +58,7 @@ export interface ReplayData {
|
|||||||
count_miss: number;
|
count_miss: number;
|
||||||
|
|
||||||
error_distribution: ErrorDistribution;
|
error_distribution: ErrorDistribution;
|
||||||
|
charts: ReplayDataChart[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DistributionEntry {
|
export interface DistributionEntry {
|
||||||
|
|||||||
@ -98,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="main term" *ngIf="this.replayData.mean_error">
|
<div class="main term mb-2" *ngIf="this.replayData.mean_error">
|
||||||
<h1># nerd stats</h1>
|
<h1># nerd stats</h1>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -150,6 +150,36 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="main term mb-2" *ngFor="let chart of this.replayData.charts">
|
||||||
|
<h1>
|
||||||
|
# {{ chart.title }}
|
||||||
|
</h1>
|
||||||
|
<table class="mb-4">
|
||||||
|
<thead>
|
||||||
|
<th></th>
|
||||||
|
<th>
|
||||||
|
value
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
adjusted value (no outliers)
|
||||||
|
</th>
|
||||||
|
</thead>
|
||||||
|
<tr *ngFor="let entry of chart.table">
|
||||||
|
<td>{{ entry.first }}</td>
|
||||||
|
<td class="text-center">{{ entry.second }}</td>
|
||||||
|
<td class="text-center">{{ entry.third }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<canvas baseChart
|
||||||
|
[data]="this.buildChartData(chart)"
|
||||||
|
[options]="barChartOptions"
|
||||||
|
[plugins]="barChartPlugins"
|
||||||
|
[legend]="false"
|
||||||
|
[type]="'bar'"
|
||||||
|
class="chart">
|
||||||
|
</canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="main term" *ngIf="this.replayData.error_distribution && Object.keys(this.replayData.error_distribution).length > 0">
|
<div class="main term" *ngIf="this.replayData.error_distribution && Object.keys(this.replayData.error_distribution).length > 0">
|
||||||
<h1># hit distribution</h1>
|
<h1># hit distribution</h1>
|
||||||
<canvas baseChart
|
<canvas baseChart
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||||
import {ChartConfiguration} from 'chart.js';
|
import {ChartConfiguration, ChartData, DefaultDataPoint} from 'chart.js';
|
||||||
import {BaseChartDirective, NgChartsModule} from 'ng2-charts';
|
import {BaseChartDirective, NgChartsModule} from 'ng2-charts';
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {DecimalPipe, JsonPipe, NgForOf, NgIf, NgOptimizedImage} from "@angular/common";
|
import {DecimalPipe, JsonPipe, NgForOf, NgIf, NgOptimizedImage} from "@angular/common";
|
||||||
import {ActivatedRoute, RouterLink} from "@angular/router";
|
import {ActivatedRoute, RouterLink} from "@angular/router";
|
||||||
import {catchError, throwError} from "rxjs";
|
import {catchError, throwError} from "rxjs";
|
||||||
import {DistributionEntry, ReplayData} from "../replays";
|
import {DistributionEntry, ReplayData, ReplayDataChart} from "../replays";
|
||||||
import {calculateAccuracy} from "../format";
|
import {calculateAccuracy} from "../format";
|
||||||
import {Title} from "@angular/platform-browser";
|
import {Title} from "@angular/platform-browser";
|
||||||
import {OsuGradeComponent} from "../../corelib/components/osu-grade/osu-grade.component";
|
import {OsuGradeComponent} from "../../corelib/components/osu-grade/osu-grade.component";
|
||||||
@ -76,6 +76,34 @@ export class ViewScoreComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildChartData(chart: ReplayDataChart): ChartData<"bar", DefaultDataPoint<"bar">, any> {
|
||||||
|
const sortedData = chart.data.sort((a, b) => a.first - b.first);
|
||||||
|
|
||||||
|
const minFirst = Math.floor(sortedData[0].first / 2) * 2; // Round down to nearest even number
|
||||||
|
const maxFirst = Math.ceil(sortedData[sortedData.length - 1].first / 2) * 2; // Round up to nearest even number
|
||||||
|
const groupRanges = Array.from({length: (maxFirst - minFirst) / 2 + 1}, (_, i) => minFirst + i * 2);
|
||||||
|
|
||||||
|
const groupedData = groupRanges.map(rangeStart => {
|
||||||
|
const rangeEnd = rangeStart + 2;
|
||||||
|
const entriesInGroup = sortedData.filter(e => e.first >= rangeStart && e.first < rangeEnd);
|
||||||
|
const sumSecond = entriesInGroup.reduce((acc, curr) => acc + curr.second, 0);
|
||||||
|
return { first: rangeStart, second: sumSecond };
|
||||||
|
});
|
||||||
|
|
||||||
|
const labels = groupedData.map(({ first }) => `${first}ms to ${first + 2}ms`);
|
||||||
|
const datasets = [
|
||||||
|
{
|
||||||
|
data: groupedData.map(({ second }) => second),
|
||||||
|
label: chart.title + " (%)",
|
||||||
|
backgroundColor: 'rgba(0,255,41,0.66)',
|
||||||
|
borderRadius: 5
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return { labels, datasets };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
buildCircleguardUrl(): string {
|
buildCircleguardUrl(): string {
|
||||||
if(!this.replayData) {
|
if(!this.replayData) {
|
||||||
return "";
|
return "";
|
||||||
@ -147,7 +175,7 @@ export class ViewScoreComponent implements OnInit {
|
|||||||
return filledEntries.map(([key, _]) => {
|
return filledEntries.map(([key, _]) => {
|
||||||
const start = parseInt(String(key));
|
const start = parseInt(String(key));
|
||||||
const end = start + 2;
|
const end = start + 2;
|
||||||
return `${start} to ${end}ms`;
|
return `${start}ms to ${end}ms`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {Component, OnInit} from '@angular/core';
|
|||||||
import {SimilarReplay} from "../replays";
|
import {SimilarReplay} from "../replays";
|
||||||
import {Observable} from "rxjs";
|
import {Observable} from "rxjs";
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {LocalCacheService} from "../../corelib/local-cache.service";
|
import {LocalCacheService} from "../../corelib/service/local-cache.service";
|
||||||
import {ActivatedRoute, Router} from "@angular/router";
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
import {FilterManagerService} from "../filter-manager.service";
|
import {FilterManagerService} from "../filter-manager.service";
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
|
|||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {SuspiciousScore} from "../replays";
|
import {SuspiciousScore} from "../replays";
|
||||||
import {Observable, Subscription, timer} from 'rxjs';
|
import {Observable, Subscription, timer} from 'rxjs';
|
||||||
import {LocalCacheService} from "../../corelib/local-cache.service";
|
import {LocalCacheService} from "../../corelib/service/local-cache.service";
|
||||||
import {ActivatedRoute, Router} from "@angular/router";
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
import {FilterManagerService} from "../filter-manager.service";
|
import {FilterManagerService} from "../filter-manager.service";
|
||||||
|
|
||||||
|
|||||||
17
nise-frontend/src/corelib/nise-http.interceptor.ts
Normal file
17
nise-frontend/src/corelib/nise-http.interceptor.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NiseHttpInterceptor implements HttpInterceptor {
|
||||||
|
|
||||||
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
const modifiedReq = req.clone({
|
||||||
|
headers: req.headers.set('X-NISE-API', '20240218'),
|
||||||
|
withCredentials: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return next.handle(modifiedReq);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {HttpBackend, HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
|
|
||||||
interface UserInfo {
|
interface UserInfo {
|
||||||
@ -12,15 +12,12 @@ interface UserInfo {
|
|||||||
})
|
})
|
||||||
export class UserService {
|
export class UserService {
|
||||||
|
|
||||||
private httpClient: HttpClient;
|
|
||||||
|
|
||||||
currentUser: UserInfo | null = null;
|
currentUser: UserInfo | null = null;
|
||||||
|
|
||||||
loginCallback: () => void = () => {};
|
loginCallback: () => void = () => {};
|
||||||
logoutCallback: () => void = () => {};
|
logoutCallback: () => void = () => {};
|
||||||
|
|
||||||
constructor(private httpBackend: HttpBackend) {
|
constructor(private httpClient: HttpClient) {
|
||||||
this.httpClient = new HttpClient(httpBackend);
|
|
||||||
this.currentUser = this.loadCurrentUserFromLocalStorage();
|
this.currentUser = this.loadCurrentUserFromLocalStorage();
|
||||||
this.updateUser()
|
this.updateUser()
|
||||||
.catch(reason => console.debug(reason));
|
.catch(reason => console.debug(reason));
|
||||||
@ -40,7 +37,7 @@ export class UserService {
|
|||||||
|
|
||||||
public updateUser(): Promise<any> {
|
public updateUser(): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.httpClient.get<UserInfo>(`${environment.apiUrl}/auth`, {withCredentials: true})
|
this.httpClient.get<UserInfo>(`${environment.apiUrl}/auth`)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (user) => {
|
next: (user) => {
|
||||||
this.currentUser = user;
|
this.currentUser = user;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user