diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/Beatmaps.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/Beatmaps.kt index 5b8d6cd..20fd807 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/Beatmaps.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/Beatmaps.kt @@ -8,7 +8,7 @@ import com.nisemoe.generated.Public import com.nisemoe.generated.keys.BEATMAPS_PKEY import com.nisemoe.generated.tables.records.BeatmapsRecord -import java.time.LocalDateTime +import java.time.OffsetDateTime import java.util.function.Function import org.jooq.Field @@ -105,7 +105,7 @@ open class Beatmaps( /** * The column public.beatmaps.sys_last_update. */ - val SYS_LAST_UPDATE: TableField = createField(DSL.name("sys_last_update"), SQLDataType.LOCALDATETIME(6), this, "") + val SYS_LAST_UPDATE: TableField = createField(DSL.name("sys_last_update"), SQLDataType.TIMESTAMPWITHTIMEZONE(6).defaultValue(DSL.field(DSL.raw("CURRENT_TIMESTAMP"), SQLDataType.TIMESTAMPWITHTIMEZONE)), this, "") private constructor(alias: Name, aliased: Table?): this(alias, null, null, aliased, null) private constructor(alias: Name, aliased: Table?, parameters: Array?>?): this(alias, null, null, aliased, parameters) @@ -150,16 +150,16 @@ open class Beatmaps( // ------------------------------------------------------------------------- // Row9 type methods // ------------------------------------------------------------------------- - override fun fieldsRow(): Row9 = super.fieldsRow() as Row9 + override fun fieldsRow(): Row9 = super.fieldsRow() as Row9 /** * Convenience mapping calling {@link SelectField#convertFrom(Function)}. */ - fun mapping(from: (Int?, String?, Int?, String?, String?, Double?, String?, String?, LocalDateTime?) -> U): SelectField = convertFrom(Records.mapping(from)) + fun mapping(from: (Int?, String?, Int?, String?, String?, Double?, String?, String?, OffsetDateTime?) -> U): SelectField = convertFrom(Records.mapping(from)) /** * Convenience mapping calling {@link SelectField#convertFrom(Class, * Function)}. */ - fun mapping(toType: Class, from: (Int?, String?, Int?, String?, String?, Double?, String?, String?, LocalDateTime?) -> U): SelectField = convertFrom(toType, Records.mapping(from)) + fun mapping(toType: Class, from: (Int?, String?, Int?, String?, String?, Double?, String?, String?, OffsetDateTime?) -> U): SelectField = convertFrom(toType, Records.mapping(from)) } diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/UpdateUserQueue.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/UpdateUserQueue.kt index 56d0226..1ca5ca8 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/UpdateUserQueue.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/UpdateUserQueue.kt @@ -9,6 +9,7 @@ import com.nisemoe.generated.keys.UPDATE_USER_QUEUE_PKEY import com.nisemoe.generated.tables.records.UpdateUserQueueRecord import java.time.LocalDateTime +import java.time.OffsetDateTime import java.util.function.Function import org.jooq.Field @@ -86,7 +87,7 @@ open class UpdateUserQueue( /** * The column public.update_user_queue.processed_at. */ - val PROCESSED_AT: TableField = createField(DSL.name("processed_at"), SQLDataType.LOCALDATETIME(6), this, "") + val PROCESSED_AT: TableField = createField(DSL.name("processed_at"), SQLDataType.TIMESTAMPWITHTIMEZONE(6).defaultValue(DSL.field(DSL.raw("CURRENT_TIMESTAMP"), SQLDataType.TIMESTAMPWITHTIMEZONE)), this, "") /** * The column public.update_user_queue.progress_current. @@ -142,16 +143,16 @@ open class UpdateUserQueue( // ------------------------------------------------------------------------- // Row7 type methods // ------------------------------------------------------------------------- - override fun fieldsRow(): Row7 = super.fieldsRow() as Row7 + override fun fieldsRow(): Row7 = super.fieldsRow() as Row7 /** * Convenience mapping calling {@link SelectField#convertFrom(Function)}. */ - fun mapping(from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?) -> U): SelectField = convertFrom(Records.mapping(from)) + fun mapping(from: (Int?, Long?, Boolean?, LocalDateTime?, OffsetDateTime?, Int?, Int?) -> U): SelectField = convertFrom(Records.mapping(from)) /** * Convenience mapping calling {@link SelectField#convertFrom(Class, * Function)}. */ - fun mapping(toType: Class, from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?) -> U): SelectField = convertFrom(toType, Records.mapping(from)) + fun mapping(toType: Class, from: (Int?, Long?, Boolean?, LocalDateTime?, OffsetDateTime?, Int?, Int?) -> U): SelectField = convertFrom(toType, Records.mapping(from)) } diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/Users.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/Users.kt index 13d59d3..eb7b1ac 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/Users.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/Users.kt @@ -9,6 +9,7 @@ import com.nisemoe.generated.keys.USERS_PKEY import com.nisemoe.generated.tables.records.UsersRecord import java.time.LocalDateTime +import java.time.OffsetDateTime import java.util.function.Function import org.jooq.Field @@ -140,7 +141,7 @@ open class Users( /** * The column public.users.sys_last_update. */ - val SYS_LAST_UPDATE: TableField = createField(DSL.name("sys_last_update"), SQLDataType.LOCALDATETIME(6), this, "") + val SYS_LAST_UPDATE: TableField = createField(DSL.name("sys_last_update"), SQLDataType.TIMESTAMPWITHTIMEZONE(6).defaultValue(DSL.field(DSL.raw("CURRENT_TIMESTAMP"), SQLDataType.TIMESTAMPWITHTIMEZONE)), this, "") /** * The column public.users.is_admin. @@ -190,16 +191,16 @@ open class Users( // ------------------------------------------------------------------------- // Row17 type methods // ------------------------------------------------------------------------- - override fun fieldsRow(): Row17 = super.fieldsRow() as Row17 + override fun fieldsRow(): Row17 = super.fieldsRow() as Row17 /** * Convenience mapping calling {@link SelectField#convertFrom(Function)}. */ - fun mapping(from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?) -> U): SelectField = convertFrom(Records.mapping(from)) + fun mapping(from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?) -> U): SelectField = convertFrom(Records.mapping(from)) /** * Convenience mapping calling {@link SelectField#convertFrom(Class, * Function)}. */ - fun mapping(toType: Class, from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, LocalDateTime?, Boolean?) -> U): SelectField = convertFrom(toType, Records.mapping(from)) + fun mapping(toType: Class, from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?) -> U): SelectField = convertFrom(toType, Records.mapping(from)) } diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/BeatmapsRecord.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/BeatmapsRecord.kt index ffe00b8..eba9d25 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/BeatmapsRecord.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/BeatmapsRecord.kt @@ -6,7 +6,7 @@ package com.nisemoe.generated.tables.records import com.nisemoe.generated.tables.Beatmaps -import java.time.LocalDateTime +import java.time.OffsetDateTime import org.jooq.Field import org.jooq.Record1 @@ -19,7 +19,7 @@ import org.jooq.impl.UpdatableRecordImpl * This class is generated by jOOQ. */ @Suppress("UNCHECKED_CAST") -open class BeatmapsRecord private constructor() : UpdatableRecordImpl(Beatmaps.BEATMAPS), Record9 { +open class BeatmapsRecord private constructor() : UpdatableRecordImpl(Beatmaps.BEATMAPS), Record9 { open var beatmapId: Int? set(value): Unit = set(0, value) @@ -53,9 +53,9 @@ open class BeatmapsRecord private constructor() : UpdatableRecordImpl = super.fieldsRow() as Row9 - override fun valuesRow(): Row9 = super.valuesRow() as Row9 + override fun fieldsRow(): Row9 = super.fieldsRow() as Row9 + override fun valuesRow(): Row9 = super.valuesRow() as Row9 override fun field1(): Field = Beatmaps.BEATMAPS.BEATMAP_ID override fun field2(): Field = Beatmaps.BEATMAPS.ARTIST override fun field3(): Field = Beatmaps.BEATMAPS.BEATMAPSET_ID @@ -77,7 +77,7 @@ open class BeatmapsRecord private constructor() : UpdatableRecordImpl = Beatmaps.BEATMAPS.STAR_RATING override fun field7(): Field = Beatmaps.BEATMAPS.TITLE override fun field8(): Field = Beatmaps.BEATMAPS.VERSION - override fun field9(): Field = Beatmaps.BEATMAPS.SYS_LAST_UPDATE + override fun field9(): Field = Beatmaps.BEATMAPS.SYS_LAST_UPDATE override fun component1(): Int? = beatmapId override fun component2(): String? = artist override fun component3(): Int? = beatmapsetId @@ -86,7 +86,7 @@ open class BeatmapsRecord private constructor() : UpdatableRecordImpl(UpdateUserQueue.UPDATE_USER_QUEUE), Record7 { +open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl(UpdateUserQueue.UPDATE_USER_QUEUE), Record7 { open var id: Int? set(value): Unit = set(0, value) @@ -37,9 +38,9 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl = super.fieldsRow() as Row7 - override fun valuesRow(): Row7 = super.valuesRow() as Row7 + override fun fieldsRow(): Row7 = super.fieldsRow() as Row7 + override fun valuesRow(): Row7 = super.valuesRow() as Row7 override fun field1(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.ID override fun field2(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.USER_ID override fun field3(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED override fun field4(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.CREATED_AT - override fun field5(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED_AT + override fun field5(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED_AT override fun field6(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.PROGRESS_CURRENT override fun field7(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.PROGRESS_TOTAL override fun component1(): Int? = id override fun component2(): Long = userId override fun component3(): Boolean? = processed override fun component4(): LocalDateTime? = createdAt - override fun component5(): LocalDateTime? = processedAt + override fun component5(): OffsetDateTime? = processedAt override fun component6(): Int? = progressCurrent override fun component7(): Int? = progressTotal override fun value1(): Int? = id override fun value2(): Long = userId override fun value3(): Boolean? = processed override fun value4(): LocalDateTime? = createdAt - override fun value5(): LocalDateTime? = processedAt + override fun value5(): OffsetDateTime? = processedAt override fun value6(): Int? = progressCurrent override fun value7(): Int? = progressTotal @@ -103,7 +104,7 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl(Users.USERS), Record17 { +open class UsersRecord private constructor() : UpdatableRecordImpl(Users.USERS), Record17 { open var userId: Long? set(value): Unit = set(0, value) @@ -81,9 +82,9 @@ open class UsersRecord private constructor() : UpdatableRecordImpl( set(value): Unit = set(14, value) get(): Long? = get(14) as Long? - open var sysLastUpdate: LocalDateTime? + open var sysLastUpdate: OffsetDateTime? set(value): Unit = set(15, value) - get(): LocalDateTime? = get(15) as LocalDateTime? + get(): OffsetDateTime? = get(15) as OffsetDateTime? @Suppress("INAPPLICABLE_JVM_NAME") @set:JvmName("setIsAdmin") @@ -101,8 +102,8 @@ open class UsersRecord private constructor() : UpdatableRecordImpl( // Record17 type implementation // ------------------------------------------------------------------------- - override fun fieldsRow(): Row17 = super.fieldsRow() as Row17 - override fun valuesRow(): Row17 = super.valuesRow() as Row17 + override fun fieldsRow(): Row17 = super.fieldsRow() as Row17 + override fun valuesRow(): Row17 = super.valuesRow() as Row17 override fun field1(): Field = Users.USERS.USER_ID override fun field2(): Field = Users.USERS.USERNAME override fun field3(): Field = Users.USERS.JOIN_DATE @@ -118,7 +119,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl( override fun field13(): Field = Users.USERS.COUNT_100 override fun field14(): Field = Users.USERS.COUNT_300 override fun field15(): Field = Users.USERS.COUNT_50 - override fun field16(): Field = Users.USERS.SYS_LAST_UPDATE + override fun field16(): Field = Users.USERS.SYS_LAST_UPDATE override fun field17(): Field = Users.USERS.IS_ADMIN override fun component1(): Long? = userId override fun component2(): String? = username @@ -135,7 +136,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl( override fun component13(): Long? = count_100 override fun component14(): Long? = count_300 override fun component15(): Long? = count_50 - override fun component16(): LocalDateTime? = sysLastUpdate + override fun component16(): OffsetDateTime? = sysLastUpdate override fun component17(): Boolean? = isAdmin override fun value1(): Long? = userId override fun value2(): String? = username @@ -152,7 +153,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl( override fun value13(): Long? = count_100 override fun value14(): Long? = count_300 override fun value15(): Long? = count_50 - override fun value16(): LocalDateTime? = sysLastUpdate + override fun value16(): OffsetDateTime? = sysLastUpdate override fun value17(): Boolean? = isAdmin override fun value1(value: Long?): UsersRecord { @@ -230,7 +231,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl( return this } - override fun value16(value: LocalDateTime?): UsersRecord { + override fun value16(value: OffsetDateTime?): UsersRecord { set(15, value) return this } @@ -240,7 +241,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl( 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 { + 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: OffsetDateTime?, value17: Boolean?): UsersRecord { this.value1(value1) this.value2(value2) this.value3(value3) @@ -264,7 +265,7 @@ open class UsersRecord private constructor() : UpdatableRecordImpl( /** * 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, isAdmin: Boolean? = 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: OffsetDateTime? = null, isAdmin: Boolean? = null): this() { this.userId = userId this.username = username this.joinDate = joinDate diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/Models.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/Models.kt index da669ca..129d414 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/Models.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/Models.kt @@ -1,11 +1,12 @@ package com.nisemoe.nise import kotlinx.serialization.Serializable -import java.time.LocalDateTime +import java.time.OffsetDateTime data class UserQueueDetails( val isProcessing: Boolean, - val lastCompletedUpdate: LocalDateTime?, + val lastCompletedUpdate: OffsetDateTime?, + val canUpdate: Boolean, val progressCurrent: Int?, val progressTotal: Int?, diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/UserDetailsController.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/UserDetailsController.kt index eace9bc..cdf1942 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/UserDetailsController.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/UserDetailsController.kt @@ -15,6 +15,8 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset @RestController class UserDetailsController( @@ -23,6 +25,8 @@ class UserDetailsController( private val userQueueService: UpdateUserQueueService ) { + + data class UserDetailsResponse( val user_details: UserDetails, val queue_details: UserQueueDetails, @@ -37,6 +41,10 @@ class UserDetailsController( @PostMapping("user-queue") fun addUserToQueue(@RequestBody request: UserQueueRequest): ResponseEntity { + val userQueueDetails = this.userQueueService.getUserQueueDetails(request.userId) + if(!userQueueDetails.canUpdate) + return ResponseEntity.badRequest().build() + val inserted = this.userQueueService.insertUser(request.userId) return if(inserted) ResponseEntity.ok().build() diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserService.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserService.kt index 3e9d6ba..d36b79b 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserService.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserService.kt @@ -15,6 +15,7 @@ import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.time.LocalDateTime import java.time.OffsetDateTime +import java.time.ZoneOffset @Service class UserService( @@ -97,7 +98,7 @@ class UserService( .set(USERS.COUNTRY, apiUser.country?.code) .set(USERS.COUNTRY_RANK, apiUser.statistics?.country_rank) .set(USERS.PLAYCOUNT, apiUser.statistics?.play_count) - .set(USERS.SYS_LAST_UPDATE, LocalDateTime.now()) + .set(USERS.SYS_LAST_UPDATE, OffsetDateTime.now(ZoneOffset.UTC)) .where(USERS.USER_ID.eq(apiUser.id)) .execute() @@ -124,7 +125,7 @@ class UserService( .set(USERS.COUNTRY, apiUser.country?.code) .set(USERS.COUNTRY_RANK, apiUser.statistics?.country_rank) .set(USERS.PLAYCOUNT, apiUser.statistics?.play_count) - .set(USERS.SYS_LAST_UPDATE, LocalDateTime.now()) + .set(USERS.SYS_LAST_UPDATE, OffsetDateTime.now(ZoneOffset.UTC)) .onDuplicateKeyIgnore() .execute() diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportScores.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportScores.kt index ff42064..c26f9ec 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportScores.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportScores.kt @@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController import java.time.LocalDateTime import java.time.OffsetDateTime +import java.time.ZoneOffset @Service @RestController @@ -159,7 +160,7 @@ class ImportScores( Thread.sleep(this.sleepTimeInMs) if(topUserScores != null) { - val userExists = dslContext.fetchExists(USERS, USERS.USER_ID.eq(userId), USERS.SYS_LAST_UPDATE.greaterOrEqual(LocalDateTime.now().minusDays(UPDATE_USER_EVERY_DAYS))) + val userExists = dslContext.fetchExists(USERS, USERS.USER_ID.eq(userId), USERS.SYS_LAST_UPDATE.greaterOrEqual(OffsetDateTime.now(ZoneOffset.UTC).minusDays(UPDATE_USER_EVERY_DAYS))) if(!userExists) { val apiUser = this.osuApi.getUserProfile(userId = userId.toString(), mode = "osu", key = "id") if(apiUser != null) { @@ -178,7 +179,7 @@ class ImportScores( .and(UPDATE_USER_QUEUE.PROCESSED.isTrue) .orderBy(UPDATE_USER_QUEUE.PROCESSED_AT.desc()) .limit(1) - .fetchOneInto(LocalDateTime::class.java) + .fetchOneInto(OffsetDateTime::class.java) for(topScore in topUserScores) { if(topScore.beatmap != null && topScore.beatmapset != null) { @@ -209,6 +210,7 @@ class ImportScores( val currentQueueDetails = UserQueueDetails( isProcessing = true, lastCompletedUpdate = lastCompletedUpdate, + canUpdate = false, progressCurrent = current, progressTotal = topUserScores.size ) @@ -310,8 +312,8 @@ class ImportScores( if(beatmapExists) { - val threeDaysAgo = LocalDateTime - .now() + val threeDaysAgo = OffsetDateTime + .now(ZoneOffset.UTC) .minusDays(3) if(dslContext.fetchExists(BEATMAPS, BEATMAPS.BEATMAP_ID.eq(beatmap.id).and(BEATMAPS.SYS_LAST_UPDATE.greaterOrEqual(threeDaysAgo)))) { @@ -360,7 +362,7 @@ class ImportScores( this.statistics.currentScore++ this.logger.debug("Processing score: ${this.statistics.currentScore}/${beatmapScores.scores.size}") - val userExists = dslContext.fetchExists(USERS, USERS.USER_ID.eq(score.user_id), USERS.SYS_LAST_UPDATE.greaterOrEqual(LocalDateTime.now().minusDays(UPDATE_USER_EVERY_DAYS))) + val userExists = dslContext.fetchExists(USERS, USERS.USER_ID.eq(score.user_id), USERS.SYS_LAST_UPDATE.greaterOrEqual(OffsetDateTime.now(ZoneOffset.UTC).minusDays(UPDATE_USER_EVERY_DAYS))) if(!userExists) { this.userToUpdateBucket.add(score.user_id) @@ -396,7 +398,7 @@ class ImportScores( checkReplaySimilarity(beatmap.id) dslContext.update(BEATMAPS) - .set(BEATMAPS.SYS_LAST_UPDATE, LocalDateTime.now()) + .set(BEATMAPS.SYS_LAST_UPDATE, OffsetDateTime.now(ZoneOffset.UTC)) .where(BEATMAPS.BEATMAP_ID.eq(beatmap.id)) .execute() } diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/service/UpdateUserQueueService.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/service/UpdateUserQueueService.kt index 3709af5..172b9e4 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/service/UpdateUserQueueService.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/service/UpdateUserQueueService.kt @@ -2,11 +2,14 @@ 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.USERS import com.nisemoe.nise.UserQueueDetails import org.jooq.DSLContext +import org.springframework.http.ResponseEntity import org.springframework.messaging.simp.SimpMessagingTemplate import org.springframework.stereotype.Service -import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset @Service class UpdateUserQueueService( @@ -14,19 +17,53 @@ class UpdateUserQueueService( private val messagingTemplate: SimpMessagingTemplate ) { + private val USER_UPDATE_INTERVAL_HOURS = 4 + + /** + * Retrieves the user queue details for the given user ID. + * + * @param userId The osu!id of the user. + */ 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) + val lastCompletedUpdateQueue = 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) + .fetchOneInto(OffsetDateTime::class.java) + + val lastCompletedUpdateUser = dslContext.select(USERS.SYS_LAST_UPDATE) + .from(USERS) + .where(USERS.USER_ID.eq(userId)) + .fetchOneInto(OffsetDateTime::class.java) + + // Select the most recent + val lastCompletedUpdate = lastCompletedUpdateQueue?.let { + if (lastCompletedUpdateUser != null) { + if (lastCompletedUpdateUser.isAfter(lastCompletedUpdateQueue)) { + lastCompletedUpdateUser + } else { + lastCompletedUpdateQueue + } + } else { + lastCompletedUpdateQueue + } + } ?: lastCompletedUpdateUser + + var canUpdate = !isProcessing + if(lastCompletedUpdate != null) { + val now = OffsetDateTime.now(ZoneOffset.UTC) + val hoursSinceLastUpdate = now.hour - lastCompletedUpdate.hour + + if(hoursSinceLastUpdate < USER_UPDATE_INTERVAL_HOURS) + canUpdate = false + } val currentProgress = dslContext.select( UPDATE_USER_QUEUE.PROGRESS_CURRENT, @@ -42,12 +79,12 @@ class UpdateUserQueueService( return UserQueueDetails( isProcessing, lastCompletedUpdate, + canUpdate, currentProgress?.progressCurrent, currentProgress?.progressTotal ) } - fun getQueue(): List { return dslContext.select(UPDATE_USER_QUEUE.USER_ID) .from(UPDATE_USER_QUEUE) @@ -78,7 +115,7 @@ class UpdateUserQueueService( fun setUserAsProcessed(userId: Long) { dslContext.update(UPDATE_USER_QUEUE) .set(UPDATE_USER_QUEUE.PROCESSED, true) - .set(UPDATE_USER_QUEUE.PROCESSED_AT, LocalDateTime.now()) + .set(UPDATE_USER_QUEUE.PROCESSED_AT, OffsetDateTime.now(ZoneOffset.UTC)) .where(UPDATE_USER_QUEUE.USER_ID.eq(userId)) .and(UPDATE_USER_QUEUE.PROCESSED.isFalse) .execute() diff --git a/nise-backend/src/main/resources/db/migration/V0.0.1.018__alter_timestamps.sql b/nise-backend/src/main/resources/db/migration/V0.0.1.018__alter_timestamps.sql new file mode 100644 index 0000000..bc7d7bd --- /dev/null +++ b/nise-backend/src/main/resources/db/migration/V0.0.1.018__alter_timestamps.sql @@ -0,0 +1,14 @@ +ALTER TABLE public.update_user_queue + ALTER COLUMN processed_at TYPE timestamptz; + +ALTER TABLE public.users + ALTER COLUMN sys_last_update TYPE timestamptz, + ALTER COLUMN sys_last_update SET DEFAULT CURRENT_TIMESTAMP; + +ALTER TABLE public.beatmaps + ALTER COLUMN sys_last_update TYPE timestamptz, + ALTER COLUMN sys_last_update SET DEFAULT CURRENT_TIMESTAMP; + +ALTER TABLE public.scores + ALTER COLUMN added_at TYPE timestamptz, + ALTER COLUMN added_at SET DEFAULT CURRENT_TIMESTAMP; \ No newline at end of file diff --git a/nise-frontend/package-lock.json b/nise-frontend/package-lock.json index 4a27eb0..b7b27d4 100644 --- a/nise-frontend/package-lock.json +++ b/nise-frontend/package-lock.json @@ -19,6 +19,7 @@ "@popperjs/core": "^2.11.8", "@stomp/rx-stomp": "^2.0.0", "chart.js": "^4.4.1", + "date-fns": "^3.3.1", "lz-string": "^1.5.0", "ng2-charts": "^5.0.4", "rxjs": "~7.8.0", @@ -5452,6 +5453,15 @@ "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", "dev": true }, + "node_modules/date-fns": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", + "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", diff --git a/nise-frontend/package.json b/nise-frontend/package.json index 8fbb60a..4b3a034 100644 --- a/nise-frontend/package.json +++ b/nise-frontend/package.json @@ -21,6 +21,7 @@ "@popperjs/core": "^2.11.8", "@stomp/rx-stomp": "^2.0.0", "chart.js": "^4.4.1", + "date-fns": "^3.3.1", "lz-string": "^1.5.0", "ng2-charts": "^5.0.4", "rxjs": "~7.8.0", diff --git a/nise-frontend/src/app/userDetails.ts b/nise-frontend/src/app/userDetails.ts index 07e1288..0ad81b0 100644 --- a/nise-frontend/src/app/userDetails.ts +++ b/nise-frontend/src/app/userDetails.ts @@ -15,6 +15,7 @@ export interface UserDetails { export interface UserQueueDetails { isProcessing: boolean; lastCompletedUpdate: string | null; + canUpdate: boolean; progressCurrent: number | null; progressTotal: number | null; diff --git a/nise-frontend/src/app/view-user/view-user.component.html b/nise-frontend/src/app/view-user/view-user.component.html index 6d1903a..c553ceb 100644 --- a/nise-frontend/src/app/view-user/view-user.component.html +++ b/nise-frontend/src/app/view-user/view-user.component.html @@ -41,10 +41,16 @@ - - update user scores now! - | - last update: {{ this.userInfo.queue_details.lastCompletedUpdate ? this.userInfo.queue_details.lastCompletedUpdate : 'never'}} + + + update user scores now! + + + + can't force update now + + | + last update: {{ this.userInfo.queue_details.lastCompletedUpdate ? this.calculateTimeAgo(this.userInfo.queue_details.lastCompletedUpdate) : 'never'}}
diff --git a/nise-frontend/src/app/view-user/view-user.component.ts b/nise-frontend/src/app/view-user/view-user.component.ts index c02e323..eb965e2 100644 --- a/nise-frontend/src/app/view-user/view-user.component.ts +++ b/nise-frontend/src/app/view-user/view-user.component.ts @@ -11,6 +11,7 @@ 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"; +import {differenceInDays, differenceInHours} from "date-fns/fp"; interface UserInfo { user_details: UserDetails; @@ -37,6 +38,8 @@ interface UserInfo { }) export class ViewUserComponent implements OnInit, OnChanges, OnDestroy { + userUpdateIntervalHours = 4 + isLoading = false; notFound = false; userId: string | null = null; @@ -94,6 +97,27 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy { this.liveUserSub?.unsubscribe(); } + calculateTimeAgo(dateStr: string): string { + const inputDate = new Date(dateStr); + const now = new Date(); + + if (isNaN(inputDate.getTime())) { + return "???"; + } + + const difference = Math.abs(differenceInHours(now, inputDate)); // Use absolute value + + if (difference < 1) { + return "recently"; + } else if (difference < 24) { + return `${difference}h ago`; + } else { + const days = differenceInDays(now, inputDate); + const hours = difference % 24; + return `${days}d ${hours}h ago`; + } + } + addUserToQueue(): void { const body = { userId: this.userInfo?.user_details.user_id diff --git a/nise-frontend/src/assets/style.css b/nise-frontend/src/assets/style.css index 5943fe0..17597b0 100644 --- a/nise-frontend/src/assets/style.css +++ b/nise-frontend/src/assets/style.css @@ -151,6 +151,17 @@ a.btn-success:hover { border: 1px dotted #2af171; } +.btn-info { + padding: 2px; + color: #3498db; + border: 1px dotted #3498db; +} + +.btn-info:hover { + color: #3498db; + border: 1px dotted #3498db; +} + .btn-warning { padding: 2px; color: #f1c40f;