Ability for visitors to add users to queue and processing with live progress
This commit is contained in:
parent
45b4334011
commit
63be57dfc8
@ -17,7 +17,7 @@ import org.jooq.Identity
|
|||||||
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.Row5
|
import org.jooq.Row7
|
||||||
import org.jooq.Schema
|
import org.jooq.Schema
|
||||||
import org.jooq.SelectField
|
import org.jooq.SelectField
|
||||||
import org.jooq.Table
|
import org.jooq.Table
|
||||||
@ -88,6 +88,16 @@ open class UpdateUserQueue(
|
|||||||
*/
|
*/
|
||||||
val PROCESSED_AT: TableField<UpdateUserQueueRecord, LocalDateTime?> = createField(DSL.name("processed_at"), SQLDataType.LOCALDATETIME(6), this, "")
|
val PROCESSED_AT: TableField<UpdateUserQueueRecord, LocalDateTime?> = createField(DSL.name("processed_at"), SQLDataType.LOCALDATETIME(6), this, "")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The column <code>public.update_user_queue.progress_current</code>.
|
||||||
|
*/
|
||||||
|
val PROGRESS_CURRENT: TableField<UpdateUserQueueRecord, Int?> = createField(DSL.name("progress_current"), SQLDataType.INTEGER, this, "")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The column <code>public.update_user_queue.progress_total</code>.
|
||||||
|
*/
|
||||||
|
val PROGRESS_TOTAL: TableField<UpdateUserQueueRecord, Int?> = createField(DSL.name("progress_total"), SQLDataType.INTEGER, this, "")
|
||||||
|
|
||||||
private constructor(alias: Name, aliased: Table<UpdateUserQueueRecord>?): this(alias, null, null, aliased, null)
|
private constructor(alias: Name, aliased: Table<UpdateUserQueueRecord>?): this(alias, null, null, aliased, null)
|
||||||
private constructor(alias: Name, aliased: Table<UpdateUserQueueRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, aliased, parameters)
|
private constructor(alias: Name, aliased: Table<UpdateUserQueueRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, aliased, parameters)
|
||||||
|
|
||||||
@ -130,18 +140,18 @@ open class UpdateUserQueue(
|
|||||||
override fun rename(name: Table<*>): UpdateUserQueue = UpdateUserQueue(name.getQualifiedName(), null)
|
override fun rename(name: Table<*>): UpdateUserQueue = UpdateUserQueue(name.getQualifiedName(), null)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Row5 type methods
|
// Row7 type methods
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
override fun fieldsRow(): Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?> = super.fieldsRow() as Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?>
|
override fun fieldsRow(): Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?> = super.fieldsRow() as Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience mapping calling {@link SelectField#convertFrom(Function)}.
|
* Convenience mapping calling {@link SelectField#convertFrom(Function)}.
|
||||||
*/
|
*/
|
||||||
fun <U> mapping(from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?) -> U): SelectField<U> = convertFrom(Records.mapping(from))
|
fun <U> mapping(from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?) -> 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: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?) -> U): SelectField<U> = convertFrom(toType, Records.mapping(from))
|
fun <U> mapping(toType: Class<U>, from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?) -> 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.Record5
|
import org.jooq.Record7
|
||||||
import org.jooq.Row5
|
import org.jooq.Row7
|
||||||
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 UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<UpdateUserQueueRecord>(UpdateUserQueue.UPDATE_USER_QUEUE), Record5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?> {
|
open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<UpdateUserQueueRecord>(UpdateUserQueue.UPDATE_USER_QUEUE), Record7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?> {
|
||||||
|
|
||||||
open var id: Int?
|
open var id: Int?
|
||||||
set(value): Unit = set(0, value)
|
set(value): Unit = set(0, value)
|
||||||
@ -41,6 +41,14 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<Upd
|
|||||||
set(value): Unit = set(4, value)
|
set(value): Unit = set(4, value)
|
||||||
get(): LocalDateTime? = get(4) as LocalDateTime?
|
get(): LocalDateTime? = get(4) as LocalDateTime?
|
||||||
|
|
||||||
|
open var progressCurrent: Int?
|
||||||
|
set(value): Unit = set(5, value)
|
||||||
|
get(): Int? = get(5) as Int?
|
||||||
|
|
||||||
|
open var progressTotal: Int?
|
||||||
|
set(value): Unit = set(6, value)
|
||||||
|
get(): Int? = get(6) as Int?
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Primary key information
|
// Primary key information
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@ -48,26 +56,32 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<Upd
|
|||||||
override fun key(): Record1<Int?> = super.key() as Record1<Int?>
|
override fun key(): Record1<Int?> = super.key() as Record1<Int?>
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Record5 type implementation
|
// Record7 type implementation
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
override fun fieldsRow(): Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?> = super.fieldsRow() as Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?>
|
override fun fieldsRow(): Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?> = super.fieldsRow() as Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?>
|
||||||
override fun valuesRow(): Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?> = super.valuesRow() as Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?>
|
override fun valuesRow(): Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?> = super.valuesRow() as Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?>
|
||||||
override fun field1(): Field<Int?> = UpdateUserQueue.UPDATE_USER_QUEUE.ID
|
override fun field1(): Field<Int?> = UpdateUserQueue.UPDATE_USER_QUEUE.ID
|
||||||
override fun field2(): Field<Long?> = UpdateUserQueue.UPDATE_USER_QUEUE.USER_ID
|
override fun field2(): Field<Long?> = UpdateUserQueue.UPDATE_USER_QUEUE.USER_ID
|
||||||
override fun field3(): Field<Boolean?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED
|
override fun field3(): Field<Boolean?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED
|
||||||
override fun field4(): Field<LocalDateTime?> = UpdateUserQueue.UPDATE_USER_QUEUE.CREATED_AT
|
override fun field4(): Field<LocalDateTime?> = UpdateUserQueue.UPDATE_USER_QUEUE.CREATED_AT
|
||||||
override fun field5(): Field<LocalDateTime?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED_AT
|
override fun field5(): Field<LocalDateTime?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED_AT
|
||||||
|
override fun field6(): Field<Int?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROGRESS_CURRENT
|
||||||
|
override fun field7(): Field<Int?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROGRESS_TOTAL
|
||||||
override fun component1(): Int? = id
|
override fun component1(): Int? = id
|
||||||
override fun component2(): Long = userId
|
override fun component2(): Long = userId
|
||||||
override fun component3(): Boolean? = processed
|
override fun component3(): Boolean? = processed
|
||||||
override fun component4(): LocalDateTime? = createdAt
|
override fun component4(): LocalDateTime? = createdAt
|
||||||
override fun component5(): LocalDateTime? = processedAt
|
override fun component5(): LocalDateTime? = processedAt
|
||||||
|
override fun component6(): Int? = progressCurrent
|
||||||
|
override fun component7(): Int? = progressTotal
|
||||||
override fun value1(): Int? = id
|
override fun value1(): Int? = id
|
||||||
override fun value2(): Long = userId
|
override fun value2(): Long = userId
|
||||||
override fun value3(): Boolean? = processed
|
override fun value3(): Boolean? = processed
|
||||||
override fun value4(): LocalDateTime? = createdAt
|
override fun value4(): LocalDateTime? = createdAt
|
||||||
override fun value5(): LocalDateTime? = processedAt
|
override fun value5(): LocalDateTime? = processedAt
|
||||||
|
override fun value6(): Int? = progressCurrent
|
||||||
|
override fun value7(): Int? = progressTotal
|
||||||
|
|
||||||
override fun value1(value: Int?): UpdateUserQueueRecord {
|
override fun value1(value: Int?): UpdateUserQueueRecord {
|
||||||
set(0, value)
|
set(0, value)
|
||||||
@ -94,24 +108,38 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<Upd
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun values(value1: Int?, value2: Long?, value3: Boolean?, value4: LocalDateTime?, value5: LocalDateTime?): UpdateUserQueueRecord {
|
override fun value6(value: Int?): UpdateUserQueueRecord {
|
||||||
|
set(5, value)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun value7(value: Int?): UpdateUserQueueRecord {
|
||||||
|
set(6, value)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun values(value1: Int?, value2: Long?, value3: Boolean?, value4: LocalDateTime?, value5: LocalDateTime?, value6: Int?, value7: Int?): UpdateUserQueueRecord {
|
||||||
this.value1(value1)
|
this.value1(value1)
|
||||||
this.value2(value2)
|
this.value2(value2)
|
||||||
this.value3(value3)
|
this.value3(value3)
|
||||||
this.value4(value4)
|
this.value4(value4)
|
||||||
this.value5(value5)
|
this.value5(value5)
|
||||||
|
this.value6(value6)
|
||||||
|
this.value7(value7)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a detached, initialised UpdateUserQueueRecord
|
* Create a detached, initialised UpdateUserQueueRecord
|
||||||
*/
|
*/
|
||||||
constructor(id: Int? = null, userId: Long, processed: Boolean? = null, createdAt: LocalDateTime? = null, processedAt: LocalDateTime? = null): this() {
|
constructor(id: Int? = null, userId: Long, processed: Boolean? = null, createdAt: LocalDateTime? = null, processedAt: LocalDateTime? = null, progressCurrent: Int? = null, progressTotal: Int? = null): this() {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
this.processed = processed
|
this.processed = processed
|
||||||
this.createdAt = createdAt
|
this.createdAt = createdAt
|
||||||
this.processedAt = processedAt
|
this.processedAt = processedAt
|
||||||
|
this.progressCurrent = progressCurrent
|
||||||
|
this.progressTotal = progressTotal
|
||||||
resetChangedOnNotNull()
|
resetChangedOnNotNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
package com.nisemoe.nise
|
package com.nisemoe.nise
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
data class UserQueueDetails(
|
||||||
|
val isProcessing: Boolean,
|
||||||
|
val lastCompletedUpdate: LocalDateTime?,
|
||||||
|
|
||||||
|
val progressCurrent: Int?,
|
||||||
|
val progressTotal: Int?,
|
||||||
|
)
|
||||||
|
|
||||||
data class UserDetails(
|
data class UserDetails(
|
||||||
val user_id: Long,
|
val user_id: Long,
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.nisemoe.nise.config
|
||||||
|
|
||||||
|
import jakarta.servlet.*
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
|
||||||
|
class CustomCorsFilter : Filter {
|
||||||
|
|
||||||
|
@Value("\${ORIGIN:http://localhost:4200}")
|
||||||
|
private lateinit var origin: String
|
||||||
|
|
||||||
|
override fun init(filterConfig: FilterConfig) {
|
||||||
|
// We don't really need to do anything special here.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, filterChain: FilterChain) {
|
||||||
|
val response = servletResponse as HttpServletResponse
|
||||||
|
response.setHeader("Access-Control-Allow-Origin", origin)
|
||||||
|
response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS,PATCH")
|
||||||
|
response.setHeader(
|
||||||
|
"Access-Control-Allow-Headers",
|
||||||
|
"Access-Control-Allow-Headers, Origin,Accept, X-NISE-API, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"
|
||||||
|
)
|
||||||
|
response.setHeader("Access-Control-Allow-Credentials", "true")
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun destroy() {
|
||||||
|
// We don't really need to do anything special here.
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ import org.springframework.security.web.authentication.logout.LogoutSuccessHandl
|
|||||||
import org.springframework.session.web.http.CookieSerializer
|
import org.springframework.session.web.http.CookieSerializer
|
||||||
import org.springframework.session.web.http.DefaultCookieSerializer
|
import org.springframework.session.web.http.DefaultCookieSerializer
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
import org.springframework.web.filter.CorsFilter
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
@ -58,10 +59,16 @@ class SecurityConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun customCorsFilter(): CustomCorsFilter {
|
||||||
|
return CustomCorsFilter()
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun filterChain(http: HttpSecurity): SecurityFilterChain? {
|
fun filterChain(http: HttpSecurity): SecurityFilterChain? {
|
||||||
http
|
http
|
||||||
.csrf { csrf -> csrf.disable() }
|
.csrf { csrf -> csrf.disable() }
|
||||||
|
.addFilterBefore(customCorsFilter(), CorsFilter::class.java)
|
||||||
.oauth2Login { oauthLogin ->
|
.oauth2Login { oauthLogin ->
|
||||||
oauthLogin.successHandler(CustomAuthenticationSuccessHandler())
|
oauthLogin.successHandler(CustomAuthenticationSuccessHandler())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
package com.nisemoe.nise.config
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry
|
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebMvc
|
|
||||||
class WebConfig: WebMvcConfigurer {
|
|
||||||
|
|
||||||
@Value("\${ORIGIN:http://localhost:4200}")
|
|
||||||
private lateinit var origin: String
|
|
||||||
|
|
||||||
override fun addCorsMappings(registry: CorsRegistry) {
|
|
||||||
registry.addMapping("/**")
|
|
||||||
.allowedOrigins(origin)
|
|
||||||
.allowCredentials(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -5,24 +5,45 @@ import com.nisemoe.nise.SimilarReplayEntry
|
|||||||
import com.nisemoe.nise.database.ScoreService
|
import com.nisemoe.nise.database.ScoreService
|
||||||
import com.nisemoe.nise.SuspiciousScoreEntry
|
import com.nisemoe.nise.SuspiciousScoreEntry
|
||||||
import com.nisemoe.nise.UserDetails
|
import com.nisemoe.nise.UserDetails
|
||||||
|
import com.nisemoe.nise.UserQueueDetails
|
||||||
import com.nisemoe.nise.database.UserService
|
import com.nisemoe.nise.database.UserService
|
||||||
|
import com.nisemoe.nise.service.UpdateUserQueueService
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
class UserDetailsController(
|
class UserDetailsController(
|
||||||
private val scoreService: ScoreService,
|
private val scoreService: ScoreService,
|
||||||
private val userService: UserService
|
private val userService: UserService,
|
||||||
|
private val userQueueService: UpdateUserQueueService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class UserDetailsResponse(
|
data class UserDetailsResponse(
|
||||||
val user_details: UserDetails,
|
val user_details: UserDetails,
|
||||||
|
val queue_details: UserQueueDetails,
|
||||||
val suspicious_scores: List<SuspiciousScoreEntry>,
|
val suspicious_scores: List<SuspiciousScoreEntry>,
|
||||||
val similar_replays: List<SimilarReplayEntry>
|
val similar_replays: List<SimilarReplayEntry>,
|
||||||
|
val total_scores: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class UserQueueRequest(
|
||||||
|
val userId: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
@PostMapping("user-queue")
|
||||||
|
fun addUserToQueue(@RequestBody request: UserQueueRequest): ResponseEntity<Unit> {
|
||||||
|
val inserted = this.userQueueService.insertUser(request.userId)
|
||||||
|
return if(inserted)
|
||||||
|
ResponseEntity.ok().build()
|
||||||
|
else
|
||||||
|
ResponseEntity.badRequest().build()
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("user-details/{userId}")
|
@GetMapping("user-details/{userId}")
|
||||||
fun getUserDetails(@PathVariable userId: String): ResponseEntity<UserDetailsResponse> {
|
fun getUserDetails(@PathVariable userId: String): ResponseEntity<UserDetailsResponse> {
|
||||||
val userDetails = this.userService.getUserDetails(username = userId)
|
val userDetails = this.userService.getUserDetails(username = userId)
|
||||||
@ -33,8 +54,10 @@ class UserDetailsController(
|
|||||||
|
|
||||||
val response = UserDetailsResponse(
|
val response = UserDetailsResponse(
|
||||||
user_details = userDetails,
|
user_details = userDetails,
|
||||||
|
queue_details = this.userQueueService.getUserQueueDetails(userDetails.user_id),
|
||||||
suspicious_scores = this.scoreService.getSuspiciousScores(suspiciousScoresCondition),
|
suspicious_scores = this.scoreService.getSuspiciousScores(suspiciousScoresCondition),
|
||||||
similar_replays = this.scoreService.getSimilarReplaysForUserId(userDetails.user_id)
|
similar_replays = this.scoreService.getSimilarReplaysForUserId(userDetails.user_id),
|
||||||
|
total_scores = this.userService.getTotalUserScores(userDetails.user_id)
|
||||||
)
|
)
|
||||||
return ResponseEntity.ok(response)
|
return ResponseEntity.ok(response)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
package com.nisemoe.nise.database
|
package com.nisemoe.nise.database
|
||||||
|
|
||||||
|
import com.nisemoe.generated.tables.records.UpdateUserQueueRecord
|
||||||
import com.nisemoe.generated.tables.records.UsersRecord
|
import com.nisemoe.generated.tables.records.UsersRecord
|
||||||
|
import com.nisemoe.generated.tables.references.SCORES
|
||||||
|
import com.nisemoe.generated.tables.references.UPDATE_USER_QUEUE
|
||||||
import com.nisemoe.generated.tables.references.USERS
|
import com.nisemoe.generated.tables.references.USERS
|
||||||
import com.nisemoe.nise.osu.OsuApi
|
|
||||||
import com.nisemoe.nise.osu.OsuApiModels
|
|
||||||
import com.nisemoe.nise.Format
|
import com.nisemoe.nise.Format
|
||||||
import com.nisemoe.nise.UserDetails
|
import com.nisemoe.nise.UserDetails
|
||||||
|
import com.nisemoe.nise.UserQueueDetails
|
||||||
|
import com.nisemoe.nise.osu.OsuApi
|
||||||
|
import com.nisemoe.nise.osu.OsuApiModels
|
||||||
import org.jooq.DSLContext
|
import org.jooq.DSLContext
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@ -36,6 +40,10 @@ class UserService(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTotalUserScores(userId: Long): Int {
|
||||||
|
return dslContext.fetchCount(SCORES, SCORES.USER_ID.eq(userId))
|
||||||
|
}
|
||||||
|
|
||||||
fun getUserDetails(username: String): UserDetails? {
|
fun getUserDetails(username: String): UserDetails? {
|
||||||
val user = dslContext.selectFrom(USERS)
|
val user = dslContext.selectFrom(USERS)
|
||||||
.where(USERS.USERNAME.equalIgnoreCase(username.lowercase()))
|
.where(USERS.USERNAME.equalIgnoreCase(username.lowercase()))
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import com.nisemoe.konata.Replay
|
|||||||
import com.nisemoe.konata.ReplaySetComparison
|
import com.nisemoe.konata.ReplaySetComparison
|
||||||
import com.nisemoe.konata.compareReplaySet
|
import com.nisemoe.konata.compareReplaySet
|
||||||
import com.nisemoe.nise.Format.Companion.fromJudgementType
|
import com.nisemoe.nise.Format.Companion.fromJudgementType
|
||||||
|
import com.nisemoe.nise.UserQueueDetails
|
||||||
import com.nisemoe.nise.database.ScoreService
|
import com.nisemoe.nise.database.ScoreService
|
||||||
import com.nisemoe.nise.database.UserService
|
import com.nisemoe.nise.database.UserService
|
||||||
import com.nisemoe.nise.integrations.CircleguardService
|
import com.nisemoe.nise.integrations.CircleguardService
|
||||||
@ -169,6 +170,16 @@ class ImportScores(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var current = 0
|
||||||
|
|
||||||
|
val lastCompletedUpdate = dslContext.select(UPDATE_USER_QUEUE.PROCESSED_AT)
|
||||||
|
.from(UPDATE_USER_QUEUE)
|
||||||
|
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
|
||||||
|
.and(UPDATE_USER_QUEUE.PROCESSED.isTrue)
|
||||||
|
.orderBy(UPDATE_USER_QUEUE.PROCESSED_AT.desc())
|
||||||
|
.limit(1)
|
||||||
|
.fetchOneInto(LocalDateTime::class.java)
|
||||||
|
|
||||||
for(topScore in topUserScores) {
|
for(topScore in topUserScores) {
|
||||||
if(topScore.beatmap != null && topScore.beatmapset != null) {
|
if(topScore.beatmap != null && topScore.beatmapset != null) {
|
||||||
val beatmapExists = dslContext.fetchExists(BEATMAPS, BEATMAPS.BEATMAP_ID.eq(topScore.beatmap.id))
|
val beatmapExists = dslContext.fetchExists(BEATMAPS, BEATMAPS.BEATMAP_ID.eq(topScore.beatmap.id))
|
||||||
@ -187,10 +198,34 @@ class ImportScores(
|
|||||||
this.statistics.beatmapsAddedToDatabase++
|
this.statistics.beatmapsAddedToDatabase++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the database
|
||||||
|
dslContext.update(UPDATE_USER_QUEUE)
|
||||||
|
.set(UPDATE_USER_QUEUE.PROGRESS_CURRENT, current)
|
||||||
|
.set(UPDATE_USER_QUEUE.PROGRESS_TOTAL, topUserScores.size)
|
||||||
|
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
|
||||||
|
.and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
val currentQueueDetails = UserQueueDetails(
|
||||||
|
isProcessing = true,
|
||||||
|
lastCompletedUpdate = lastCompletedUpdate,
|
||||||
|
progressCurrent = current,
|
||||||
|
progressTotal = topUserScores.size
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update the frontend
|
||||||
|
messagingTemplate.convertAndSend(
|
||||||
|
"/topic/live-user/${userId}",
|
||||||
|
currentQueueDetails
|
||||||
|
)
|
||||||
|
|
||||||
this.insertAndProcessNewScore(topScore.beatmap.id, topScore)
|
this.insertAndProcessNewScore(topScore.beatmap.id, topScore)
|
||||||
}
|
}
|
||||||
|
current += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the backend
|
||||||
this.updateUserQueueService.setUserAsProcessed(userId)
|
this.updateUserQueueService.setUserAsProcessed(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,53 @@
|
|||||||
package com.nisemoe.nise.service
|
package com.nisemoe.nise.service
|
||||||
|
|
||||||
|
import com.nisemoe.generated.tables.records.UpdateUserQueueRecord
|
||||||
import com.nisemoe.generated.tables.references.UPDATE_USER_QUEUE
|
import com.nisemoe.generated.tables.references.UPDATE_USER_QUEUE
|
||||||
|
import com.nisemoe.nise.UserQueueDetails
|
||||||
import org.jooq.DSLContext
|
import org.jooq.DSLContext
|
||||||
|
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class UpdateUserQueueService(
|
class UpdateUserQueueService(
|
||||||
private val dslContext: DSLContext
|
private val dslContext: DSLContext,
|
||||||
|
private val messagingTemplate: SimpMessagingTemplate
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
fun getUserQueueDetails(userId: Long): UserQueueDetails {
|
||||||
|
val isProcessing = dslContext.fetchExists(
|
||||||
|
UPDATE_USER_QUEUE,
|
||||||
|
UPDATE_USER_QUEUE.USER_ID.eq(userId).and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
|
||||||
|
)
|
||||||
|
|
||||||
|
val lastCompletedUpdate = dslContext.select(UPDATE_USER_QUEUE.PROCESSED_AT)
|
||||||
|
.from(UPDATE_USER_QUEUE)
|
||||||
|
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
|
||||||
|
.and(UPDATE_USER_QUEUE.PROCESSED.isTrue)
|
||||||
|
.orderBy(UPDATE_USER_QUEUE.PROCESSED_AT.desc())
|
||||||
|
.limit(1)
|
||||||
|
.fetchOneInto(LocalDateTime::class.java)
|
||||||
|
|
||||||
|
val currentProgress = dslContext.select(
|
||||||
|
UPDATE_USER_QUEUE.PROGRESS_CURRENT,
|
||||||
|
UPDATE_USER_QUEUE.PROGRESS_TOTAL
|
||||||
|
)
|
||||||
|
.from(UPDATE_USER_QUEUE)
|
||||||
|
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
|
||||||
|
.and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
|
||||||
|
.orderBy(UPDATE_USER_QUEUE.PROCESSED_AT.desc())
|
||||||
|
.limit(1)
|
||||||
|
.fetchOneInto(UpdateUserQueueRecord::class.java)
|
||||||
|
|
||||||
|
return UserQueueDetails(
|
||||||
|
isProcessing,
|
||||||
|
lastCompletedUpdate,
|
||||||
|
currentProgress?.progressCurrent,
|
||||||
|
currentProgress?.progressTotal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getQueue(): List<Long> {
|
fun getQueue(): List<Long> {
|
||||||
return dslContext.select(UPDATE_USER_QUEUE.USER_ID)
|
return dslContext.select(UPDATE_USER_QUEUE.USER_ID)
|
||||||
.from(UPDATE_USER_QUEUE)
|
.from(UPDATE_USER_QUEUE)
|
||||||
@ -18,17 +56,23 @@ class UpdateUserQueueService(
|
|||||||
.fetchInto(Long::class.java)
|
.fetchInto(Long::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insertUser(userId: Long) {
|
/**
|
||||||
|
* Inserts a user into the update queue if they are not already in the queue.
|
||||||
|
* @return true if the user was inserted, false if the user was already in the queue
|
||||||
|
*/
|
||||||
|
fun insertUser(userId: Long): Boolean {
|
||||||
val exists = dslContext.fetchExists(UPDATE_USER_QUEUE,
|
val exists = dslContext.fetchExists(UPDATE_USER_QUEUE,
|
||||||
UPDATE_USER_QUEUE.USER_ID.eq(userId),
|
UPDATE_USER_QUEUE.USER_ID.eq(userId),
|
||||||
UPDATE_USER_QUEUE.PROCESSED.isFalse
|
UPDATE_USER_QUEUE.PROCESSED.isFalse
|
||||||
)
|
)
|
||||||
if (exists)
|
if (exists)
|
||||||
return
|
return false
|
||||||
|
|
||||||
dslContext.insertInto(UPDATE_USER_QUEUE)
|
val insertedRows = dslContext.insertInto(UPDATE_USER_QUEUE)
|
||||||
.set(UPDATE_USER_QUEUE.USER_ID, userId)
|
.set(UPDATE_USER_QUEUE.USER_ID, userId)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
|
return insertedRows == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setUserAsProcessed(userId: Long) {
|
fun setUserAsProcessed(userId: Long) {
|
||||||
@ -38,7 +82,12 @@ class UpdateUserQueueService(
|
|||||||
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
|
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
|
||||||
.and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
|
.and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
|
// Notify the user that their queue has been processed with fresh info
|
||||||
|
messagingTemplate.convertAndSend(
|
||||||
|
"/topic/live-user/${userId}",
|
||||||
|
this.getUserQueueDetails(userId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE public.update_user_queue
|
||||||
|
ADD COLUMN progress_current int;
|
||||||
|
|
||||||
|
ALTER TABLE public.update_user_queue
|
||||||
|
ADD COLUMN progress_total int;
|
||||||
@ -11,3 +11,11 @@ export interface UserDetails {
|
|||||||
|
|
||||||
suspicious_scores?: number;
|
suspicious_scores?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserQueueDetails {
|
||||||
|
isProcessing: boolean;
|
||||||
|
lastCompletedUpdate: string | null;
|
||||||
|
|
||||||
|
progressCurrent: number | null;
|
||||||
|
progressTotal: number | null;
|
||||||
|
}
|
||||||
|
|||||||
@ -40,6 +40,24 @@
|
|||||||
<li *ngIf="this.userInfo.user_details.playcount">Playcount: {{ this.userInfo.user_details.playcount | number: '1.0-1' }}</li>
|
<li *ngIf="this.userInfo.user_details.playcount">Playcount: {{ this.userInfo.user_details.playcount | number: '1.0-1' }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!this.userInfo.queue_details.isProcessing; else updateProgress">
|
||||||
|
<a (click)="this.addUserToQueue()" class="btn btn-success pointer" target="_blank">
|
||||||
|
update user scores now!
|
||||||
|
</a> <span style="margin-left: 4px">|</span>
|
||||||
|
last update: {{ this.userInfo.queue_details.lastCompletedUpdate ? this.userInfo.queue_details.lastCompletedUpdate : 'never'}}
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #updateProgress>
|
||||||
|
<div class="progress">
|
||||||
|
<span class="btn-warning">updating now!</span> <span style="margin-left: 4px">|</span> progress: {{ this.userInfo.queue_details.progressCurrent ? this.userInfo.queue_details.progressCurrent : "?" }}/{{ this.userInfo.queue_details.progressTotal ? this.userInfo.queue_details.progressTotal : "?" }}
|
||||||
|
<ng-container *ngIf="!this.userInfo.queue_details.progressTotal && !this.userInfo.queue_details.progressCurrent; else loading">
|
||||||
|
<span style="font-weight: bold">(in queue)</span>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #loading>
|
||||||
|
<app-cute-loading></app-cute-loading>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4>
|
<h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4>
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
|
|||||||
@ -1,18 +1,23 @@
|
|||||||
import {Component, OnChanges, OnInit} from '@angular/core';
|
import {Component, OnChanges, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {SimilarReplay, SuspiciousScore} from "../replays";
|
import {SimilarReplay, SuspiciousScore} from "../replays";
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {catchError, EMPTY, finalize, Observable} from "rxjs";
|
import {catchError, EMPTY, finalize, Observable, Subscription} from "rxjs";
|
||||||
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 {UserDetails} from "../userDetails";
|
import {UserDetails, UserQueueDetails} from "../userDetails";
|
||||||
import {countryCodeToFlag, formatDuration} from "../format";
|
import {countryCodeToFlag, formatDuration} from "../format";
|
||||||
import {Title} from "@angular/platform-browser";
|
import {Title} from "@angular/platform-browser";
|
||||||
|
import {RxStompService} from "../../corelib/stomp/stomp.service";
|
||||||
|
import {Message} from "@stomp/stompjs/esm6";
|
||||||
|
import {CuteLoadingComponent} from "../../corelib/components/cute-loading/cute-loading.component";
|
||||||
|
|
||||||
interface UserInfo {
|
interface UserInfo {
|
||||||
user_details: UserDetails;
|
user_details: UserDetails;
|
||||||
|
queue_details: UserQueueDetails;
|
||||||
suspicious_scores: SuspiciousScore[];
|
suspicious_scores: SuspiciousScore[];
|
||||||
similar_replays: SimilarReplay[];
|
similar_replays: SimilarReplay[];
|
||||||
|
total_scores: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -24,22 +29,26 @@ interface UserInfo {
|
|||||||
NgForOf,
|
NgForOf,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
NgIf,
|
NgIf,
|
||||||
NgOptimizedImage
|
NgOptimizedImage,
|
||||||
|
CuteLoadingComponent
|
||||||
],
|
],
|
||||||
templateUrl: './view-user.component.html',
|
templateUrl: './view-user.component.html',
|
||||||
styleUrl: './view-user.component.css'
|
styleUrl: './view-user.component.css'
|
||||||
})
|
})
|
||||||
export class ViewUserComponent implements OnInit, OnChanges {
|
export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
notFound = false;
|
notFound = false;
|
||||||
userId: string | null = null;
|
userId: string | null = null;
|
||||||
userInfo: UserInfo | null = null;
|
userInfo: UserInfo | null = null;
|
||||||
|
|
||||||
|
liveUserSub: Subscription | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private title: Title
|
private title: Title,
|
||||||
|
private rxStompService: RxStompService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
getUserInfo(): Observable<UserInfo> {
|
getUserInfo(): Observable<UserInfo> {
|
||||||
@ -74,14 +83,40 @@ export class ViewUserComponent implements OnInit, OnChanges {
|
|||||||
this.notFound = false;
|
this.notFound = false;
|
||||||
this.userInfo = response;
|
this.userInfo = response;
|
||||||
this.title.setTitle(`${this.userInfo.user_details.username}`);
|
this.title.setTitle(`${this.userInfo.user_details.username}`);
|
||||||
|
this.subscribeToUser();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly formatDuration = formatDuration;
|
ngOnDestroy(): void {
|
||||||
|
this.liveUserSub?.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
addUserToQueue(): void {
|
||||||
|
const body = {
|
||||||
|
userId: this.userInfo?.user_details.user_id
|
||||||
|
}
|
||||||
|
this.httpClient.post(`${environment.apiUrl}/user-queue`, body).subscribe(
|
||||||
|
() => {
|
||||||
|
this.userInfo!.queue_details.isProcessing = true;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
alert("Failed to add user to queue.");
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToUser(): void {
|
||||||
|
this.liveUserSub = this.rxStompService
|
||||||
|
.watch(`/topic/live-user/${this.userInfo?.user_details.user_id}`)
|
||||||
|
.subscribe((message: Message) => {
|
||||||
|
this.userInfo!.queue_details = JSON.parse(message.body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly formatDuration = formatDuration;
|
||||||
protected readonly countryCodeToFlag = countryCodeToFlag;
|
protected readonly countryCodeToFlag = countryCodeToFlag;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -141,6 +141,27 @@ a.btn {
|
|||||||
border: 1px dotted #b3b8c3;
|
border: 1px dotted #b3b8c3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.btn-success {
|
||||||
|
color: #5eaf78;
|
||||||
|
border: 1px dotted #5eaf78;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-success:hover {
|
||||||
|
color: #2af171;
|
||||||
|
border: 1px dotted #2af171;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
padding: 2px;
|
||||||
|
color: #f1c40f;
|
||||||
|
border: 1px dotted #f1c40f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning:hover {
|
||||||
|
color: #f1c40f;
|
||||||
|
border: 1px dotted #f1c40f;
|
||||||
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
{{ dots }}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cute-loading',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './cute-loading.component.html'
|
||||||
|
})
|
||||||
|
export class CuteLoadingComponent implements OnInit {
|
||||||
|
dots = '';
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
setInterval(() => {
|
||||||
|
this.dots += '.';
|
||||||
|
if (this.dots.length > 3) {
|
||||||
|
this.dots = '.';
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user