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 9350266..4cc40de 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 @@ -38,7 +38,7 @@ import java.time.ZoneOffset @Service @RestController -@Profile("updater") +@Profile("import:scores") class ImportScores( private val dslContext: DSLContext, private val userService: UserService, diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportUsers.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportUsers.kt new file mode 100644 index 0000000..a7c8175 --- /dev/null +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportUsers.kt @@ -0,0 +1,100 @@ +package com.nisemoe.nise.scheduler + +import com.nisemoe.generated.tables.references.SCORES +import com.nisemoe.generated.tables.references.USERS +import com.nisemoe.nise.database.UserService +import com.nisemoe.nise.integrations.DiscordEmbed +import com.nisemoe.nise.integrations.DiscordService +import com.nisemoe.nise.osu.OsuApi +import org.jooq.DSLContext +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Profile +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +@Profile("import:users") +class ImportUsers( + private val dslContext: DSLContext, + private val userService: UserService, + private val discordService: DiscordService, + private val osuApi: OsuApi, +) { + + private val logger = LoggerFactory.getLogger(javaClass) + + companion object { + + const val SLEEP_AFTER_API_CALL = 1000L + + } + + @Value("\${WEBHOOK_URL}") + private lateinit var webhookUrl: String + + @Scheduled(fixedDelay = 20000, initialDelay = 0) + fun updateUsersScheduler() { + try { + this.updateUsers() + } catch (exception: Exception) { + val errorEmbed = DiscordEmbed( + title = "Exception ocurred", + description = exception.stackTraceToString() + ) + this.discordService.sendEmbeds( + this.webhookUrl, + listOf(errorEmbed) + ) + this.logger.error(exception.stackTraceToString()) + } + } + + private fun updateUsers() { + // Fetch 50 users with the oldest last update time + // We exclude all users with at least 1 banned score in the last 3 months + val threeMonthsAgo = LocalDateTime.now().minusMonths(3) + + val bannedUsersCondition = SCORES.IS_BANNED.eq(true) + .and(SCORES.DATE.greaterOrEqual(threeMonthsAgo)) + + val userIds = dslContext + .select(USERS.USER_ID) + .from(USERS) + .leftJoin(SCORES).on(USERS.USER_ID.eq(SCORES.USER_ID).and(bannedUsersCondition)) + .where(SCORES.USER_ID.isNull) + .orderBy(USERS.SYS_LAST_UPDATE.asc()) + .limit(50) + .fetchInto(Long::class.java) + + val usersResult = this.osuApi.getUsersBatch(userIds) + + if (usersResult == null) { + this.logger.error("Failed to get users") + return + } + + // Check which ids are missing; if any are missing, we explicitly check if they're banned. + val missingIds = userIds.filter { it !in usersResult.users.map { it.id } } + for (missingId in missingIds) { + val isUserBanned = this.osuApi.checkIfUserBanned(missingId) + Thread.sleep(SLEEP_AFTER_API_CALL) + if (isUserBanned == true) { + this.logger.warn("User $missingId is banned") + dslContext.update(SCORES) + .set(SCORES.IS_BANNED, true) + .where(SCORES.USER_ID.eq(missingId)) + .execute() + } + } + + // Update the rest of the users + for (user in usersResult.users) { + userService.insertApiUser(user) + } + + this.logger.info("Updated ${usersResult.users.size} users") + } + +} \ No newline at end of file