Add leaderboard feature with monthly reset and top 50 limit, update version to 1.1.0
This commit is contained in:
152
app.py
152
app.py
@@ -105,6 +105,9 @@ db.users.create_index('username', unique=True)
|
||||
db.songs.create_index('id', unique=True)
|
||||
db.songs.create_index('song_type')
|
||||
db.scores.create_index('username')
|
||||
db.leaderboards.create_index([('song_id', 1), ('difficulty', 1), ('month', 1), ('score', -1)])
|
||||
db.leaderboards.create_index([('song_id', 1), ('difficulty', 1), ('username', 1), ('month', 1)], unique=True)
|
||||
db.leaderboards.create_index('month')
|
||||
|
||||
|
||||
class HashException(Exception):
|
||||
@@ -746,6 +749,155 @@ def route_api_scores_get():
|
||||
return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name'], 'don': don})
|
||||
|
||||
|
||||
@app.route(basedir + 'api/leaderboard/submit', methods=['POST'])
|
||||
@login_required
|
||||
def route_api_leaderboard_submit():
|
||||
data = request.get_json()
|
||||
if not schema.validate(data, schema.leaderboard_submit):
|
||||
return abort(400)
|
||||
|
||||
username = session.get('username')
|
||||
user = db.users.find_one({'username': username})
|
||||
if not user:
|
||||
return api_error('user_not_found')
|
||||
|
||||
song_id = data.get('song_id')
|
||||
difficulty = data.get('difficulty')
|
||||
score_data = data.get('score')
|
||||
|
||||
# Validate difficulty
|
||||
valid_difficulties = ['easy', 'normal', 'hard', 'oni', 'ura']
|
||||
if difficulty not in valid_difficulties:
|
||||
return api_error('invalid_difficulty')
|
||||
|
||||
# Get current month (YYYY-MM format)
|
||||
current_month = time.strftime('%Y-%m', time.gmtime())
|
||||
|
||||
# Check if user already has a record for this song/difficulty/month
|
||||
existing = db.leaderboards.find_one({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'username': username,
|
||||
'month': current_month
|
||||
})
|
||||
|
||||
# Parse score (assuming it's in the same format as the scores collection)
|
||||
try:
|
||||
if isinstance(score_data, str):
|
||||
import json as json_module
|
||||
score_obj = json_module.loads(score_data)
|
||||
else:
|
||||
score_obj = score_data
|
||||
|
||||
score_value = int(score_obj.get('score', 0))
|
||||
except:
|
||||
return api_error('invalid_score_format')
|
||||
|
||||
if existing:
|
||||
# Update only if new score is higher
|
||||
existing_score = int(existing.get('score', {}).get('score', 0))
|
||||
if score_value > existing_score:
|
||||
db.leaderboards.update_one(
|
||||
{'_id': existing['_id']},
|
||||
{'$set': {
|
||||
'score': score_data,
|
||||
'display_name': user['display_name'],
|
||||
'submitted_at': time.time()
|
||||
}}
|
||||
)
|
||||
return jsonify({'status': 'ok', 'message': 'score_updated'})
|
||||
else:
|
||||
return jsonify({'status': 'ok', 'message': 'score_not_higher'})
|
||||
else:
|
||||
# Check if this score would be in top 50
|
||||
count = db.leaderboards.count_documents({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'month': current_month
|
||||
})
|
||||
|
||||
if count >= 50:
|
||||
# Find the 50th score
|
||||
leaderboard = list(db.leaderboards.find({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'month': current_month
|
||||
}).sort('score', -1).limit(50))
|
||||
|
||||
if len(leaderboard) >= 50:
|
||||
last_score = int(leaderboard[49].get('score', {}).get('score', 0))
|
||||
if score_value <= last_score:
|
||||
return jsonify({'status': 'ok', 'message': 'score_too_low'})
|
||||
|
||||
# Insert new record
|
||||
db.leaderboards.insert_one({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'username': username,
|
||||
'display_name': user['display_name'],
|
||||
'score': score_data,
|
||||
'submitted_at': time.time(),
|
||||
'month': current_month
|
||||
})
|
||||
|
||||
# Remove entries beyond 50th place
|
||||
if count >= 50:
|
||||
# Get all entries sorted by score
|
||||
all_entries = list(db.leaderboards.find({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'month': current_month
|
||||
}).sort('score', -1))
|
||||
|
||||
# Delete entries beyond 50
|
||||
if len(all_entries) > 50:
|
||||
for entry in all_entries[50:]:
|
||||
db.leaderboards.delete_one({'_id': entry['_id']})
|
||||
|
||||
return jsonify({'status': 'ok', 'message': 'score_submitted'})
|
||||
|
||||
|
||||
@app.route(basedir + 'api/leaderboard/get')
|
||||
def route_api_leaderboard_get():
|
||||
song_id = request.args.get('song_id', None)
|
||||
difficulty = request.args.get('difficulty', None)
|
||||
|
||||
if not song_id or not difficulty:
|
||||
return abort(400)
|
||||
|
||||
try:
|
||||
song_id = int(song_id)
|
||||
except:
|
||||
return abort(400)
|
||||
|
||||
# Validate difficulty
|
||||
valid_difficulties = ['easy', 'normal', 'hard', 'oni', 'ura']
|
||||
if difficulty not in valid_difficulties:
|
||||
return abort(400)
|
||||
|
||||
# Get current month
|
||||
current_month = time.strftime('%Y-%m', time.gmtime())
|
||||
|
||||
# Get top 50 scores
|
||||
leaderboard = list(db.leaderboards.find({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'month': current_month
|
||||
}, {
|
||||
'_id': False,
|
||||
'username': True,
|
||||
'display_name': True,
|
||||
'score': True,
|
||||
'submitted_at': True
|
||||
}).sort('score', -1).limit(50))
|
||||
|
||||
# Add rank to each entry
|
||||
for i, entry in enumerate(leaderboard):
|
||||
entry['rank'] = i + 1
|
||||
|
||||
return jsonify({'status': 'ok', 'leaderboard': leaderboard, 'month': current_month})
|
||||
|
||||
|
||||
@app.route(basedir + 'privacy')
|
||||
def route_api_privacy():
|
||||
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))
|
||||
|
||||
Reference in New Issue
Block a user