diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/Public.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/Public.kt
index 37a49fa..f7fb720 100644
--- a/nise-backend/src/main/kotlin/com/nisemoe/generated/Public.kt
+++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/Public.kt
@@ -16,6 +16,7 @@ import com.nisemoe.generated.sequences.USERS_USER_ID_SEQ
import com.nisemoe.generated.sequences.USERS_USER_ID_SEQ1
import com.nisemoe.generated.tables.Beatmaps
import com.nisemoe.generated.tables.FlywaySchemaHistory
+import com.nisemoe.generated.tables.OsuApiKeys
import com.nisemoe.generated.tables.RedditPost
import com.nisemoe.generated.tables.Scores
import com.nisemoe.generated.tables.ScoresJudgements
@@ -54,6 +55,11 @@ open class Public : SchemaImpl("public", DefaultCatalog.DEFAULT_CATALOG) {
*/
val FLYWAY_SCHEMA_HISTORY: FlywaySchemaHistory get() = FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY
+ /**
+ * The table public.osu_api_keys.
+ */
+ val OSU_API_KEYS: OsuApiKeys get() = OsuApiKeys.OSU_API_KEYS
+
/**
* The table public.reddit_post.
*/
@@ -102,6 +108,7 @@ open class Public : SchemaImpl("public", DefaultCatalog.DEFAULT_CATALOG) {
override fun getTables(): List
> = listOf(
Beatmaps.BEATMAPS,
FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY,
+ OsuApiKeys.OSU_API_KEYS,
RedditPost.REDDIT_POST,
Scores.SCORES,
ScoresJudgements.SCORES_JUDGEMENTS,
diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/keys/Keys.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/keys/Keys.kt
index a81784d..4dd9612 100644
--- a/nise-backend/src/main/kotlin/com/nisemoe/generated/keys/Keys.kt
+++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/keys/Keys.kt
@@ -6,6 +6,7 @@ package com.nisemoe.generated.keys
import com.nisemoe.generated.tables.Beatmaps
import com.nisemoe.generated.tables.FlywaySchemaHistory
+import com.nisemoe.generated.tables.OsuApiKeys
import com.nisemoe.generated.tables.RedditPost
import com.nisemoe.generated.tables.Scores
import com.nisemoe.generated.tables.ScoresJudgements
@@ -14,6 +15,7 @@ import com.nisemoe.generated.tables.UpdateUserQueue
import com.nisemoe.generated.tables.Users
import com.nisemoe.generated.tables.records.BeatmapsRecord
import com.nisemoe.generated.tables.records.FlywaySchemaHistoryRecord
+import com.nisemoe.generated.tables.records.OsuApiKeysRecord
import com.nisemoe.generated.tables.records.RedditPostRecord
import com.nisemoe.generated.tables.records.ScoresJudgementsRecord
import com.nisemoe.generated.tables.records.ScoresRecord
@@ -34,6 +36,7 @@ import org.jooq.impl.Internal
val BEATMAPS_PKEY: UniqueKey = Internal.createUniqueKey(Beatmaps.BEATMAPS, DSL.name("beatmaps_pkey"), arrayOf(Beatmaps.BEATMAPS.BEATMAP_ID), true)
val FLYWAY_SCHEMA_HISTORY_PK: UniqueKey = Internal.createUniqueKey(FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY, DSL.name("flyway_schema_history_pk"), arrayOf(FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY.INSTALLED_RANK), true)
+val OSU_API_KEYS_PKEY: UniqueKey = Internal.createUniqueKey(OsuApiKeys.OSU_API_KEYS, DSL.name("osu_api_keys_pkey"), arrayOf(OsuApiKeys.OSU_API_KEYS.ID), true)
val REDDIT_POST_PKEY: UniqueKey = Internal.createUniqueKey(RedditPost.REDDIT_POST, DSL.name("reddit_post_pkey"), arrayOf(RedditPost.REDDIT_POST.POST_ID), true)
val REPLAY_ID_UNIQUE: UniqueKey = Internal.createUniqueKey(Scores.SCORES, DSL.name("replay_id_unique"), arrayOf(Scores.SCORES.REPLAY_ID), true)
val SCORES_PKEY: UniqueKey = Internal.createUniqueKey(Scores.SCORES, DSL.name("scores_pkey"), arrayOf(Scores.SCORES.ID), true)
diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/OsuApiKeys.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/OsuApiKeys.kt
new file mode 100644
index 0000000..444bca0
--- /dev/null
+++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/OsuApiKeys.kt
@@ -0,0 +1,147 @@
+/*
+ * This file is generated by jOOQ.
+ */
+package com.nisemoe.generated.tables
+
+
+import com.nisemoe.generated.Public
+import com.nisemoe.generated.keys.OSU_API_KEYS_PKEY
+import com.nisemoe.generated.tables.records.OsuApiKeysRecord
+
+import java.time.OffsetDateTime
+import java.util.function.Function
+
+import org.jooq.Field
+import org.jooq.ForeignKey
+import org.jooq.Identity
+import org.jooq.Name
+import org.jooq.Record
+import org.jooq.Records
+import org.jooq.Row5
+import org.jooq.Schema
+import org.jooq.SelectField
+import org.jooq.Table
+import org.jooq.TableField
+import org.jooq.TableOptions
+import org.jooq.UniqueKey
+import org.jooq.impl.DSL
+import org.jooq.impl.Internal
+import org.jooq.impl.SQLDataType
+import org.jooq.impl.TableImpl
+
+
+/**
+ * This class is generated by jOOQ.
+ */
+@Suppress("UNCHECKED_CAST")
+open class OsuApiKeys(
+ alias: Name,
+ child: Table?,
+ path: ForeignKey?,
+ aliased: Table?,
+ parameters: Array?>?
+): TableImpl(
+ alias,
+ Public.PUBLIC,
+ child,
+ path,
+ aliased,
+ parameters,
+ DSL.comment(""),
+ TableOptions.table()
+) {
+ companion object {
+
+ /**
+ * The reference instance of public.osu_api_keys
+ */
+ val OSU_API_KEYS: OsuApiKeys = OsuApiKeys()
+ }
+
+ /**
+ * The class holding records for this type
+ */
+ override fun getRecordType(): Class = OsuApiKeysRecord::class.java
+
+ /**
+ * The column public.osu_api_keys.id.
+ */
+ val ID: TableField = createField(DSL.name("id"), SQLDataType.INTEGER.nullable(false).identity(true), this, "")
+
+ /**
+ * The column public.osu_api_keys.api_key.
+ */
+ val API_KEY: TableField = createField(DSL.name("api_key"), SQLDataType.CLOB.nullable(false), this, "")
+
+ /**
+ * The column public.osu_api_keys.created_at.
+ */
+ val CREATED_AT: TableField = createField(DSL.name("created_at"), SQLDataType.TIMESTAMPWITHTIMEZONE(6).nullable(false).defaultValue(DSL.field(DSL.raw("CURRENT_TIMESTAMP"), SQLDataType.TIMESTAMPWITHTIMEZONE)), this, "")
+
+ /**
+ * The column public.osu_api_keys.is_valid.
+ */
+ val IS_VALID: TableField = createField(DSL.name("is_valid"), SQLDataType.BOOLEAN.nullable(false).defaultValue(DSL.field(DSL.raw("true"), SQLDataType.BOOLEAN)), this, "")
+
+ /**
+ * The column public.osu_api_keys.is_active.
+ */
+ val IS_ACTIVE: TableField = createField(DSL.name("is_active"), SQLDataType.BOOLEAN.nullable(false).defaultValue(DSL.field(DSL.raw("true"), SQLDataType.BOOLEAN)), 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)
+
+ /**
+ * Create an aliased public.osu_api_keys table reference
+ */
+ constructor(alias: String): this(DSL.name(alias))
+
+ /**
+ * Create an aliased public.osu_api_keys table reference
+ */
+ constructor(alias: Name): this(alias, null)
+
+ /**
+ * Create a public.osu_api_keys table reference
+ */
+ constructor(): this(DSL.name("osu_api_keys"), null)
+
+ constructor(child: Table, key: ForeignKey): this(Internal.createPathAlias(child, key), child, key, OSU_API_KEYS, null)
+ override fun getSchema(): Schema? = if (aliased()) null else Public.PUBLIC
+ override fun getIdentity(): Identity = super.getIdentity() as Identity
+ override fun getPrimaryKey(): UniqueKey = OSU_API_KEYS_PKEY
+ override fun `as`(alias: String): OsuApiKeys = OsuApiKeys(DSL.name(alias), this)
+ override fun `as`(alias: Name): OsuApiKeys = OsuApiKeys(alias, this)
+ override fun `as`(alias: Table<*>): OsuApiKeys = OsuApiKeys(alias.getQualifiedName(), this)
+
+ /**
+ * Rename this table
+ */
+ override fun rename(name: String): OsuApiKeys = OsuApiKeys(DSL.name(name), null)
+
+ /**
+ * Rename this table
+ */
+ override fun rename(name: Name): OsuApiKeys = OsuApiKeys(name, null)
+
+ /**
+ * Rename this table
+ */
+ override fun rename(name: Table<*>): OsuApiKeys = OsuApiKeys(name.getQualifiedName(), null)
+
+ // -------------------------------------------------------------------------
+ // Row5 type methods
+ // -------------------------------------------------------------------------
+ override fun fieldsRow(): Row5 = super.fieldsRow() as Row5
+
+ /**
+ * Convenience mapping calling {@link SelectField#convertFrom(Function)}.
+ */
+ fun mapping(from: (Int?, String?, OffsetDateTime?, Boolean?, Boolean?) -> U): SelectField = convertFrom(Records.mapping(from))
+
+ /**
+ * Convenience mapping calling {@link SelectField#convertFrom(Class,
+ * Function)}.
+ */
+ fun mapping(toType: Class, from: (Int?, String?, OffsetDateTime?, Boolean?, Boolean?) -> 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 1ca5ca8..6ea4e02 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
@@ -87,7 +87,7 @@ open class UpdateUserQueue(
/**
* The column public.update_user_queue.processed_at.
*/
- val PROCESSED_AT: TableField = createField(DSL.name("processed_at"), SQLDataType.TIMESTAMPWITHTIMEZONE(6).defaultValue(DSL.field(DSL.raw("CURRENT_TIMESTAMP"), SQLDataType.TIMESTAMPWITHTIMEZONE)), this, "")
+ val PROCESSED_AT: TableField = createField(DSL.name("processed_at"), SQLDataType.TIMESTAMPWITHTIMEZONE(6), this, "")
/**
* The column public.update_user_queue.progress_current.
diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/OsuApiKeysRecord.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/OsuApiKeysRecord.kt
new file mode 100644
index 0000000..9b7082d
--- /dev/null
+++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/OsuApiKeysRecord.kt
@@ -0,0 +1,121 @@
+/*
+ * This file is generated by jOOQ.
+ */
+package com.nisemoe.generated.tables.records
+
+
+import com.nisemoe.generated.tables.OsuApiKeys
+
+import java.time.OffsetDateTime
+
+import org.jooq.Field
+import org.jooq.Record1
+import org.jooq.Record5
+import org.jooq.Row5
+import org.jooq.impl.UpdatableRecordImpl
+
+
+/**
+ * This class is generated by jOOQ.
+ */
+@Suppress("UNCHECKED_CAST")
+open class OsuApiKeysRecord private constructor() : UpdatableRecordImpl(OsuApiKeys.OSU_API_KEYS), Record5 {
+
+ open var id: Int?
+ set(value): Unit = set(0, value)
+ get(): Int? = get(0) as Int?
+
+ open var apiKey: String
+ set(value): Unit = set(1, value)
+ get(): String = get(1) as String
+
+ open var createdAt: OffsetDateTime?
+ set(value): Unit = set(2, value)
+ get(): OffsetDateTime? = get(2) as OffsetDateTime?
+
+ @Suppress("INAPPLICABLE_JVM_NAME")
+ @set:JvmName("setIsValid")
+ open var isValid: Boolean?
+ set(value): Unit = set(3, value)
+ get(): Boolean? = get(3) as Boolean?
+
+ @Suppress("INAPPLICABLE_JVM_NAME")
+ @set:JvmName("setIsActive")
+ open var isActive: Boolean?
+ set(value): Unit = set(4, value)
+ get(): Boolean? = get(4) as Boolean?
+
+ // -------------------------------------------------------------------------
+ // Primary key information
+ // -------------------------------------------------------------------------
+
+ override fun key(): Record1 = super.key() as Record1
+
+ // -------------------------------------------------------------------------
+ // Record5 type implementation
+ // -------------------------------------------------------------------------
+
+ override fun fieldsRow(): Row5 = super.fieldsRow() as Row5
+ override fun valuesRow(): Row5 = super.valuesRow() as Row5
+ override fun field1(): Field = OsuApiKeys.OSU_API_KEYS.ID
+ override fun field2(): Field = OsuApiKeys.OSU_API_KEYS.API_KEY
+ override fun field3(): Field = OsuApiKeys.OSU_API_KEYS.CREATED_AT
+ override fun field4(): Field = OsuApiKeys.OSU_API_KEYS.IS_VALID
+ override fun field5(): Field = OsuApiKeys.OSU_API_KEYS.IS_ACTIVE
+ override fun component1(): Int? = id
+ override fun component2(): String = apiKey
+ override fun component3(): OffsetDateTime? = createdAt
+ override fun component4(): Boolean? = isValid
+ override fun component5(): Boolean? = isActive
+ override fun value1(): Int? = id
+ override fun value2(): String = apiKey
+ override fun value3(): OffsetDateTime? = createdAt
+ override fun value4(): Boolean? = isValid
+ override fun value5(): Boolean? = isActive
+
+ override fun value1(value: Int?): OsuApiKeysRecord {
+ set(0, value)
+ return this
+ }
+
+ override fun value2(value: String?): OsuApiKeysRecord {
+ set(1, value)
+ return this
+ }
+
+ override fun value3(value: OffsetDateTime?): OsuApiKeysRecord {
+ set(2, value)
+ return this
+ }
+
+ override fun value4(value: Boolean?): OsuApiKeysRecord {
+ set(3, value)
+ return this
+ }
+
+ override fun value5(value: Boolean?): OsuApiKeysRecord {
+ set(4, value)
+ return this
+ }
+
+ override fun values(value1: Int?, value2: String?, value3: OffsetDateTime?, value4: Boolean?, value5: Boolean?): OsuApiKeysRecord {
+ this.value1(value1)
+ this.value2(value2)
+ this.value3(value3)
+ this.value4(value4)
+ this.value5(value5)
+ return this
+ }
+
+ /**
+ * Create a detached, initialised OsuApiKeysRecord
+ */
+ constructor(id: Int? = null, apiKey: String, createdAt: OffsetDateTime? = null, isValid: Boolean? = null, isActive: Boolean? = null): this() {
+ this.id = id
+ this.apiKey = apiKey
+ this.createdAt = createdAt
+ this.isValid = isValid
+ this.isActive = isActive
+ resetChangedOnNotNull()
+ }
+}
diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/references/Tables.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/references/Tables.kt
index 35ebc13..a521b55 100644
--- a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/references/Tables.kt
+++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/references/Tables.kt
@@ -6,6 +6,7 @@ package com.nisemoe.generated.tables.references
import com.nisemoe.generated.tables.Beatmaps
import com.nisemoe.generated.tables.FlywaySchemaHistory
+import com.nisemoe.generated.tables.OsuApiKeys
import com.nisemoe.generated.tables.RedditPost
import com.nisemoe.generated.tables.Scores
import com.nisemoe.generated.tables.ScoresJudgements
@@ -25,6 +26,11 @@ val BEATMAPS: Beatmaps = Beatmaps.BEATMAPS
*/
val FLYWAY_SCHEMA_HISTORY: FlywaySchemaHistory = FlywaySchemaHistory.FLYWAY_SCHEMA_HISTORY
+/**
+ * The table public.osu_api_keys.
+ */
+val OSU_API_KEYS: OsuApiKeys = OsuApiKeys.OSU_API_KEYS
+
/**
* The table public.reddit_post.
*/
diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/osu/OsuApi.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/osu/OsuApi.kt
index 0ab130e..e0e5f96 100644
--- a/nise-backend/src/main/kotlin/com/nisemoe/nise/osu/OsuApi.kt
+++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/osu/OsuApi.kt
@@ -1,25 +1,65 @@
package com.nisemoe.nise.osu
+import com.nisemoe.generated.tables.references.OSU_API_KEYS
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
+import org.jooq.DSLContext
import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
+import java.util.concurrent.atomic.AtomicInteger
+
+class InvalidOsuApiKeyException() : Exception()
@Service
class OsuApi(
- private val tokenService: TokenService
-) {
+ private val tokenService: TokenService,
+ private val dslContext: DSLContext
+): InitializingBean {
private val logger = LoggerFactory.getLogger(javaClass)
- @Value("\${OSU_API_KEY}")
- private lateinit var osuApiKey: String
+ private lateinit var apiKeysList: List
+ private val currentKeyIndex = AtomicInteger(0)
+
+ override fun afterPropertiesSet() {
+ this.loadApiKeys()
+ }
+
+ fun loadApiKeys() {
+ currentKeyIndex.set(0)
+
+ val keys = dslContext.select(OSU_API_KEYS.API_KEY)
+ .from(OSU_API_KEYS)
+ .where(OSU_API_KEYS.IS_VALID.eq(true))
+ .and(OSU_API_KEYS.IS_ACTIVE.eq(true))
+ .fetchInto(String::class.java)
+
+ this.apiKeysList = keys
+ this.logger.info("Loaded ${keys.size} valid API keys")
+ }
+
+ fun getCurrentApiKey(): String {
+ val key = apiKeysList[currentKeyIndex.get() % apiKeysList.size]
+ currentKeyIndex.getAndIncrement()
+ return key
+ }
+
+ fun setKeyAsInvalid(apiKey: String) {
+ dslContext.update(OSU_API_KEYS)
+ .set(OSU_API_KEYS.IS_VALID, false)
+ .where(OSU_API_KEYS.API_KEY.eq(apiKey))
+ .execute()
+
+ this.logger.info("Marked API key $apiKey as invalid")
+ this.loadApiKeys()
+ }
@OptIn(ExperimentalSerializationApi::class)
private val serializer = Json { ignoreUnknownKeys = true; explicitNulls = false }
@@ -57,29 +97,41 @@ class OsuApi(
return this.sendWithRetry(request)
}
+ /**
+ * Retrieves the replay data for a given score ID from the Osu API.
+ * Efficiently cycles through the API keys to avoid rate limiting.
+ * It's limited to 10 requests per minute according to @ https://github.com/ppy/osu-api/wiki#get-replay-data
+ *
+ * @param scoreId The ID of the score (best_id)
+ */
fun getReplay(scoreId: Long): OsuApiModels.ReplayResponse? {
- val queryParams = mapOf(
- "k" to this.osuApiKey,
- "s" to scoreId,
- "m" to 0 // [osu!std]
- )
- val response = this.doRequest("https://osu.ppy.sh/api/get_replay?", queryParams)
- if(response == null) {
- this.logger.info("Error loading replay data")
- return null
+ while (apiKeysList.isNotEmpty()) {
+ val apiKey = this.getCurrentApiKey()
+ val queryParams = mapOf(
+ "k" to apiKey,
+ "s" to scoreId.toString(),
+ "m" to "0" // [osu!std]
+ )
+
+ try {
+ val response = this.doRequest("https://osu.ppy.sh/api/get_replay?", queryParams)
+ if (response != null && response.statusCode() == 200) {
+ try {
+ return serializer.decodeFromString(OsuApiModels.ReplayResponse.serializer(), response.body())
+ } catch(exception: Exception) {
+ this.logger.error("Failed to parse successful response: ${response.body()}")
+ this.logger.error(exception.stackTraceToString())
+ break
+ }
+ }
+ } catch (e: InvalidOsuApiKeyException) {
+ this.logger.error("Invalid API key detected: $apiKey, trying next...")
+ this.setKeyAsInvalid(apiKey)
+ }
}
- return if (response.statusCode() == 200) {
- try {
- serializer.decodeFromString(OsuApiModels.ReplayResponse.serializer(), response.body())
- } catch(exception: Exception) {
- this.logger.error(response.body())
- this.logger.error(exception.stackTraceToString())
- return null
- }
- } else {
- null
- }
+ this.logger.error("Failed to load replay data after cycling through all keys.")
+ return null
}
fun getTopBeatmapScores(beatmapId: Int): OsuApiModels.BeatmapScores? {
@@ -213,6 +265,11 @@ class OsuApi(
this.logger.debug("Result: {}", response.statusCode())
this.logger.debug("")
+ // Handle osu!api v1 invalid api key errors in a specific way; mark the key as invalid so that
+ // it won't be used again and return null to indicate that the request failed.
+ if(response.statusCode() == 401 && response.body() == "{\"error\":\"Please provide a valid API key.\"}")
+ throw InvalidOsuApiKeyException()
+
when(response.statusCode()) {
401 -> {
// If the status code is 401, the access token has expired or something, refresh it.
diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/GlobalCache.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/GlobalCache.kt
index f6aa1a9..40a4a8a 100644
--- a/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/GlobalCache.kt
+++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/GlobalCache.kt
@@ -12,6 +12,8 @@ import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
+import org.springframework.util.StopWatch
+import kotlin.math.roundToInt
@Service
class GlobalCache(
@@ -27,9 +29,12 @@ class GlobalCache(
var statistics: Statistics? = null
var rssFeed: RssFeed? = null
+ val stopwatch = StopWatch()
+
// 10 minutes to ms = 600000
@Scheduled(fixedDelay = 600000, initialDelay = 0)
fun updateCaches() {
+ this.stopwatch.start()
logger.info("Updating the cache!")
runBlocking {
@@ -43,6 +48,9 @@ class GlobalCache(
similarReplays = similarReplaysDeferred.await()
suspiciousScores = suspiciousScoresDeferred.await()
}
+
+ this.stopwatch.stop()
+ logger.info("Cache updated in {} seconds", String.format("%.2f", stopwatch.totalTimeSeconds))
}
}
\ No newline at end of file
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 0dbab4f..b625d92 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
@@ -563,7 +563,7 @@ class ImportScores(
// It's limited to 10 requests per minute according to @ https://github.com/ppy/osu-api/wiki#get-replay-data
// So, we sleep for 6 seconds.
- Thread.sleep(6000)
+// Thread.sleep(6000)
// Calculate UR
val processedReplay: CircleguardService.ReplayResponse? = try {
diff --git a/nise-backend/src/main/resources/db/migration/V0.0.1.019__create_osu_api_keys.sql b/nise-backend/src/main/resources/db/migration/V0.0.1.019__create_osu_api_keys.sql
new file mode 100644
index 0000000..f7f7e40
--- /dev/null
+++ b/nise-backend/src/main/resources/db/migration/V0.0.1.019__create_osu_api_keys.sql
@@ -0,0 +1,8 @@
+create table "public".osu_api_keys
+(
+ id serial primary key,
+ api_key text not null,
+ created_at timestamp with time zone not null default current_timestamp,
+ is_valid boolean not null default true,
+ is_active boolean not null default true
+);
\ No newline at end of file
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 5210cc8..88e6a61 100644
--- a/nise-frontend/src/app/view-user/view-user.component.html
+++ b/nise-frontend/src/app/view-user/view-user.component.html
@@ -56,7 +56,7 @@
updating now! | progress: {{ this.userInfo.queue_details.progressCurrent != null ? this.userInfo.queue_details.progressCurrent : "?" }}/{{ this.userInfo.queue_details.progressTotal != null ? this.userInfo.queue_details.progressTotal : "?" }}
- (in queue)
+ (in queue, be patient)