Compare commits

...

10 Commits

Author SHA1 Message Date
38e997cba2 Merge pull request 'Sync sansei branch' (#1) from sansei into main
Reviewed-on: #1
2025-02-24 21:12:27 +00:00
Stedoss
792b203255 Add mods_bitwise to score endpoints 2025-02-17 07:46:20 +00:00
Stedoss
d6d5953a44 Add version endpoint for backend 2025-02-13 22:39:23 +00:00
Stedoss
d0c4964caa Update FE version number 2025-02-13 22:23:58 +00:00
Stedoss
9d33c64949 Suspicious scores are now returned when cvur is <35 2025-02-13 22:23:46 +00:00
Stedoss
aadada2084 Add basic health check endpoint 2025-02-13 00:09:15 +00:00
Stedoss
559124460d Use alpine nginx image for frontend container 2025-01-18 23:59:30 +00:00
Stedoss
eb7f9e1e23 Bump frontend version 2025-01-18 23:49:54 +00:00
Stedoss
69829518a1 Make text reporting more Reddit friendly 2025-01-18 23:48:37 +00:00
Stedoss
e0a7cbbfcb Sync main docker compose 2024-12-20 17:48:19 +00:00
10 changed files with 98 additions and 104 deletions

View File

@ -141,6 +141,7 @@ 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?,

View File

@ -0,0 +1,22 @@
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)
}
}

View File

@ -0,0 +1,19 @@
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)
}

View File

@ -177,6 +177,8 @@ 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),
@ -204,7 +206,8 @@ 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 = Mod.parseModCombination(result.get(SCORES.MODS, Int::class.java)), mods_bitwise = mods,
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),
@ -232,7 +235,7 @@ class ScoreService(
} }
fun getDefaultCondition(): Condition { fun getDefaultCondition(): Condition {
return SCORES.UR.lessOrEqual(25.0) return SCORES.UR.lessOrEqual(35.0)
.and(SCORES.IS_BANNED.eq(false)) .and(SCORES.IS_BANNED.eq(false))
} }

View File

@ -76,6 +76,8 @@ 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),
@ -100,7 +102,8 @@ 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 = Mod.parseModCombination(result.get(USER_SCORES.MODS, Int::class.java)), mods_bitwise = mods,
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),

View File

@ -1,4 +1,4 @@
FROM nginx:1.27.0 FROM nginx:1.27.0-alpine
RUN rm -rf /usr/share/nginx/html/* RUN rm -rf /usr/share/nginx/html/*

View File

@ -34,5 +34,5 @@
<router-outlet></router-outlet> <router-outlet></router-outlet>
<div class="text-center version"> <div class="text-center version">
v20241105 v20250213
</div> </div>

View File

@ -25,26 +25,29 @@ 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${this.getRelaxReport(suspiciousScore)}\n`; report += `\n\n${this.getRelaxReport(suspiciousScore)}\n`;
} }
for (const similarReplay of similarReplays) { for (const similarReplay of similarReplays) {
report += `\n${this.getStealingReport(similarReplay)}\n`; report += `\n\n${this.getStealingReport(similarReplay)}\n`;
} }
report += `\nGenerated on ${site} - [${userDetails.username} on ${site}](${environment.webUrl}/u/${userDetails.user_id})`; report += `\n\nGenerated on ${site} - [${userDetails.username} on ${site}](${environment.webUrl}/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`;
} }
} }

View File

@ -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 <25 cvUR. Low values can indicate cheating but always manually review users and This includes all replays with <35 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>

View File

@ -1,5 +1,3 @@
version: '3'
services: services:
caddy-main: caddy-main:
@ -11,10 +9,17 @@ services:
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: groonga/pgroonga:3.1.6-alpine-15 image: postgres:alpine
container_name: postgres container_name: postgres
restart: always restart: always
environment: environment:
@ -22,47 +27,6 @@ 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
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@ -72,19 +36,31 @@ 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: git.nise.moe/nuff/nise-backend:latest image: code.stedos.dev/stedos/nise-backend:latest
container_name: nise-backend container_name: nise-backend
environment: environment:
SPRING_PROFILES_ACTIVE: postgres,discord,import:scores,import:users,fix:scores SPRING_PROFILES_ACTIVE: postgres,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: nise POSTGRES_DB: ${DB_NAME}
# redis # redis
REDIS_DB: 4 REDIS_DB: 4
# Discord # Discord
@ -94,69 +70,36 @@ 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.moe/api/login/oauth2/code/osu" OSU_CALLBACK: "https://nise.stedos.dev/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.moe" ORIGIN: "https://nise.stedos.dev"
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-circleguard: nise-frontend:
image: git.nise.moe/nuff/nise-circleguard:latest image: code.stedos.dev/stedos/nise-frontend:latest
container_name: nise-circleguard container_name: nise-frontend
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: