diff --git a/CHANGELOG.md b/CHANGELOG.md index ef4eaa4e..1ccba58c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ * Add `/api/v1/challenges?view=admin` to allow admin users to see all challenges regardless of their visibility state * Add `/api/v1/users?view=admin` to allow admin users to see all users regardless of their hidden/banned state * Add `/api/v1/teams?view=admin` to allow admin users to see all teams regardless of their hidden/banned state -* The scoreboard endpoint `/api/v1/scoreboard` is now significantly more performant due to better response generation +* The scoreboard endpoint `/api/v1/scoreboard` is now significantly more performant (20x) due to better response generation +* The top scoreboard endpoint `/api/v1/scoreboard/top/` is now more performant (3x) due to better response generation * The scoreboard endpoint `/api/v1/scoreboard` will no longer show hidden/banned users in a non-hidden team **Deployment** diff --git a/CTFd/api/v1/scoreboard.py b/CTFd/api/v1/scoreboard.py index 85c23a93..4314d71e 100644 --- a/CTFd/api/v1/scoreboard.py +++ b/CTFd/api/v1/scoreboard.py @@ -1,3 +1,5 @@ +from collections import defaultdict + from flask_restx import Namespace, Resource from sqlalchemy.orm import joinedload @@ -119,38 +121,31 @@ class ScoreboardDetail(Resource): solves = solves.all() awards = awards.all() + # Build a mapping of accounts to their solves and awards + solves = solves + awards + solves_mapper = defaultdict(list) + for solve in solves: + solves_mapper[solve.account_id].append( + { + "challenge_id": solve.challenge_id, + "account_id": solve.account_id, + "team_id": solve.team_id, + "user_id": solve.user_id, + "value": solve.challenge.value, + "date": isoformat(solve.date), + } + ) + + # Sort all solves by date + for team_id in solves_mapper: + solves_mapper[team_id] = sorted( + solves_mapper[team_id], key=lambda k: k["date"] + ) + for i, team in enumerate(team_ids): response[i + 1] = { "id": standings[i].account_id, "name": standings[i].name, - "solves": [], + "solves": solves_mapper.get(standings[i].account_id, []), } - for solve in solves: - if solve.account_id == team: - response[i + 1]["solves"].append( - { - "challenge_id": solve.challenge_id, - "account_id": solve.account_id, - "team_id": solve.team_id, - "user_id": solve.user_id, - "value": solve.challenge.value, - "date": isoformat(solve.date), - } - ) - for award in awards: - if award.account_id == team: - response[i + 1]["solves"].append( - { - "challenge_id": None, - "account_id": award.account_id, - "team_id": award.team_id, - "user_id": award.user_id, - "value": award.value, - "date": isoformat(award.date), - } - ) - response[i + 1]["solves"] = sorted( - response[i + 1]["solves"], key=lambda k: k["date"] - ) - return {"success": True, "data": response}