diff --git a/CTFd/api/v1/scoreboard.py b/CTFd/api/v1/scoreboard.py index f9a67f8a2ea45e5dac14f1d8f188c84b3cb3f94b..c4d35ed8580e172fe070abf33333ed121c59399c 100644 --- a/CTFd/api/v1/scoreboard.py +++ b/CTFd/api/v1/scoreboard.py @@ -27,86 +27,124 @@ class ScoreboardList(Resource): @check_score_visibility @cache.cached(timeout=60, key_prefix=make_cache_key) def get(self): - standings = get_standings() - response = [] mode = get_config("user_mode") account_type = get_mode_as_word() - + freeze = get_config("freeze") + + standings = get_standings() + team_ids = [x.account_id for x in standings] + + fails_query = db.session.query( + Fails.account_id, + Fails.challenge_id, + Fails.value + ).filter(Fails.account_id.in_(team_ids)) + + if freeze: + fails_query = fails_query.filter(Fails.date < unix_time_to_utc(freeze)) + + # Précharger toutes les informations de challenges en une seule requête + from sqlalchemy import distinct + challenge_ids = db.session.query(distinct(Fails.challenge_id)).filter( + Fails.account_id.in_(team_ids) + ).all() + challenge_ids = [c[0] for c in challenge_ids] + + challenges = {} + if challenge_ids: + challenges_data = db.session.query( + Challenges.id, + Challenges.type, + Challenges.value + ).filter(Challenges.id.in_(challenge_ids)).all() + + challenges = {c.id: c for c in challenges_data} + + potential_scores = defaultdict(int) + for fail in fails_query.all(): + challenge = challenges.get(fail.challenge_id) + if not challenge: + continue + + if challenge.type in ("manual", "manualRecursive", "flash"): + potential_scores[fail.account_id] += challenge.value + elif challenge.type == "sport": + potential_scores[fail.account_id] += fail.value + + response = [] + if mode == TEAMS_MODE: - r = db.session.execute( - select( - [ - Users.id, - Users.name, - Users.oauth_id, - Users.team_id, - Users.hidden, - Users.color, - Users.banned, - Users.bracket_id, - Brackets.name.label("bracket_name"), - ] - ) - .where(Users.team_id.isnot(None)) - .join(Brackets, Users.bracket_id == Brackets.id, isouter=True) + users_query = db.session.query( + Users.id, + Users.name, + Users.oauth_id, + Users.team_id, + Users.hidden, + Users.color, + Users.banned, + Users.bracket_id, + Brackets.name.label("bracket_name") + ).filter( + Users.team_id.isnot(None), + Users.hidden.is_(False), + Users.banned.is_(False) + ).join( + Brackets, + Users.bracket_id == Brackets.id, + isouter=True ) - users = r.fetchall() + membership = defaultdict(dict) - for u in users: - if u.hidden is False and u.banned is False: - membership[u.team_id][u.id] = { - "id": u.id, - "oauth_id": u.oauth_id, - "name": u.name, - "color": u.color, - "score": 0, - "bracket_id": u.bracket_id, - "bracket_name": u.bracket_name, - } - - # Get user_standings as a dict so that we can more quickly get member scores + for u in users_query.all(): + membership[u.team_id][u.id] = { + "id": u.id, + "oauth_id": u.oauth_id, + "name": u.name, + "color": u.color, + "score": 0, + "bracket_id": u.bracket_id, + "bracket_name": u.bracket_name, + } + user_standings = get_user_standings() + for u in user_standings: - membership[u.team_id][u.user_id]["score"] = int(u.score) - - - - - for i, x in enumerate(standings): + if u.team_id in membership and u.user_id in membership[u.team_id]: + membership[u.team_id][u.user_id]["score"] = int(u.score) - potentialScore = 0 - team_ids = [x.account_id for team in standings] - fails = Fails.query.filter(Fails.account_id.in_(team_ids)) - - freeze = get_config("freeze") - if freeze: - fails = fails.filter(Fails.date < unix_time_to_utc(freeze)) - - for fail in fails: - challenge = Challenges.query.filter_by(id=fail.challenge_id).first_or_404() - if challenge.type == "manual" or challenge.type == "manualRecursive" or challenge.type == "flash": - potentialScore += challenge.value - elif challenge.type == "sport": - potentialScore += fail.value - - entry = { - "pos": i + 1, - "account_id": x.account_id, - "account_url": generate_account_url(account_id=x.account_id), - "account_type": account_type, - "oauth_id": x.oauth_id, - "color": x.color, - "name": x.name, - "score": int(x.score), - "bracket_id": x.bracket_id, - "bracket_name": x.bracket_name, - "potential_score": potentialScore, - } - - if mode == TEAMS_MODE: - entry["members"] = list(membership[x.account_id].values()) - - response.append(entry) + for i, x in enumerate(standings): + entry = { + "pos": i + 1, + "account_id": x.account_id, + "account_url": generate_account_url(account_id=x.account_id), + "account_type": account_type, + "oauth_id": x.oauth_id, + "color": x.color, + "name": x.name, + "score": int(x.score), + "bracket_id": x.bracket_id, + "bracket_name": x.bracket_name, + "potential_score": potential_scores.get(x.account_id, 0), + "members": list(membership.get(x.account_id, {}).values()) + } + response.append(entry) + else: + for i, x in enumerate(standings): + entry = { + "pos": i + 1, + "account_id": x.account_id, + "account_url": generate_account_url(account_id=x.account_id), + "account_type": account_type, + "oauth_id": x.oauth_id, + "color": x.color, + "name": x.name, + "score": int(x.score), + "bracket_id": x.bracket_id, + "bracket_name": x.bracket_name, + "potential_score": potential_scores.get(x.account_id, 0), + } + response.append(entry) + return {"success": True, "data": response}