Compare commits
No commits in common. "38e997cba214a301595ffec428ee78d921582970" and "34a05015e2cf0711c54eb16dc19c6d3242293e12" have entirely different histories.
38e997cba2
...
34a05015e2
@ -141,7 +141,6 @@ data class ReplayData(
|
|||||||
val beatmap_count_sliders: Int?,
|
val beatmap_count_sliders: Int?,
|
||||||
val beatmap_count_spinners: Int?,
|
val beatmap_count_spinners: Int?,
|
||||||
val score: Int,
|
val score: Int,
|
||||||
val mods_bitwise: Int,
|
|
||||||
val mods: List<String>,
|
val mods: List<String>,
|
||||||
val rank: String?,
|
val rank: String?,
|
||||||
val ur: Double?,
|
val ur: Double?,
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
package com.nisemoe.nise.config
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
import org.springframework.boot.jdbc.DataSourceBuilder
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import org.springframework.context.annotation.Primary
|
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement
|
|
||||||
import javax.sql.DataSource
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableTransactionManagement
|
|
||||||
class DataSourceConfig {
|
|
||||||
@Primary
|
|
||||||
@Bean(name = ["niseDataSource"])
|
|
||||||
@ConfigurationProperties(prefix = "spring.datasource.nise")
|
|
||||||
fun niseDataSource(): DataSource = DataSourceBuilder.create().build()
|
|
||||||
|
|
||||||
@Bean(name = ["replayCacheDataSource"])
|
|
||||||
@ConfigurationProperties(prefix = "spring.datasource.replay-cache")
|
|
||||||
fun replayCacheDataSource(): DataSource = DataSourceBuilder.create().build()
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
package com.nisemoe.nise.controller
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
|
|
||||||
data class HealthResponse(
|
|
||||||
val healthy: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
val healthResponse = HealthResponse(
|
|
||||||
healthy = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
class HealthController {
|
|
||||||
@GetMapping("/health")
|
|
||||||
fun healthCheck(): ResponseEntity<HealthResponse> {
|
|
||||||
return ResponseEntity.ok(healthResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
package com.nisemoe.nise.controller
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
|
|
||||||
data class VersionResponse(
|
|
||||||
val version: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
val versionResponse = VersionResponse(
|
|
||||||
version = "v20250213",
|
|
||||||
)
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
class VersionController {
|
|
||||||
@GetMapping("/version")
|
|
||||||
fun getVersion(): ResponseEntity<VersionResponse> = ResponseEntity.ok(versionResponse)
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
package com.nisemoe.nise.database
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import javax.sql.DataSource
|
|
||||||
|
|
||||||
data class ReplayCacheReplay(
|
|
||||||
val replayId: Long,
|
|
||||||
val mapId: Int,
|
|
||||||
val userId: Int,
|
|
||||||
val replayData: ByteArray,
|
|
||||||
val mods: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class ReplayCacheService(
|
|
||||||
@Qualifier("replayCacheDataSource") private val dataSource: DataSource,
|
|
||||||
) {
|
|
||||||
fun getReplayById(replayId: Long): ByteArray? =
|
|
||||||
dataSource.connection.use { connection ->
|
|
||||||
val statement = connection.prepareStatement("SELECT replay_data FROM replays WHERE replay_id = ?")
|
|
||||||
statement.setLong(1, replayId)
|
|
||||||
val resultSet = statement.executeQuery()
|
|
||||||
|
|
||||||
var replayData: ByteArray? = null
|
|
||||||
while (resultSet.next()) {
|
|
||||||
replayData = resultSet.getBytes(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return replayData
|
|
||||||
}
|
|
||||||
|
|
||||||
fun insertReplay(replay: ReplayCacheReplay): Boolean =
|
|
||||||
dataSource.connection.use { connection ->
|
|
||||||
val statement = connection.prepareStatement("INSERT INTO replays VALUES (?, ?, ?, ?, ?)")
|
|
||||||
statement.setLong(1, replay.replayId)
|
|
||||||
statement.setInt(2, replay.mapId)
|
|
||||||
statement.setInt(3, replay.userId)
|
|
||||||
statement.setBytes(4, replay.replayData)
|
|
||||||
statement.setInt(5, replay.mods)
|
|
||||||
|
|
||||||
val updateCount = statement.executeUpdate()
|
|
||||||
|
|
||||||
return updateCount != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -177,8 +177,6 @@ class ScoreService(
|
|||||||
val hitDistribution = this.getHitDistribution(scoreId = result.get(SCORES.ID, Int::class.java))
|
val hitDistribution = this.getHitDistribution(scoreId = result.get(SCORES.ID, Int::class.java))
|
||||||
val charts = this.getCharts(result)
|
val charts = this.getCharts(result)
|
||||||
|
|
||||||
val mods = result.get(SCORES.MODS, Int::class.java)
|
|
||||||
|
|
||||||
val replayData = ReplayData(
|
val replayData = ReplayData(
|
||||||
replay_id = replayId,
|
replay_id = replayId,
|
||||||
user_id = result.get(SCORES.USER_ID, Int::class.java),
|
user_id = result.get(SCORES.USER_ID, Int::class.java),
|
||||||
@ -206,8 +204,7 @@ class ScoreService(
|
|||||||
ur = result.get(SCORES.UR, Double::class.java),
|
ur = result.get(SCORES.UR, Double::class.java),
|
||||||
adjusted_ur = result.get(SCORES.ADJUSTED_UR, Double::class.java),
|
adjusted_ur = result.get(SCORES.ADJUSTED_UR, Double::class.java),
|
||||||
score = result.get(SCORES.SCORE, Int::class.java),
|
score = result.get(SCORES.SCORE, Int::class.java),
|
||||||
mods_bitwise = mods,
|
mods = Mod.parseModCombination(result.get(SCORES.MODS, Int::class.java)),
|
||||||
mods = Mod.parseModCombination(mods),
|
|
||||||
rank = result.get(SCORES.RANK, String::class.java),
|
rank = result.get(SCORES.RANK, String::class.java),
|
||||||
snaps = result.get(SCORES.SNAPS, Int::class.java),
|
snaps = result.get(SCORES.SNAPS, Int::class.java),
|
||||||
hits = result.get(SCORES.EDGE_HITS, Int::class.java),
|
hits = result.get(SCORES.EDGE_HITS, Int::class.java),
|
||||||
@ -235,7 +232,7 @@ class ScoreService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getDefaultCondition(): Condition {
|
fun getDefaultCondition(): Condition {
|
||||||
return SCORES.UR.lessOrEqual(35.0)
|
return SCORES.UR.lessOrEqual(25.0)
|
||||||
.and(SCORES.IS_BANNED.eq(false))
|
.and(SCORES.IS_BANNED.eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,8 +76,6 @@ class UserScoreService(
|
|||||||
val hitDistribution = this.getHitDistribution(result.get(USER_SCORES.JUDGEMENTS, ByteArray::class.java))
|
val hitDistribution = this.getHitDistribution(result.get(USER_SCORES.JUDGEMENTS, ByteArray::class.java))
|
||||||
val charts = this.scoreService.getCharts(result)
|
val charts = this.scoreService.getCharts(result)
|
||||||
|
|
||||||
val mods = result.get(USER_SCORES.MODS, Int::class.java)
|
|
||||||
|
|
||||||
val replayData = ReplayData(
|
val replayData = ReplayData(
|
||||||
replay_id = result.get(USER_SCORES.ONLINE_SCORE_ID, Long::class.java),
|
replay_id = result.get(USER_SCORES.ONLINE_SCORE_ID, Long::class.java),
|
||||||
username = result.get(USER_SCORES.PLAYER_NAME, String::class.java),
|
username = result.get(USER_SCORES.PLAYER_NAME, String::class.java),
|
||||||
@ -102,8 +100,7 @@ class UserScoreService(
|
|||||||
ur = result.get(USER_SCORES.UR, Double::class.java),
|
ur = result.get(USER_SCORES.UR, Double::class.java),
|
||||||
adjusted_ur = result.get(USER_SCORES.ADJUSTED_UR, Double::class.java),
|
adjusted_ur = result.get(USER_SCORES.ADJUSTED_UR, Double::class.java),
|
||||||
score = result.get(USER_SCORES.TOTAL_SCORE, Int::class.java),
|
score = result.get(USER_SCORES.TOTAL_SCORE, Int::class.java),
|
||||||
mods_bitwise = mods,
|
mods = Mod.parseModCombination(result.get(USER_SCORES.MODS, Int::class.java)),
|
||||||
mods = Mod.parseModCombination(mods),
|
|
||||||
snaps = result.get(USER_SCORES.SNAPS, Int::class.java),
|
snaps = result.get(USER_SCORES.SNAPS, Int::class.java),
|
||||||
hits = result.get(USER_SCORES.EDGE_HITS, Int::class.java),
|
hits = result.get(USER_SCORES.EDGE_HITS, Int::class.java),
|
||||||
perfect = result.get(USER_SCORES.PERFECT, Boolean::class.java),
|
perfect = result.get(USER_SCORES.PERFECT, Boolean::class.java),
|
||||||
|
|||||||
@ -70,15 +70,14 @@ class OsuApi(
|
|||||||
.version(HttpClient.Version.HTTP_2)
|
.version(HttpClient.Version.HTTP_2)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun doRequest(url: String, queryParams: Map<String, Any?>, authorized: Boolean = true, appendToUrl: String? = null): HttpResponse<String>? {
|
fun doRequest(url: String, queryParams: Map<String, Any>, authorized: Boolean = true, appendToUrl: String? = null): HttpResponse<String>? {
|
||||||
var accessToken: TokenService.AccessTokenResponse? = null
|
var accessToken: TokenService.AccessTokenResponse? = null
|
||||||
if(authorized)
|
if(authorized)
|
||||||
accessToken = this.tokenService.getAccessToken()
|
accessToken = this.tokenService.getAccessToken()
|
||||||
|
|
||||||
val uriBuilder = StringBuilder(url)
|
val uriBuilder = StringBuilder(url)
|
||||||
queryParams.forEach { (key, value) ->
|
queryParams.forEach { (key, value) ->
|
||||||
if (value != null)
|
uriBuilder.append("$key=$value&")
|
||||||
uriBuilder.append("$key=$value&")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(appendToUrl != null)
|
if(appendToUrl != null)
|
||||||
@ -137,19 +136,6 @@ class OsuApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBeatmapFromId(beatmapId: Int): OsuApiModels.Beatmap? {
|
|
||||||
val response = doRequest("https://osu.ppy.sh/api/v2/beatmaps/$beatmapId", emptyMap())
|
|
||||||
if (response == null) {
|
|
||||||
this.logger.info("Error loading beatmap $beatmapId")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (response.statusCode()) {
|
|
||||||
200 -> serializer.decodeFromString<OsuApiModels.Beatmap>(response.body())
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the replay data for a given score ID from the osu!api.
|
* Retrieves the replay data for a given score ID from the osu!api.
|
||||||
* Efficiently cycles through the API keys to avoid rate limiting.
|
* Efficiently cycles through the API keys to avoid rate limiting.
|
||||||
@ -227,20 +213,6 @@ class OsuApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserBeatmapScores(userId: Long, beatmapId: Int): OsuApiModels.BeatmapScores? {
|
|
||||||
val response = doRequest("https://osu.ppy.sh/api/v2/beatmaps/$beatmapId/scores/users/$userId/all", emptyMap())
|
|
||||||
|
|
||||||
if(response == null) {
|
|
||||||
this.logger.info("Error getting scores on beatmap $beatmapId for user $userId")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (response.statusCode()) {
|
|
||||||
200 -> serializer.decodeFromString<OsuApiModels.BeatmapScores>(response.body())
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchBeatmapsets(cursor: OsuApiModels.BeatmapsetSearchResultCursor?): OsuApiModels.BeatmapsetSearchResult? {
|
fun searchBeatmapsets(cursor: OsuApiModels.BeatmapsetSearchResultCursor?): OsuApiModels.BeatmapsetSearchResult? {
|
||||||
val queryParams = mutableMapOf(
|
val queryParams = mutableMapOf(
|
||||||
"s" to "ranked", // Status [only ranked]
|
"s" to "ranked", // Status [only ranked]
|
||||||
@ -262,7 +234,7 @@ class OsuApi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun checkIfUserBanned(userId: Long): Boolean? {
|
fun checkIfUserBanned(userId: Long): Boolean? {
|
||||||
val response = this.doRequest("https://osu.ppy.sh/api/v2/users/$userId/osu?key=id", emptyMap())
|
val response = this.doRequest("https://osu.ppy.sh/api/v2/users/$userId/osu?key=id", mapOf())
|
||||||
if(response == null) {
|
if(response == null) {
|
||||||
this.logger.info("Error loading user with userId = $userId")
|
this.logger.info("Error loading user with userId = $userId")
|
||||||
return null
|
return null
|
||||||
@ -321,24 +293,6 @@ class OsuApi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserMostPlayed(userId: Long, limit: Int? = null, offset: Int? = null): List<OsuApiModels.BeatmapPlaycount>? {
|
|
||||||
val queryParams = mapOf(
|
|
||||||
"limit" to limit,
|
|
||||||
"offset" to offset,
|
|
||||||
)
|
|
||||||
val response = this.doRequest("https://osu.ppy.sh/api/v2/users/$userId/beatmapsets/most_played/?", queryParams)
|
|
||||||
|
|
||||||
if (response == null) {
|
|
||||||
this.logger.info("Error getting user most played ($userId)")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (response.statusCode()) {
|
|
||||||
200 -> serializer.decodeFromString<List<OsuApiModels.BeatmapPlaycount>>(response.body())
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var rateLimitRemaining: Long = 0L
|
var rateLimitRemaining: Long = 0L
|
||||||
var rateLimitTotal: Long = 0L
|
var rateLimitTotal: Long = 0L
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,6 @@ class OsuApiModels {
|
|||||||
val avatar_url: String,
|
val avatar_url: String,
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val username: String,
|
val username: String,
|
||||||
val beatmap_playcounts_count: Int?,
|
|
||||||
|
|
||||||
// Documentation: https://osu.ppy.sh/docs/index.html#userextended
|
// Documentation: https://osu.ppy.sh/docs/index.html#userextended
|
||||||
val join_date: String?,
|
val join_date: String?,
|
||||||
@ -205,7 +204,6 @@ class OsuApiModels {
|
|||||||
data class Beatmap(
|
data class Beatmap(
|
||||||
val beatmapset_id: Int,
|
val beatmapset_id: Int,
|
||||||
val difficulty_rating: Double?,
|
val difficulty_rating: Double?,
|
||||||
val checksum: String?,
|
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val version: String?,
|
val version: String?,
|
||||||
val beatmapset: BeatmapSet,
|
val beatmapset: BeatmapSet,
|
||||||
@ -223,7 +221,6 @@ class OsuApiModels {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BeatmapSet(
|
data class BeatmapSet(
|
||||||
val id: Int,
|
|
||||||
val artist: String?,
|
val artist: String?,
|
||||||
val creator: String?,
|
val creator: String?,
|
||||||
val source: String?,
|
val source: String?,
|
||||||
@ -235,10 +232,4 @@ class OsuApiModels {
|
|||||||
val content: String
|
val content: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class BeatmapPlaycount(
|
|
||||||
val beatmap_id: Int,
|
|
||||||
val count: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,28 +0,0 @@
|
|||||||
package com.nisemoe.nise.osu
|
|
||||||
|
|
||||||
fun OsuApiModels.Beatmap.toScoreBeatmap(): OsuApiModels.ScoreBeatmap =
|
|
||||||
OsuApiModels.ScoreBeatmap(
|
|
||||||
id = this.id,
|
|
||||||
checksum = this.checksum,
|
|
||||||
difficulty_rating = this.difficulty_rating,
|
|
||||||
version = this.version,
|
|
||||||
max_combo = this.max_combo,
|
|
||||||
total_length = this.total_length,
|
|
||||||
bpm = this.bpm,
|
|
||||||
accuracy = this.accuracy,
|
|
||||||
ar = this.ar,
|
|
||||||
cs = this.cs,
|
|
||||||
drain = this.drain,
|
|
||||||
count_circles = this.count_circles,
|
|
||||||
count_sliders = this.count_sliders,
|
|
||||||
count_spinners = this.count_spinners,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun OsuApiModels.BeatmapSet.toScoreBeatmapSet(): OsuApiModels.ScoreBeatmapset =
|
|
||||||
OsuApiModels.ScoreBeatmapset(
|
|
||||||
id = this.id,
|
|
||||||
title = this.title,
|
|
||||||
artist = this.artist,
|
|
||||||
creator = this.creator,
|
|
||||||
source = this.source,
|
|
||||||
)
|
|
||||||
@ -3,8 +3,6 @@ package com.nisemoe.nise.scheduler
|
|||||||
import com.nisemoe.generated.tables.records.ScoresRecord
|
import com.nisemoe.generated.tables.records.ScoresRecord
|
||||||
import com.nisemoe.generated.tables.references.*
|
import com.nisemoe.generated.tables.references.*
|
||||||
import com.nisemoe.nise.UserQueueDetails
|
import com.nisemoe.nise.UserQueueDetails
|
||||||
import com.nisemoe.nise.database.ReplayCacheReplay
|
|
||||||
import com.nisemoe.nise.database.ReplayCacheService
|
|
||||||
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
|
||||||
@ -13,7 +11,9 @@ import com.nisemoe.nise.integrations.DiscordService
|
|||||||
import com.nisemoe.nise.konata.Replay
|
import com.nisemoe.nise.konata.Replay
|
||||||
import com.nisemoe.nise.konata.ReplaySetComparison
|
import com.nisemoe.nise.konata.ReplaySetComparison
|
||||||
import com.nisemoe.nise.konata.compareReplaySet
|
import com.nisemoe.nise.konata.compareReplaySet
|
||||||
import com.nisemoe.nise.osu.*
|
import com.nisemoe.nise.osu.Mod
|
||||||
|
import com.nisemoe.nise.osu.OsuApi
|
||||||
|
import com.nisemoe.nise.osu.OsuApiModels
|
||||||
import com.nisemoe.nise.service.CacheService
|
import com.nisemoe.nise.service.CacheService
|
||||||
import com.nisemoe.nise.service.CompressReplay
|
import com.nisemoe.nise.service.CompressReplay
|
||||||
import com.nisemoe.nise.service.UpdateUserQueueService
|
import com.nisemoe.nise.service.UpdateUserQueueService
|
||||||
@ -36,7 +36,6 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import java.util.Base64
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RestController
|
@RestController
|
||||||
@ -50,10 +49,8 @@ class ImportScores(
|
|||||||
private val scoreService: ScoreService,
|
private val scoreService: ScoreService,
|
||||||
private val updateUserQueueService: UpdateUserQueueService,
|
private val updateUserQueueService: UpdateUserQueueService,
|
||||||
private val circleguardService: CircleguardService,
|
private val circleguardService: CircleguardService,
|
||||||
private val messagingTemplate: SimpMessagingTemplate,
|
private val messagingTemplate: SimpMessagingTemplate
|
||||||
private val replayCacheService: ReplayCacheService,
|
|
||||||
) : InitializingBean {
|
) : InitializingBean {
|
||||||
val replayCacheEnabled = (System.getenv("REPLAY_CACHE_ENABLED") ?: "0") != "0"
|
|
||||||
|
|
||||||
private val userToUpdateBucket = mutableListOf<Long>()
|
private val userToUpdateBucket = mutableListOf<Long>()
|
||||||
|
|
||||||
@ -162,93 +159,39 @@ class ImportScores(
|
|||||||
this.logger.info("Processing ${queue.size} users from the queue.")
|
this.logger.info("Processing ${queue.size} users from the queue.")
|
||||||
}
|
}
|
||||||
|
|
||||||
for(queueEntry in queue) {
|
for(userId in queue) {
|
||||||
val userId = queueEntry.userId
|
val topUserScores = this.osuApi.getTopUserScores(userId = userId)
|
||||||
|
val recentUserScores = this.osuApi.getTopUserScores(userId = userId, type = "recent")
|
||||||
// We should only 'full fetch' a user if they have been explicitly added by another user,
|
val firstPlaceUserScores = this.osuApi.getTopUserScores(userId = userId, type = "firsts")
|
||||||
// else we will spend way too much time on random users.
|
|
||||||
val shouldFullFetch = queueEntry.addedByUserId != null
|
|
||||||
|
|
||||||
val user = this.osuApi.getUserProfile(userId.toString())
|
|
||||||
|
|
||||||
if (user == null) {
|
|
||||||
this.logger.error("Failed to fetch user from queue $userId")
|
|
||||||
this.updateUserQueueService.setUserAsProcessed(userId, failed = true)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var userScores = mutableListOf<OsuApiModels.Score>()
|
|
||||||
|
|
||||||
if (shouldFullFetch && user.beatmap_playcounts_count != null) {
|
|
||||||
val mapsPlayed: MutableSet<Int> = mutableSetOf()
|
|
||||||
|
|
||||||
this.logger.info("User has ${user.beatmap_playcounts_count} unique beatmap plays")
|
|
||||||
|
|
||||||
for (page in 1..(user.beatmap_playcounts_count / 50) + 1) {
|
|
||||||
val maps = this.osuApi.getUserMostPlayed(userId, 50, 50 * page)
|
|
||||||
?: break
|
|
||||||
|
|
||||||
mapsPlayed.addAll(maps.map { it.beatmap_id })
|
|
||||||
|
|
||||||
this.logger.info("Page: $page/${(user.beatmap_playcounts_count / 50) + 1}")
|
|
||||||
|
|
||||||
Thread.sleep(SLEEP_AFTER_API_CALL)
|
|
||||||
}
|
|
||||||
|
|
||||||
var scoreProcessCount = 0
|
|
||||||
for (mapId in mapsPlayed) {
|
|
||||||
val scores = this.osuApi.getUserBeatmapScores(userId, mapId)
|
|
||||||
?: continue
|
|
||||||
|
|
||||||
for (mapScore in scores.scores) {
|
|
||||||
if (mapScore.replay && mapScore.id != null) {
|
|
||||||
val beatmap = this.osuApi.getBeatmapFromId(mapId)
|
|
||||||
?: continue
|
|
||||||
|
|
||||||
userScores.add(mapScore.copy(
|
|
||||||
beatmap = beatmap.toScoreBeatmap(),
|
|
||||||
beatmapset = beatmap.beatmapset.toScoreBeatmapSet(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info(
|
|
||||||
"Getting all user scores for $userId: Processed map scores ${++scoreProcessCount}/${mapsPlayed.size}"
|
|
||||||
)
|
|
||||||
|
|
||||||
Thread.sleep(SLEEP_AFTER_API_CALL)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
val topUserScores = this.osuApi.getTopUserScores(userId = userId)
|
|
||||||
val recentUserScores = this.osuApi.getTopUserScores(userId = userId, type = "recent")
|
|
||||||
val firstPlaceUserScores = this.osuApi.getTopUserScores(userId = userId, type = "firsts")
|
|
||||||
|
|
||||||
if (topUserScores == null || recentUserScores == null || firstPlaceUserScores == null) {
|
|
||||||
this.logger.error("Failed to fetch top scores for user with id = $userId")
|
|
||||||
this.updateUserQueueService.setUserAsProcessed(userId, failed = true)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
userScores += (topUserScores + recentUserScores + firstPlaceUserScores)
|
|
||||||
|
|
||||||
userScores = userScores
|
|
||||||
.filter { it.beatmap != null && it.beatmapset != null }
|
|
||||||
.distinctBy { it.best_id }
|
|
||||||
.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info("Processing user with id = $userId")
|
this.logger.info("Processing user with id = $userId")
|
||||||
this.logger.info("User has ${userScores.size} total scores")
|
this.logger.info("Top scores: ${topUserScores?.size}")
|
||||||
|
this.logger.info("Recent scores: ${recentUserScores?.size}")
|
||||||
|
this.logger.info("First place scores: ${firstPlaceUserScores?.size}")
|
||||||
|
|
||||||
Thread.sleep(SLEEP_AFTER_API_CALL)
|
Thread.sleep(SLEEP_AFTER_API_CALL)
|
||||||
|
|
||||||
this.logger.info("Unique scores: ${userScores.size}")
|
if(topUserScores == null || recentUserScores == null || firstPlaceUserScores == null) {
|
||||||
|
this.logger.error("Failed to fetch top scores for user with id = $userId")
|
||||||
|
this.updateUserQueueService.setUserAsProcessed(userId, failed = true)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val allUserScores = (topUserScores + recentUserScores + firstPlaceUserScores)
|
||||||
|
.filter { it.beatmap != null && it.beatmapset != null }
|
||||||
|
.distinctBy { it.best_id }
|
||||||
|
|
||||||
|
this.logger.info("Unique scores: ${allUserScores.size}")
|
||||||
|
|
||||||
val userExists = dslContext.fetchExists(USERS, USERS.USER_ID.eq(userId), USERS.SYS_LAST_UPDATE.greaterOrEqual(OffsetDateTime.now(ZoneOffset.UTC).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) {
|
if(!userExists) {
|
||||||
this.userService.insertApiUser(user)
|
val apiUser = this.osuApi.getUserProfile(userId = userId.toString(), mode = "osu", key = "id")
|
||||||
this.statistics.usersAddedToDatabase++
|
if(apiUser != null) {
|
||||||
|
this.userService.insertApiUser(apiUser)
|
||||||
|
this.statistics.usersAddedToDatabase++
|
||||||
|
} else {
|
||||||
|
this.logger.error("Failed to fetch user with id = $userId")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var current = 0
|
var current = 0
|
||||||
@ -261,7 +204,7 @@ class ImportScores(
|
|||||||
.limit(1)
|
.limit(1)
|
||||||
.fetchOneInto(OffsetDateTime::class.java)
|
.fetchOneInto(OffsetDateTime::class.java)
|
||||||
|
|
||||||
for(topScore in userScores) {
|
for(topScore in allUserScores) {
|
||||||
val beatmapExists = dslContext.fetchExists(BEATMAPS, BEATMAPS.BEATMAP_ID.eq(topScore.beatmap!!.id))
|
val beatmapExists = dslContext.fetchExists(BEATMAPS, BEATMAPS.BEATMAP_ID.eq(topScore.beatmap!!.id))
|
||||||
if (!beatmapExists) {
|
if (!beatmapExists) {
|
||||||
val beatmapFile = this.osuApi.getBeatmapFile(beatmapId = topScore.beatmap.id)
|
val beatmapFile = this.osuApi.getBeatmapFile(beatmapId = topScore.beatmap.id)
|
||||||
@ -316,7 +259,7 @@ class ImportScores(
|
|||||||
// Update the database
|
// Update the database
|
||||||
dslContext.update(UPDATE_USER_QUEUE)
|
dslContext.update(UPDATE_USER_QUEUE)
|
||||||
.set(UPDATE_USER_QUEUE.PROGRESS_CURRENT, current)
|
.set(UPDATE_USER_QUEUE.PROGRESS_CURRENT, current)
|
||||||
.set(UPDATE_USER_QUEUE.PROGRESS_TOTAL, userScores.size)
|
.set(UPDATE_USER_QUEUE.PROGRESS_TOTAL, allUserScores.size)
|
||||||
.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()
|
||||||
@ -326,7 +269,7 @@ class ImportScores(
|
|||||||
lastCompletedUpdate = lastCompletedUpdate,
|
lastCompletedUpdate = lastCompletedUpdate,
|
||||||
canUpdate = false,
|
canUpdate = false,
|
||||||
progressCurrent = current,
|
progressCurrent = current,
|
||||||
progressTotal = userScores.size
|
progressTotal = allUserScores.size
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update the frontend
|
// Update the frontend
|
||||||
@ -341,7 +284,7 @@ class ImportScores(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for stolen replays.
|
// Check for stolen replays.
|
||||||
val uniqueBeatmapIds = userScores
|
val uniqueBeatmapIds = allUserScores
|
||||||
.groupBy { it.beatmap!!.id }
|
.groupBy { it.beatmap!!.id }
|
||||||
|
|
||||||
this.logger.info("Checking similarity for ${uniqueBeatmapIds.size} beatmaps.")
|
this.logger.info("Checking similarity for ${uniqueBeatmapIds.size} beatmaps.")
|
||||||
@ -841,24 +784,6 @@ class ImportScores(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replayCacheEnabled) {
|
|
||||||
// Insert into replay cache
|
|
||||||
val replayCacheReplay = ReplayCacheReplay(
|
|
||||||
score.best_id,
|
|
||||||
beatmapId,
|
|
||||||
score.user_id.toInt(),
|
|
||||||
Base64.getDecoder().decode(scoreReplay.content),
|
|
||||||
Mod.combineModStrings(score.mods),
|
|
||||||
)
|
|
||||||
val replayCacheInsertSuccess = replayCacheService.insertReplay(replayCacheReplay)
|
|
||||||
|
|
||||||
if (replayCacheInsertSuccess) {
|
|
||||||
logger.info("Inserted replay ${score.id} into replay cache")
|
|
||||||
} else {
|
|
||||||
logger.error("Could not insert replay ${score.id} into replay cache")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.statistics.scoresWithReplayAndAnalyzed++
|
this.statistics.scoresWithReplayAndAnalyzed++
|
||||||
|
|
||||||
if (scoreId == null) {
|
if (scoreId == null) {
|
||||||
|
|||||||
@ -79,11 +79,12 @@ class UpdateUserQueueService(
|
|||||||
/**
|
/**
|
||||||
* Retrieves the full update queue, only pending users.
|
* Retrieves the full update queue, only pending users.
|
||||||
*/
|
*/
|
||||||
fun getQueue(): List<UpdateUserQueueRecord> {
|
fun getQueue(): List<Long> {
|
||||||
return dslContext.selectFrom(UPDATE_USER_QUEUE)
|
return dslContext.select(UPDATE_USER_QUEUE.USER_ID)
|
||||||
|
.from(UPDATE_USER_QUEUE)
|
||||||
.where(UPDATE_USER_QUEUE.PROCESSED.isFalse)
|
.where(UPDATE_USER_QUEUE.PROCESSED.isFalse)
|
||||||
.orderBy(UPDATE_USER_QUEUE.CREATED_AT.asc())
|
.orderBy(UPDATE_USER_QUEUE.CREATED_AT.asc())
|
||||||
.fetch()
|
.fetchInto(Long::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
spring.datasource.url=jdbc:postgresql://${POSTGRES_HOST:postgres}:${POSTGRES_PORT:5432}/${POSTGRES_DB:postgres}?currentSchema=public
|
||||||
|
spring.datasource.username=${POSTGRES_USER:postgres}
|
||||||
|
spring.datasource.password=${POSTGRES_PASS:postgres}
|
||||||
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
spring.datasource.name=HikariPool-PostgreSQL
|
||||||
|
|
||||||
|
spring.flyway.enabled=${FLYWAY_ENABLED:true}
|
||||||
|
spring.flyway.schemas=public
|
||||||
|
|
||||||
|
# Batching
|
||||||
|
spring.datasource.hikari.data-source-properties.prepStmtCacheSize=250
|
||||||
|
spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=2048
|
||||||
|
spring.datasource.hikari.data-source-properties.useServerPrepStmts=true
|
||||||
|
spring.datasource.hikari.data-source-properties.rewriteBatchedStatements=true
|
||||||
@ -32,25 +32,4 @@ spring.security.oauth2.client.registration.osu.provider=osu
|
|||||||
spring.security.oauth2.client.provider.osu.authorization-uri=https://osu.ppy.sh/oauth/authorize
|
spring.security.oauth2.client.provider.osu.authorization-uri=https://osu.ppy.sh/oauth/authorize
|
||||||
spring.security.oauth2.client.provider.osu.token-uri=https://osu.ppy.sh/oauth/token
|
spring.security.oauth2.client.provider.osu.token-uri=https://osu.ppy.sh/oauth/token
|
||||||
spring.security.oauth2.client.provider.osu.user-info-uri=https://osu.ppy.sh/api/v2/me/osu
|
spring.security.oauth2.client.provider.osu.user-info-uri=https://osu.ppy.sh/api/v2/me/osu
|
||||||
spring.security.oauth2.client.provider.osu.user-name-attribute=username
|
spring.security.oauth2.client.provider.osu.user-name-attribute=username
|
||||||
|
|
||||||
spring.datasource.nise.jdbcUrl=jdbc:postgresql://${POSTGRES_HOST:postgres}:${POSTGRES_PORT:5432}/${POSTGRES_DB:postgres}?currentSchema=public
|
|
||||||
spring.datasource.nise.username=${POSTGRES_USER:postgres}
|
|
||||||
spring.datasource.nise.password=${POSTGRES_PASS:postgres}
|
|
||||||
spring.datasource.nise.driver-class-name=org.postgresql.Driver
|
|
||||||
spring.datasource.nise.name=HikariPool-PostgreSQL
|
|
||||||
|
|
||||||
spring.datasource.replay-cache.jdbcUrl=jdbc:postgresql://${REPLAY_CACHE_HOST:postgres}:${REPLAY_CACHE_PORT:5433}/${REPLAY_CACHE_DB:REPLAY_CACHE}?currentSchema=public
|
|
||||||
spring.datasource.replay-cache.username=${REPLAY_CACHE_USER:postgres}
|
|
||||||
spring.datasource.replay-cache.password=${REPLAY_CACHE_PASS:postgres}
|
|
||||||
spring.datasource.replay-cache.driver-class-name=org.postgresql.Driver
|
|
||||||
spring.datasource.replay-cache.name=HikariPool-PostgreSQL
|
|
||||||
|
|
||||||
spring.flyway.enabled=${FLYWAY_ENABLED:true}
|
|
||||||
spring.flyway.schemas=public
|
|
||||||
|
|
||||||
# Batching
|
|
||||||
spring.datasource.hikari.data-source-properties.prepStmtCacheSize=250
|
|
||||||
spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=2048
|
|
||||||
spring.datasource.hikari.data-source-properties.useServerPrepStmts=true
|
|
||||||
spring.datasource.hikari.data-source-properties.rewriteBatchedStatements=true
|
|
||||||
@ -1,13 +1,16 @@
|
|||||||
FROM python:3.11.8-slim
|
FROM python:3.11.8-slim
|
||||||
|
|
||||||
ENV version=2
|
ENV version=2
|
||||||
ENV PYTHONPATH=/app
|
ENV PYTHONPATH /app
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt update
|
||||||
|
|
||||||
COPY requirements.txt ./requirements.txt
|
COPY requirements.txt ./requirements.txt
|
||||||
|
|
||||||
RUN pip3 install --no-cache-dir -r requirements.txt
|
RUN pip3 install --upgrade pip && \
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
|
||||||
# This is *really* bad, but I'd rather get this working rather than forking packages and re-publishing them.
|
# This is *really* bad, but I'd rather get this working rather than forking packages and re-publishing them.
|
||||||
# It'll probably break some day.
|
# It'll probably break some day.
|
||||||
@ -19,6 +22,7 @@ RUN sed -i '238s|return \[x for x in arr if lower_limit < x < upper_limit\]|arr_
|
|||||||
|
|
||||||
COPY ./src/ ./src/
|
COPY ./src/ ./src/
|
||||||
|
|
||||||
WORKDIR /app/src
|
ENV GUNICORN_CMD_ARGS="--bind=0.0.0.0:5000 --workers=16"
|
||||||
|
|
||||||
CMD ["python", "main.py"]
|
# Run gunicorn with the application
|
||||||
|
CMD ["gunicorn", "--chdir", "src", "main:app"]
|
||||||
@ -1,4 +1,5 @@
|
|||||||
ossapi==3.4.3
|
ossapi==3.4.3
|
||||||
circleguard==5.4.2
|
circleguard==5.4.1
|
||||||
|
flask==3.0.2
|
||||||
brparser==1.0.4
|
brparser==1.0.4
|
||||||
sanic==24.6.0
|
gunicorn==21.2.0
|
||||||
@ -5,21 +5,21 @@ from dataclasses import dataclass, asdict
|
|||||||
from typing import List, Iterable
|
from typing import List, Iterable
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from sanic import Request, Sanic, exceptions, json
|
|
||||||
import scipy
|
import scipy
|
||||||
from brparser import Replay, BeatmapOsu, Mod
|
from brparser import Replay, BeatmapOsu, Mod
|
||||||
from circleguard import Circleguard, ReplayString, Hit
|
from circleguard import Circleguard, ReplayString, Hit
|
||||||
|
from flask import Flask, request, jsonify, abort
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
from math import isnan
|
from math import isnan
|
||||||
from slider import Beatmap, Circle, Slider, Spinner
|
from slider import Beatmap, Circle, Slider, Spinner
|
||||||
|
|
||||||
from WriteStreamWrapper import WriteStreamWrapper
|
from src.WriteStreamWrapper import WriteStreamWrapper
|
||||||
from keypresses import get_kp_sliders
|
from src.keypresses import get_kp_sliders
|
||||||
|
|
||||||
# Circleguard
|
# Circleguard
|
||||||
cg = Circleguard(os.getenv("OSU_API_KEY"), db_path="./dbs/db.db", slider_dir="./dbs/")
|
cg = Circleguard(os.getenv("OSU_API_KEY"), db_path="./dbs/db.db", slider_dir="./dbs/")
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
def my_filter_outliers(arr, bias=1.5):
|
def my_filter_outliers(arr, bias=1.5):
|
||||||
"""
|
"""
|
||||||
@ -123,11 +123,11 @@ class ScoreJudgement:
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/replay")
|
@app.post("/replay")
|
||||||
async def process_replay(request: Request):
|
def process_replay():
|
||||||
try:
|
try:
|
||||||
request_data = request.json
|
request_data = request.get_json()
|
||||||
if not request_data:
|
if not request_data:
|
||||||
raise exceptions.BadRequest("Bad Request: No JSON data provided.")
|
abort(400, description="Bad Request: No JSON data provided.")
|
||||||
|
|
||||||
replay_request = ReplayRequest.from_dict(request_data)
|
replay_request = ReplayRequest.from_dict(request_data)
|
||||||
|
|
||||||
@ -219,10 +219,10 @@ async def process_replay(request: Request):
|
|||||||
|
|
||||||
judgements=judgements
|
judgements=judgements
|
||||||
)
|
)
|
||||||
return json(ur_response.to_dict())
|
return jsonify(ur_response.to_dict())
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise exceptions.BadRequest(str(e))
|
abort(400, description=str(e))
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -242,11 +242,11 @@ class ReplayDto:
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/similarity")
|
@app.post("/similarity")
|
||||||
async def process_similarity(request: Request):
|
def process_similarity():
|
||||||
try:
|
try:
|
||||||
request_data = request.json
|
request_data = request.get_json()
|
||||||
if not request_data:
|
if not request_data:
|
||||||
raise exceptions.BadRequest("Bad Request: No JSON data provided.")
|
abort(400, description="Bad Request: No JSON data provided.")
|
||||||
|
|
||||||
replays: List[ReplayDto] = request_data['replays']
|
replays: List[ReplayDto] = request_data['replays']
|
||||||
replay_cache = {}
|
replay_cache = {}
|
||||||
@ -287,10 +287,11 @@ async def process_similarity(request: Request):
|
|||||||
)
|
)
|
||||||
response.append(new_score_similarity)
|
response.append(new_score_similarity)
|
||||||
|
|
||||||
return json({'result': response})
|
return jsonify({'result': response})
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise exceptions.BadRequest(str(e))
|
abort(400, description=str(e))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(host='0.0.0.0', port=5000)
|
if __name__ == "__main__":
|
||||||
|
app.run(host='0.0.0.0', debug=False)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
FROM nginx:1.27.0-alpine
|
FROM nginx:1.27.0
|
||||||
|
|
||||||
RUN rm -rf /usr/share/nginx/html/*
|
RUN rm -rf /usr/share/nginx/html/*
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const routes: Routes = [
|
|||||||
|
|
||||||
{path: 'neko', component: MetabaseComponent, title: 'metabase integration'},
|
{path: 'neko', component: MetabaseComponent, title: 'metabase integration'},
|
||||||
|
|
||||||
{path: '**', component: HomeComponent, title: '/nise.stedos.dev/'},
|
{path: '**', component: HomeComponent, title: '/nise.moe/'},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@ -5,12 +5,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 style="margin-top: 6px">/nise.stedos.dev/</h2>
|
<h2 style="margin-top: 6px">/nise.moe/</h2>
|
||||||
<ul style="font-size: 15px; line-height: 19px;">
|
<ul style="font-size: 15px; line-height: 19px;">
|
||||||
<li><a [routerLink]="['/']">./home</a></li>
|
<li><a [routerLink]="['/']">./home</a></li>
|
||||||
<li><a [routerLink]="['/sus']">./suspicious-scores</a></li>
|
<li><a [routerLink]="['/sus']">./suspicious-scores</a></li>
|
||||||
<li><a [routerLink]="['/stolen']">./stolen-replays</a></li>
|
<li><a [routerLink]="['/stolen']">./stolen-replays</a></li>
|
||||||
<li><a [routerLink]="['/search']">./advanced-search</a></li>
|
<li><a [routerLink]="['/search']">./advanced-search</a></li>
|
||||||
|
<li *ngIf="this.userService.ephemeralUserInfo.showContributions"><a class="link-pink" [routerLink]="['/contribute']">./contribute ♥</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<form (ngSubmit)="onSubmit()">
|
<form (ngSubmit)="onSubmit()">
|
||||||
<input style="width: 100%" type="text" [(ngModel)]="term" [ngModelOptions]="{standalone: true}" id="nise-osu-username" required minlength="2" maxlength="50" placeholder="Search for users...">
|
<input style="width: 100%" type="text" [(ngModel)]="term" [ngModelOptions]="{standalone: true}" id="nise-osu-username" required minlength="2" maxlength="50" placeholder="Search for users...">
|
||||||
@ -34,5 +35,5 @@
|
|||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
<div class="text-center version">
|
<div class="text-center version">
|
||||||
v20250213
|
v20240511
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,15 +2,20 @@
|
|||||||
|
|
||||||
<div class="subcontainer">
|
<div class="subcontainer">
|
||||||
<div class="term">
|
<div class="term">
|
||||||
<h1># Welcome to [nise.stedos.dev] (formerly nise.moe)</h1>
|
<h1># Welcome to [nise.moe]</h1>
|
||||||
<h3>wtf is this?</h3>
|
<h3>wtf is this?</h3>
|
||||||
<p>This application will automatically crawl [osu!std] top scores and search for stolen replays or obvious relax/timewarp scores.</p>
|
<p>This application will automatically crawl [osu!std] top scores and search for stolen replays or obvious relax/timewarp scores.</p>
|
||||||
<p>It is currently in an <i>ALPHA</i> state and will not persist any data. This will change soon.</p>
|
<p>It started collecting replays on <i>2024-01-12</i></p>
|
||||||
<p>This website is not affiliated with the osu! game nor ppy. It is an unrelated, unaffiliated, 3rd party project.</p>
|
<p>This website is not affiliated with the osu! game nor ppy. It is an unrelated, unaffiliated, 3rd party project.</p>
|
||||||
|
<p>If you have any suggestions or want to report bugs, feel free to join the Discord server below.</p>
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<a href="https://discord.gg/wn4gWpA36w" target="_blank" class="btn">Join the Discord!</a>
|
||||||
|
<a [routerLink]="['/docs']" class="btn api-docs-btn" style="margin-left: 20px;">Check out the /api/ docs.</a>
|
||||||
|
</div>
|
||||||
<h3 class="mt-4"># do you use rss? (nerd)</h3>
|
<h3 class="mt-4"># do you use rss? (nerd)</h3>
|
||||||
<p>you can keep up with newly detected scores with the rss feed, subscribe to it using your favorite reader.</p>
|
<p>you can keep up with newly detected scores with the rss feed, subscribe to it using your favorite reader.</p>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a href="https://nise.stedos.dev/api/rss.xml" target="_blank">
|
<a href="https://nise.moe/api/rss.xml" target="_blank">
|
||||||
<img title="rss-chan!" src="/assets/rss.png" width="64" style="filter: grayscale(40%) sepia(10%) brightness(90%);">
|
<img title="rss-chan!" src="/assets/rss.png" width="64" style="filter: grayscale(40%) sepia(10%) brightness(90%);">
|
||||||
<br>
|
<br>
|
||||||
<span style="padding: 2px; border: 1px dotted #b3b8c3;">
|
<span style="padding: 2px; border: 1px dotted #b3b8c3;">
|
||||||
|
|||||||
@ -1,16 +1,12 @@
|
|||||||
import {UserDetails} from './userDetails';
|
import {UserDetails} from './userDetails';
|
||||||
import {SimilarReplay, SuspiciousScore} from './replays';
|
import {SimilarReplay, SuspiciousScore} from './replays';
|
||||||
import {environment} from "../environments/environment";
|
|
||||||
|
|
||||||
export class TextReportService {
|
export class TextReportService {
|
||||||
|
|
||||||
static generateTextReportForUserScores(
|
static generateTextReportForUserScores(
|
||||||
userDetails: UserDetails,
|
userDetails: UserDetails,
|
||||||
suspiciousScores: SuspiciousScore[],
|
suspiciousScores: SuspiciousScore[],
|
||||||
similarReplays: SimilarReplay[],
|
similarReplays: SimilarReplay[],
|
||||||
) {
|
) {
|
||||||
const site = 'nise.stedos.dev';
|
|
||||||
|
|
||||||
const detections: string[] = [];
|
const detections: string[] = [];
|
||||||
|
|
||||||
if (suspiciousScores.length > 0) {
|
if (suspiciousScores.length > 0) {
|
||||||
@ -25,29 +21,26 @@ export class TextReportService {
|
|||||||
report += `Profile: https://osu.ppy.sh/users/${userDetails.user_id}\n`;
|
report += `Profile: https://osu.ppy.sh/users/${userDetails.user_id}\n`;
|
||||||
|
|
||||||
for (const suspiciousScore of suspiciousScores) {
|
for (const suspiciousScore of suspiciousScores) {
|
||||||
report += `\n\n${this.getRelaxReport(suspiciousScore)}\n`;
|
report += `\n${this.getRelaxReport(suspiciousScore)}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const similarReplay of similarReplays) {
|
for (const similarReplay of similarReplays) {
|
||||||
report += `\n\n${this.getStealingReport(similarReplay)}\n`;
|
report += `\n${this.getStealingReport(similarReplay)}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
report += `\n\nGenerated on ${site} - [${userDetails.username} on ${site}](${environment.webUrl}/u/${userDetails.user_id})`;
|
report += `\nGenerated on nise.moe - [${userDetails.username} on nise.moe](https://nise.moe/u/${userDetails.user_id})`;
|
||||||
|
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getRelaxReport(suspiciousScore: SuspiciousScore): string {
|
private static getRelaxReport(suspiciousScore: SuspiciousScore): string {
|
||||||
return `[Replay on ${suspiciousScore.beatmap_title}](https://osu.ppy.sh/scores/osu/${suspiciousScore.replay_id})
|
return `[Replay on ${suspiciousScore.beatmap_title}](https://osu.ppy.sh/scores/osu/${suspiciousScore.replay_id})
|
||||||
|
|
||||||
cvUR: ${suspiciousScore.ur.toFixed(2)} according to Circleguard`;
|
cvUR: ${suspiciousScore.ur.toFixed(2)} according to Circleguard`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getStealingReport(similarReplay: SimilarReplay): string {
|
private static getStealingReport(similarReplay: SimilarReplay): string {
|
||||||
return `[${similarReplay.username_2}'s replay (cheated)](https://osu.ppy.sh/scores/osu/${similarReplay.replay_id_2})
|
return `[${similarReplay.username_2}'s replay (cheated)](https://osu.ppy.sh/scores/osu/${similarReplay.replay_id_2})
|
||||||
|
|
||||||
[${similarReplay.username_1}'s replay (original)](https://osu.ppy.sh/scores/osu/${similarReplay.replay_id_1})
|
[${similarReplay.username_1}'s replay (original)](https://osu.ppy.sh/scores/osu/${similarReplay.replay_id_1})
|
||||||
|
|
||||||
${similarReplay.similarity.toFixed(2)} similarity according to Circleguard`;
|
${similarReplay.similarity.toFixed(2)} similarity according to Circleguard`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,9 +29,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="text-center mt-2">
|
<div class="text-center mt-2">
|
||||||
<a class="btn" [href]="'https://replay.nise.moe/' + this.pair.replays[0].replay_id + '/' + this.pair.replays[1].replay_id" target="_blank">Open in Replay Viewer</a>
|
<a class="btn" [href]="'https://replay.nise.moe/' + this.pair.replays[0].replay_id + '/' + this.pair.replays[1].replay_id" target="_blank">Open in Replay Viewer</a>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
<div class="some-page-wrapper text-center">
|
<div class="some-page-wrapper text-center">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@ -53,9 +53,9 @@
|
|||||||
Open in CircleGuard
|
Open in CircleGuard
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- <a style="flex: 1" class="text-center" [href]="'https://replay.nise.moe/' + this.replayData.replay_id" target="_blank" [class.disabled]="!hasReplay()">
|
<a style="flex: 1" class="text-center" [href]="'https://replay.nise.moe/' + this.replayData.replay_id" target="_blank" [class.disabled]="!hasReplay()">
|
||||||
Open in Replay Viewer
|
Open in Replay Viewer
|
||||||
</a> -->
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<div class="main term">
|
<div class="main term">
|
||||||
<h1><span class="board">/sus/</span> - Suspicious Scores</h1>
|
<h1><span class="board">/sus/</span> - Suspicious Scores</h1>
|
||||||
<div class="alert mb-2">
|
<div class="alert mb-2">
|
||||||
This includes all replays with <35 cvUR. Low values can indicate cheating but always manually review users and
|
This includes all replays with <25 cvUR. Low values can indicate cheating but always manually review users and
|
||||||
replays before making judgements.
|
replays before making judgements.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<input class="form-control" type="number" id="maxPP" [(ngModel)]="this.filterManager.filters.maxPP" (input)="filterScores()"
|
<input class="form-control" type="number" id="maxPP" [(ngModel)]="this.filterManager.filters.maxPP" (input)="filterScores()"
|
||||||
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
[readOnly]="this.isUrlFilters" [disabled]="this.isUrlFilters">
|
||||||
</p>
|
</p>
|
||||||
`
|
|
||||||
<!-- Min cvUR -->
|
<!-- Min cvUR -->
|
||||||
<p>
|
<p>
|
||||||
<label for="minUR" class="form-label">Min cvUR</label>
|
<label for="minUR" class="form-label">Min cvUR</label>
|
||||||
|
|||||||
@ -69,7 +69,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
width: 600px;
|
width: 555px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,8 +23,8 @@ export class ChartHitDistributionComponent implements OnInit, OnChanges {
|
|||||||
@Input() mods!: string[];
|
@Input() mods!: string[];
|
||||||
|
|
||||||
removeOutliers = true;
|
removeOutliers = true;
|
||||||
groupData = false;
|
groupData = true;
|
||||||
showPercentages = false;
|
showPercentages = true;
|
||||||
|
|
||||||
public barChartLegend = true;
|
public barChartLegend = true;
|
||||||
public barChartPlugins = [];
|
public barChartPlugins = [];
|
||||||
|
|||||||
@ -43,8 +43,8 @@ export class ChartComponent implements OnChanges {
|
|||||||
@Input() data!: number[];
|
@Input() data!: number[];
|
||||||
|
|
||||||
removeOutliers = true;
|
removeOutliers = true;
|
||||||
groupData = false;
|
groupData = true;
|
||||||
showPercentages = false;
|
showPercentages = true;
|
||||||
|
|
||||||
calculateStatistics(): Array<{ name: string, value: number }> {
|
calculateStatistics(): Array<{ name: string, value: number }> {
|
||||||
if (this.data.length === 0) {
|
if (this.data.length === 0) {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
webUrl: 'http://localhost:4200',
|
|
||||||
apiUrl: 'http://localhost:8080',
|
apiUrl: 'http://localhost:8080',
|
||||||
wsUrl: 'ws://localhost:8080/websocket',
|
wsUrl: 'ws://localhost:8080/websocket',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
const URL = 'nise.stedos.dev';
|
|
||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
webUrl: `https://${URL}`,
|
apiUrl: 'https://nise.moe/api',
|
||||||
apiUrl: `https://${URL}/api`,
|
wsUrl: 'wss://nise.moe/api/websocket',
|
||||||
wsUrl: `wss://${URL}/api/websocket`,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,14 +6,14 @@
|
|||||||
<title></title>
|
<title></title>
|
||||||
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
||||||
<!-- Embed data -->
|
<!-- Embed data -->
|
||||||
<meta property="og:title" content="/nise.stedos.dev/ - osu!cheaters finder">
|
<meta property="og:title" content="/nise.moe/ - osu!cheaters finder">
|
||||||
<meta property="og:description" content="crawls osu!std replays and tries to find naughty boys.">
|
<meta property="og:description" content="crawls osu!std replays and tries to find naughty boys.">
|
||||||
<meta property="og:url" content="https://nise.stedos.dev">
|
<meta property="og:url" content="https://nise.moe">
|
||||||
<meta property="og:image" content="https://nise.stedos.dev/assets/banner.png">
|
<meta property="og:image" content="https://nise.moe/assets/banner.png">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta name="theme-color" content="#151515">
|
<meta name="theme-color" content="#151515">
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<meta name="twitter:image:src" content="https://nise.stedos.dev/assets/banner.png">
|
<meta name="twitter:image:src" content="https://nise.moe/assets/banner.png">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
nise.stedos.dev {
|
|
||||||
reverse_proxy nise-nginx
|
|
||||||
}
|
|
||||||
@ -1,25 +1,23 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
caddy-main:
|
nginx-main:
|
||||||
image: caddy:alpine
|
image: nginx:latest
|
||||||
container_name: caddy-main
|
container_name: nginx-main
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
- ./nginx-main.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
# nise.moe certificates (by Cloudflare)
|
||||||
|
- ./nise-data/certificate.pem:/etc/ssl/certs/nisemoe/certificate.pem:ro
|
||||||
|
- ./nise-data/private.key:/etc/ssl/certs/nisemoe/private.key:ro
|
||||||
ports:
|
ports:
|
||||||
- "443:443"
|
- "443:443"
|
||||||
- "80:80"
|
- "80:80"
|
||||||
depends_on:
|
|
||||||
- nise-nginx
|
|
||||||
|
|
||||||
# Shared services which are used by others
|
# Shared services which are used by others
|
||||||
redis:
|
|
||||||
image: redis:alpine
|
|
||||||
container_name: redis
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:alpine
|
image: groonga/pgroonga:3.1.6-alpine-15
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
@ -27,6 +25,47 @@ services:
|
|||||||
POSTGRES_PASSWORD: ${DB_PASS}
|
POSTGRES_PASSWORD: ${DB_PASS}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres-data:/var/lib/postgresql/data
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
command: >
|
||||||
|
-c shared_buffers=6GB
|
||||||
|
-c effective_cache_size=12GB
|
||||||
|
-c work_mem=64MB
|
||||||
|
-c maintenance_work_mem=2GB
|
||||||
|
-c checkpoint_completion_target=0.9
|
||||||
|
-c checkpoint_timeout=15min
|
||||||
|
-c max_wal_size=2GB
|
||||||
|
-c wal_buffers=16MB
|
||||||
|
-c max_connections=100
|
||||||
|
-c max_worker_processes=8
|
||||||
|
-c max_parallel_workers_per_gather=4
|
||||||
|
-c max_parallel_workers=8
|
||||||
|
-c effective_io_concurrency=40
|
||||||
|
shm_size: '128mb'
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
container_name: redis
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
gitea:
|
||||||
|
image: gitea/gitea
|
||||||
|
container_name: gitea
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
USER_UID: 1336
|
||||||
|
USER_GID: 1336
|
||||||
|
GITEA__database__DB_TYPE: postgres
|
||||||
|
GITEA__database__HOST: ${DB_HOST}:5432
|
||||||
|
GITEA__database__NAME: gitea
|
||||||
|
GITEA__database__USER: ${DB_USER}
|
||||||
|
GITEA__database__PASSWD: ${DB_PASS}
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
volumes:
|
||||||
|
- ./gitea-data/app.ini:/data/gitea/conf/app.ini
|
||||||
|
- gitea-data:/data
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
@ -36,31 +75,19 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./nise-data/nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./nise-data/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
depends_on:
|
|
||||||
- nise-backend
|
|
||||||
- nise-frontend
|
|
||||||
|
|
||||||
nise-circleguard:
|
|
||||||
image: code.stedos.dev/stedos/nise-circleguard:latest
|
|
||||||
container_name: nise-circleguard
|
|
||||||
environment:
|
|
||||||
OSU_API_KEY: ${OSU_API_KEY}
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
- ./nise-data/beatmaps:/app/dbs
|
|
||||||
|
|
||||||
nise-backend:
|
nise-backend:
|
||||||
image: code.stedos.dev/stedos/nise-backend:latest
|
image: git.nise.moe/nuff/nise-backend:latest
|
||||||
container_name: nise-backend
|
container_name: nise-backend
|
||||||
environment:
|
environment:
|
||||||
SPRING_PROFILES_ACTIVE: postgres,import:scores,import:users,fix:scores
|
SPRING_PROFILES_ACTIVE: postgres,discord,import:scores,import:users,fix:scores
|
||||||
# App configuration
|
# App configuration
|
||||||
OLD_SCORES_PAGE_SIZE: 1000
|
OLD_SCORES_PAGE_SIZE: 1000
|
||||||
# Postgres
|
# Postgres
|
||||||
POSTGRES_HOST: ${DB_HOST}
|
POSTGRES_HOST: ${DB_HOST}
|
||||||
POSTGRES_USER: ${DB_USER}
|
POSTGRES_USER: ${DB_USER}
|
||||||
POSTGRES_PASS: ${DB_PASS}
|
POSTGRES_PASS: ${DB_PASS}
|
||||||
POSTGRES_DB: ${DB_NAME}
|
POSTGRES_DB: nise
|
||||||
# redis
|
# redis
|
||||||
REDIS_DB: 4
|
REDIS_DB: 4
|
||||||
# Discord
|
# Discord
|
||||||
@ -70,36 +97,69 @@ services:
|
|||||||
OSU_API_KEY: ${OSU_API_KEY}
|
OSU_API_KEY: ${OSU_API_KEY}
|
||||||
OSU_CLIENT_ID: ${OSU_CLIENT_ID}
|
OSU_CLIENT_ID: ${OSU_CLIENT_ID}
|
||||||
OSU_CLIENT_SECRET: ${OSU_CLIENT_SECRET}
|
OSU_CLIENT_SECRET: ${OSU_CLIENT_SECRET}
|
||||||
OSU_CALLBACK: "https://nise.stedos.dev/api/login/oauth2/code/osu"
|
OSU_CALLBACK: "https://nise.moe/api/login/oauth2/code/osu"
|
||||||
# Metabase
|
# Metabase
|
||||||
METABASE_API_KEY: ${METABASE_API_KEY}
|
METABASE_API_KEY: ${METABASE_API_KEY}
|
||||||
# Internal API
|
# Internal API
|
||||||
CIRCLEGUARD_API_URL: http://nise-circleguard:5000
|
CIRCLEGUARD_API_URL: http://nise-circleguard:5000
|
||||||
# Auth
|
# Auth
|
||||||
ORIGIN: "https://nise.stedos.dev"
|
ORIGIN: "https://nise.moe"
|
||||||
REPLAY_ORIGIN: "https://replay.nise.moe"
|
REPLAY_ORIGIN: "https://replay.nise.moe"
|
||||||
COOKIE_SECURE: false
|
COOKIE_SECURE: false
|
||||||
BEATMAPS_PATH: "/app/dbs"
|
BEATMAPS_PATH: "/app/dbs"
|
||||||
# Replay cache
|
|
||||||
REPLAY_CACHE_ENABLED: ${REPLAY_CACHE_ENABLED}
|
|
||||||
REPLAY_CACHE_HOST: ${REPLAY_CACHE_HOST}
|
|
||||||
REPLAY_CACHE_PORT: ${REPLAY_CACHE_PORT}
|
|
||||||
REPLAY_CACHE_DB: ${REPLAY_CACHE_DB}
|
|
||||||
REPLAY_CACHE_USER: ${REPLAY_CACHE_USER}
|
|
||||||
REPLAY_CACHE_PASS: ${REPLAY_CACHE_PASS}
|
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./nise-data/beatmaps:/app/dbs
|
- ./nise-data/beatmaps:/app/dbs
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
- nise-circleguard
|
|
||||||
|
|
||||||
nise-frontend:
|
nise-circleguard:
|
||||||
image: code.stedos.dev/stedos/nise-frontend:latest
|
image: git.nise.moe/nuff/nise-circleguard:latest
|
||||||
container_name: nise-frontend
|
container_name: nise-circleguard
|
||||||
|
environment:
|
||||||
|
OSU_API_KEY: ${OSU_API_KEY}
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./nise-data/beatmaps:/app/dbs
|
||||||
|
|
||||||
|
nise-frontend2:
|
||||||
|
image: git.nise.moe/nuff/nise-frontend:latest
|
||||||
|
container_name: nise-frontend2
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
nise-replay-viewer:
|
||||||
|
image: git.nise.moe/nuff/nise-replay-viewer:latest
|
||||||
|
container_name: nise-replay-viewer
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
nise-discord:
|
||||||
|
image: git.nise.moe/nuff/nise-discord:latest
|
||||||
|
container_name: nise-discord
|
||||||
|
environment:
|
||||||
|
DISCORD_TOKEN: ${DISCORD_TOKEN}
|
||||||
|
REACTION_CHANNEL_ID: ${REACTION_CHANNEL_ID}
|
||||||
|
REACTION_EMOJI_ID: ${REACTION_EMOJI_ID}
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
nise-metabase:
|
||||||
|
image: metabase/metabase:latest
|
||||||
|
container_name: nise-metabase
|
||||||
|
volumes:
|
||||||
|
- /dev/urandom:/dev/random:ro
|
||||||
|
environment:
|
||||||
|
MB_DB_TYPE: postgres
|
||||||
|
MB_DB_DBNAME: metabase
|
||||||
|
MB_DB_PORT: 5432
|
||||||
|
MB_DB_USER: ${DB_METABASE_USER}
|
||||||
|
MB_DB_PASS: ${DB_METABASE_PASS}
|
||||||
|
MB_DB_HOST: postgres
|
||||||
|
healthcheck:
|
||||||
|
test: curl --fail -I http://localhost:3000/api/health || exit 1
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
|
gitea-data:
|
||||||
|
|||||||
@ -9,13 +9,13 @@ http {
|
|||||||
# Redirect HTTP to HTTPS
|
# Redirect HTTP to HTTPS
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name nise.stedos.dev;
|
server_name nise.moe replay.nise.moe neko.nise.moe;
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name nise.stedos.dev;
|
server_name nise.moe replay.nise.moe git.nise.moe neko.nise.moe;
|
||||||
|
|
||||||
ssl_certificate /etc/ssl/certs/nisemoe/certificate.pem;
|
ssl_certificate /etc/ssl/certs/nisemoe/certificate.pem;
|
||||||
ssl_certificate_key /etc/ssl/certs/nisemoe/private.key;
|
ssl_certificate_key /etc/ssl/certs/nisemoe/private.key;
|
||||||
|
|||||||
@ -2,17 +2,29 @@ events {}
|
|||||||
|
|
||||||
http {
|
http {
|
||||||
|
|
||||||
|
upstream gitea {
|
||||||
|
server gitea:3000;
|
||||||
|
}
|
||||||
|
|
||||||
upstream nise-frontend {
|
upstream nise-frontend {
|
||||||
server nise-frontend:80;
|
server nise-frontend2:80;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream nise-replay-viewer {
|
||||||
|
server nise-replay-viewer:80;
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream nise-backend {
|
upstream nise-backend {
|
||||||
server nise-backend:8080;
|
server nise-backend:8080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upstream nise-metabase {
|
||||||
|
server nise-metabase:3000;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name nise.stedos.dev;
|
server_name nise.moe;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://nise-frontend;
|
proxy_pass http://nise-frontend;
|
||||||
@ -37,4 +49,46 @@ http {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
server_name git.nise.moe;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
client_max_body_size 10G;
|
||||||
|
proxy_pass http://gitea/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name replay.nise.moe;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://nise-replay-viewer/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name neko.nise.moe;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://nise-metabase/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user