Fix leaderboard submission display issue: update frontend to correctly handle score vs points

This commit is contained in:
2026-01-17 21:22:34 +08:00
parent 45d6b1d9de
commit e52baf2555
5 changed files with 370 additions and 3 deletions

201
local_debug.py Normal file
View File

@@ -0,0 +1,201 @@
import time
import json
import base64
# Mock DB and objects
class MockCollection:
def __init__(self, name):
self.name = name
self.data = []
def find_one(self, query):
for item in self.data:
match = True
for k, v in query.items():
if item.get(k) != v:
match = False
break
if match:
return item
return None
def find(self, query, projection=None):
results = []
for item in self.data:
match = True
for k, v in query.items():
if item.get(k) != v:
match = False
break
if match:
if projection:
filtered = {k: v for k, v in item.items() if projection.get(k, False)}
if projection.get('_id') is None or projection.get('_id') is True:
filtered['_id'] = item.get('_id')
results.append(filtered)
else:
results.append(item)
return MockCursor(results)
def insert_one(self, doc):
doc['_id'] = len(self.data) + 1
print(f"[{self.name}] Inserted: {doc}")
self.data.append(doc)
def count_documents(self, query):
return len(self.data)
class MockCursor:
def __init__(self, data):
self.data = data
def sort(self, key, direction):
self.data.sort(key=lambda x: x.get(key, 0), reverse=(direction < 0))
return self
def limit(self, n):
self.data = self.data[:n]
return self.data
class MockDB:
def __init__(self):
self.users = MockCollection('users')
self.leaderboards = MockCollection('leaderboards')
db = MockDB()
db.users.insert_one({'username': 'testuser', 'display_name': 'Test User'})
# --- Logic from app.py rewritten for standalone test ---
def logic_submit(data, username):
print(f"\n--- Submitting {data.get('song_id')} ---")
user = db.users.find_one({'username': username})
if not user:
return {'error': 'user_not_found'}
song_id = data.get('song_id')
difficulty = data.get('difficulty')
score_data = data.get('score')
current_month = time.strftime('%Y-%m', time.gmtime())
existing = db.leaderboards.find_one({
'song_id': song_id,
'difficulty': difficulty,
'username': username,
'month': current_month
})
# Patched Logic from app.py
try:
if isinstance(score_data, str):
score_obj = json.loads(score_data)
else:
score_obj = score_data
# Check for 'score' first, then 'points'
score_val = score_obj.get('score')
if score_val is None:
score_val = score_obj.get('points')
score_value = int(score_val or 0)
print(f" Parsed Score Value: {score_value}")
except Exception as e:
print(f"Error parsing score: {e}")
return {'error': 'invalid_score_format'}
if existing:
print(" Existing record found (simulated update).")
else:
print(" Inserting new record.")
db.leaderboards.insert_one({
'song_id': song_id,
'difficulty': difficulty,
'username': username,
'display_name': user['display_name'],
'score': score_data,
'score_value': score_value,
'submitted_at': time.time(),
'month': current_month
})
return {'status': 'ok'}
def logic_get(args):
print(f"\n--- Getting {args.get('song_id')} ---")
song_id = args.get('song_id', None)
difficulty = args.get('difficulty', None)
if not song_id or not difficulty:
return {'error': 'missing args'}
try:
song_id = int(song_id)
except (ValueError, TypeError):
pass
current_month = time.strftime('%Y-%m', time.gmtime())
leaderboard = list(db.leaderboards.find({
'song_id': song_id,
'difficulty': difficulty,
'month': current_month
}, {
'_id': False,
'username': True,
'score_value': True
}).sort('score_value', -1).limit(50))
return {'status': 'ok', 'leaderboard': leaderboard}
# --- Test Cases ---
# Case 1: Hash ID
print("=== TEST CASE 1: Hash ID ===")
logic_submit({
'song_id': "hash123",
'difficulty': 'oni',
'score': {'score': 1000000}
}, 'testuser')
res1 = logic_get({'song_id': "hash123", 'difficulty': 'oni'})
if len(res1['leaderboard']) > 0: print("✅ Success")
else: print("❌ Failed")
# Case 2: Numeric ID
print("\n=== TEST CASE 2: Numeric ID ===")
logic_submit({
'song_id': 123,
'difficulty': 'oni',
'score': {'score': 2000000}
}, 'testuser')
res2 = logic_get({'song_id': "123", 'difficulty': 'oni'})
if len(res2['leaderboard']) > 0: print("✅ Success")
else: print("❌ Failed")
# Case 3: Points Only (Frontend Payload Simulation)
print("\n=== TEST CASE 3: Points Only (Frontend Payload) ===")
# This simulates what the JS 'this.resultsObj' looks like (it has 'points', not 'score')
logic_submit({
'song_id': "points_song",
'difficulty': 'hard',
'score': {'points': 345678, 'bad': 0, 'good': 100} # No 'score' key
}, 'testuser')
res3 = logic_get({'song_id': "points_song", 'difficulty': 'hard'})
passed = False
if len(res3.get('leaderboard', [])) > 0:
val = res3['leaderboard'][0].get('score_value')
if val == 345678:
print(f"✅ SUCCESS: Correctly parsed 'points' -> {val}")
passed = True
else:
print(f"❌ FAILED: Score value mismatch. Expected 345678, got {val}")
else:
print("❌ FAILED: No record found")
if passed:
print("\n---------------------------------------------------")
print("🎉 ALL LOCAL TESTS PASSED. BACKEND LOGIC VERIFIED.")
print("---------------------------------------------------")
else:
print("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
print("🛑 TESTS FAILED. CHECK LOGS.")
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")

View File

@@ -380,7 +380,8 @@ class Leaderboard {
this.ctx.fillText(displayName, modalX + 140, y + 2)
// Score with formatting
var score = entry.score && entry.score.score ? entry.score.score : 0
// Score with formatting
var score = entry.score_value !== undefined ? entry.score_value : (entry.score && entry.score.score ? entry.score.score : 0)
this.ctx.fillStyle = rank <= 3 ? "#1a1a1a" : "#555555"
this.ctx.font = (rank <= 3 ? "bold " : "") + "20px " + (strings.font || "sans-serif")
this.ctx.textAlign = "right"

View File

@@ -235,7 +235,7 @@ class Scoresheet {
this.leaderboardData = {
songId: songId,
difficulty: results.difficulty,
scoreObj: Object.assign({}, results)
scoreObj: Object.assign({ score: results.points }, results)
}
// Clean up scoreObj
if (this.leaderboardData.scoreObj) {
@@ -1025,7 +1025,7 @@ class Scoresheet {
this.leaderboardData = {
songId: songId,
difficulty: difficulty,
scoreObj: Object.assign({}, this.resultsObj)
scoreObj: Object.assign({ score: this.resultsObj.points }, this.resultsObj)
}
this.leaderboardSubmitted = false

133
reproduce_issue.py Normal file
View File

@@ -0,0 +1,133 @@
import requests
import json
import base64
BASE_URL = "http://localhost:34801"
def register_user(username, password):
url = f"{BASE_URL}/api/register"
data = {"username": username, "password": password}
try:
response = requests.post(url, json=data)
if response.status_code == 200:
return response.json()
print(f"Register failed: {response.text}")
except Exception as e:
print(f"Register error: {e}")
return None
def login_user(username, password):
url = f"{BASE_URL}/api/login"
data = {"username": username, "password": password}
session = requests.Session()
try:
response = session.post(url, json=data)
if response.status_code == 200:
return session
print(f"Login failed: {response.text}")
except Exception as e:
print(f"Login error: {e}")
return None
def get_csrf_token(session):
url = f"{BASE_URL}/api/csrftoken"
response = session.get(url)
if response.status_code == 200:
return response.json()['token']
return None
def submit_score(session, song_id, difficulty, score_obj, csrf_token):
url = f"{BASE_URL}/api/leaderboard/submit"
headers = {"X-CSRFToken": csrf_token}
data = {
"song_id": song_id,
"difficulty": difficulty,
"score": score_obj
}
response = session.post(url, json=data, headers=headers)
return response.json()
def get_leaderboard(session, song_id, difficulty):
url = f"{BASE_URL}/api/leaderboard/get"
params = {"song_id": song_id, "difficulty": difficulty}
response = session.get(url, params=params)
return response.json()
def main():
username = "debug_user_1"
password = "password123"
print("1. Registering/Logging in...")
# Try login first
session = login_user(username, password)
if not session:
# Register
reg = register_user(username, password)
if reg:
session = login_user(username, password)
else:
print("Could not register or login")
return
print("2. Getting CSRF token...")
csrf_token = get_csrf_token(session)
if not csrf_token:
print("Could not get CSRF token")
return
song_id = 1 # Assuming song 1 exists
difficulty = "oni"
# Simulate scoresheet.js submission (points only)
score_obj = {
"points": 123456,
"good": 100,
"ok": 10,
"bad": 0,
"maxCombo": 110,
"drumroll": 5,
"crown": "gold"
}
print(f"3. Submitting score: {score_obj['points']}")
submit_res = submit_score(session, song_id, difficulty, score_obj, csrf_token)
print(f"Submit response: {submit_res}")
print("4. Fetching leaderboard...")
leaderboard_res = get_leaderboard(session, song_id, difficulty)
if leaderboard_res['status'] == 'ok':
leaderboard = leaderboard_res['leaderboard']
found = False
for entry in leaderboard:
if entry['username'] == username:
found = True
print(f"Found entry: {entry}")
print(f"entry.score_value: {entry.get('score_value')}")
print(f"entry.score: {entry.get('score')}")
score_value = entry.get('score_value')
score_data = entry.get('score')
# Check what leaderboard.js would see
if score_data and 'score' in score_data:
print(f"JS would assume score: {score_data['score']}")
else:
print("JS would assume score: 0 (undefined)")
if score_value == 123456:
print("SUCCESS: score_value is correct.")
else:
print(f"FAILURE: score_value is incorrect (expected 123456, got {score_value})")
if score_data and score_data.get('points') == 123456:
print("SUCCESS: points preserved in score object.")
if not found:
print("Entry not found in leaderboard (maybe not top 50?)")
else:
print(f"Failed to get leaderboard: {leaderboard_res}")
if __name__ == "__main__":
main()

32
test_frontend_logic.js Normal file
View File

@@ -0,0 +1,32 @@
// Simulation of song objects
const songs = [
{ name: "Server Song", id: 123, hash: "hash123" },
{ name: "Custom Song (No ID)", id: undefined, hash: "hashCustom" },
{ name: "Custom Song (Null ID)", id: null, hash: "hashNull" },
{ name: "Edge Case (ID 0)", id: 0, hash: "hashZero" } // Checking if behavior is acceptable
];
console.log("=== Frontend Logic Test: Song ID Fallback ===");
songs.forEach(song => {
// Logic from scoresheet.js / songselect.js
const songId = song.id || song.hash;
console.log(`Song: ${song.name}`);
console.log(` Raw ID: ${song.id}, Hash: ${song.hash}`);
console.log(` Resolved songId: ${songId}`);
if (song.id && songId === song.id) {
console.log(" ✅ Correctly used ID");
} else if (!song.id && songId === song.hash) {
console.log(" ✅ Correctly fell back to Hash");
} else if (song.id === 0 && songId === song.hash) {
console.log(" ⚠️ ID was 0, used Hash. (Acceptable if ID > 0 always)");
} else {
console.log(" ❌ FAILED logic mismatch");
}
console.log("---");
});
console.log("Tests Completed.");