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.Record
|
||||
import org.jooq.Records
|
||||
import org.jooq.Row16
|
||||
import org.jooq.Row17
|
||||
import org.jooq.Schema
|
||||
import org.jooq.SelectField
|
||||
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, "")
|
||||
|
||||
/**
|
||||
* 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>?, 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)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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)}.
|
||||
*/
|
||||
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,
|
||||
* 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.Record1
|
||||
import org.jooq.Record16
|
||||
import org.jooq.Row16
|
||||
import org.jooq.Record17
|
||||
import org.jooq.Row17
|
||||
import org.jooq.impl.UpdatableRecordImpl
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ import org.jooq.impl.UpdatableRecordImpl
|
||||
* This class is generated by jOOQ.
|
||||
*/
|
||||
@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?
|
||||
set(value): Unit = set(0, value)
|
||||
@ -85,6 +85,12 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
set(value): Unit = set(15, value)
|
||||
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
|
||||
// -------------------------------------------------------------------------
|
||||
@ -92,11 +98,11 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
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 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 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(): 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 field2(): Field<String?> = Users.USERS.USERNAME
|
||||
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 field15(): Field<Long?> = Users.USERS.COUNT_50
|
||||
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 component2(): String? = username
|
||||
override fun component3(): LocalDateTime? = joinDate
|
||||
@ -129,6 +136,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
override fun component14(): Long? = count_300
|
||||
override fun component15(): Long? = count_50
|
||||
override fun component16(): LocalDateTime? = sysLastUpdate
|
||||
override fun component17(): Boolean? = isAdmin
|
||||
override fun value1(): Long? = userId
|
||||
override fun value2(): String? = username
|
||||
override fun value3(): LocalDateTime? = joinDate
|
||||
@ -145,6 +153,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
override fun value14(): Long? = count_300
|
||||
override fun value15(): Long? = count_50
|
||||
override fun value16(): LocalDateTime? = sysLastUpdate
|
||||
override fun value17(): Boolean? = isAdmin
|
||||
|
||||
override fun value1(value: Long?): UsersRecord {
|
||||
set(0, value)
|
||||
@ -226,7 +235,12 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
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.value2(value2)
|
||||
this.value3(value3)
|
||||
@ -243,13 +257,14 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
this.value14(value14)
|
||||
this.value15(value15)
|
||||
this.value16(value16)
|
||||
this.value17(value17)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.username = username
|
||||
this.joinDate = joinDate
|
||||
@ -266,6 +281,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
this.count_300 = count_300
|
||||
this.count_50 = count_50
|
||||
this.sysLastUpdate = sysLastUpdate
|
||||
this.isAdmin = isAdmin
|
||||
resetChangedOnNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +69,13 @@ data class ReplayPair(
|
||||
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(
|
||||
val replay_id: Long,
|
||||
val user_id: Int,
|
||||
@ -121,7 +128,8 @@ data class ReplayData(
|
||||
val count_50: Int,
|
||||
val count_miss: Int,
|
||||
|
||||
val error_distribution: Map<Int, DistributionEntry>
|
||||
val error_distribution: Map<Int, DistributionEntry>,
|
||||
val charts: List<ReplayDataChart>
|
||||
) {
|
||||
|
||||
fun calculateAccuracy(): Double {
|
||||
|
||||
@ -4,13 +4,13 @@ import com.nisemoe.generated.tables.records.ScoresJudgementsRecord
|
||||
import com.nisemoe.generated.tables.references.*
|
||||
import com.nisemoe.nise.*
|
||||
import com.nisemoe.nise.osu.Mod
|
||||
import com.nisemoe.nise.service.AuthService
|
||||
import org.jooq.Condition
|
||||
import org.jooq.DSLContext
|
||||
import org.jooq.Record
|
||||
import org.jooq.Result
|
||||
import org.jooq.impl.DSL
|
||||
import org.jooq.impl.DSL.avg
|
||||
import org.springframework.cache.annotation.Cacheable
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.math.roundToInt
|
||||
@ -18,7 +18,8 @@ import kotlin.math.roundToInt
|
||||
@Service
|
||||
class ScoreService(
|
||||
private val dslContext: DSLContext,
|
||||
private val beatmapService: BeatmapService
|
||||
private val beatmapService: BeatmapService,
|
||||
private val authService: AuthService
|
||||
) {
|
||||
|
||||
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? {
|
||||
val result = dslContext.select(DSL.asterisk())
|
||||
.from(SCORES)
|
||||
@ -41,6 +111,7 @@ class ScoreService(
|
||||
val beatmapId = result.get(BEATMAPS.BEATMAP_ID, Int::class.java)
|
||||
val averageUR = beatmapService.getAverageUR(beatmapId = beatmapId, excludeReplayId = replayId)
|
||||
val hitDistribution = this.getHitDistribution(scoreId = result.get(SCORES.ID, Int::class.java))
|
||||
val charts = this.getCharts(result)
|
||||
|
||||
val replayData = ReplayData(
|
||||
replay_id = replayId,
|
||||
@ -80,6 +151,7 @@ class ScoreService(
|
||||
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_skewness = result.get(SCORES.ERROR_SKEWNESS, Double::class.java),
|
||||
charts = charts
|
||||
)
|
||||
this.loadComparableReplayData(replayData)
|
||||
return replayData
|
||||
|
||||
@ -1,18 +1,30 @@
|
||||
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.core.context.SecurityContextHolder
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class AuthService {
|
||||
class AuthService(
|
||||
private val dslContext: DSLContext
|
||||
) {
|
||||
|
||||
data class UserInfo(
|
||||
val userId: Int,
|
||||
val userId: Long,
|
||||
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 {
|
||||
val authentication = SecurityContextHolder.getContext().authentication
|
||||
return !(authentication == null || authentication is AnonymousAuthenticationToken || authentication.principal == null)
|
||||
@ -26,10 +38,9 @@ class AuthService {
|
||||
val userDetails = authentication.principal as DefaultOAuth2User
|
||||
|
||||
return UserInfo(
|
||||
userId = userDetails.attributes["id"] as Int,
|
||||
userId = (userDetails.attributes["id"] as Int).toLong(),
|
||||
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>
|
||||
<router-outlet></router-outlet>
|
||||
<div class="text-center version">
|
||||
v20240217
|
||||
v20240218
|
||||
</div>
|
||||
|
||||
@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
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 { ViewSimilarReplaysComponent } from './view-similar-replays/view-similar-replays.component';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
@ -12,6 +12,7 @@ import {FormsModule} from "@angular/forms";
|
||||
import {NgOptimizedImage} from "@angular/common";
|
||||
import {rxStompServiceFactory} from "../corelib/stomp/stomp.factory";
|
||||
import {RxStompService} from "../corelib/stomp/stomp.service";
|
||||
import {NiseHttpInterceptor} from "../corelib/nise-http.interceptor";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -32,7 +33,8 @@ import {RxStompService} from "../corelib/stomp/stomp.service";
|
||||
{
|
||||
provide: RxStompService,
|
||||
useFactory: rxStompServiceFactory,
|
||||
}
|
||||
},
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: NiseHttpInterceptor, multi: true }
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Observable, Subscription} from "rxjs";
|
||||
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 {Message} from "@stomp/stompjs/esm6";
|
||||
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 {
|
||||
replay_id: number;
|
||||
user_id: number;
|
||||
@ -51,6 +58,7 @@ export interface ReplayData {
|
||||
count_miss: number;
|
||||
|
||||
error_distribution: ErrorDistribution;
|
||||
charts: ReplayDataChart[];
|
||||
}
|
||||
|
||||
export interface DistributionEntry {
|
||||
|
||||
@ -98,7 +98,7 @@
|
||||
</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>
|
||||
<table>
|
||||
<thead>
|
||||
@ -150,6 +150,36 @@
|
||||
</table>
|
||||
</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">
|
||||
<h1># hit distribution</h1>
|
||||
<canvas baseChart
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {HttpClient} from "@angular/common/http";
|
||||
import {environment} from "../../environments/environment";
|
||||
import {DecimalPipe, JsonPipe, NgForOf, NgIf, NgOptimizedImage} from "@angular/common";
|
||||
import {ActivatedRoute, RouterLink} from "@angular/router";
|
||||
import {catchError, throwError} from "rxjs";
|
||||
import {DistributionEntry, ReplayData} from "../replays";
|
||||
import {DistributionEntry, ReplayData, ReplayDataChart} from "../replays";
|
||||
import {calculateAccuracy} from "../format";
|
||||
import {Title} from "@angular/platform-browser";
|
||||
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 {
|
||||
if(!this.replayData) {
|
||||
return "";
|
||||
@ -147,7 +175,7 @@ export class ViewScoreComponent implements OnInit {
|
||||
return filledEntries.map(([key, _]) => {
|
||||
const start = parseInt(String(key));
|
||||
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 {Observable} from "rxjs";
|
||||
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 {FilterManagerService} from "../filter-manager.service";
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {environment} from "../../environments/environment";
|
||||
import {SuspiciousScore} from "../replays";
|
||||
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 {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 {HttpBackend, HttpClient} from "@angular/common/http";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {environment} from "../../environments/environment";
|
||||
|
||||
interface UserInfo {
|
||||
@ -12,15 +12,12 @@ interface UserInfo {
|
||||
})
|
||||
export class UserService {
|
||||
|
||||
private httpClient: HttpClient;
|
||||
|
||||
currentUser: UserInfo | null = null;
|
||||
|
||||
loginCallback: () => void = () => {};
|
||||
logoutCallback: () => void = () => {};
|
||||
|
||||
constructor(private httpBackend: HttpBackend) {
|
||||
this.httpClient = new HttpClient(httpBackend);
|
||||
constructor(private httpClient: HttpClient) {
|
||||
this.currentUser = this.loadCurrentUserFromLocalStorage();
|
||||
this.updateUser()
|
||||
.catch(reason => console.debug(reason));
|
||||
@ -40,7 +37,7 @@ export class UserService {
|
||||
|
||||
public updateUser(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.httpClient.get<UserInfo>(`${environment.apiUrl}/auth`, {withCredentials: true})
|
||||
this.httpClient.get<UserInfo>(`${environment.apiUrl}/auth`)
|
||||
.subscribe({
|
||||
next: (user) => {
|
||||
this.currentUser = user;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user