import { db } from '../db/index.js'; import { leaderboardMetrics, type LeaderboardEntry, type LeaderboardMap, type LeaderboardMetric, type LeaderboardOptions, type LeaderboardResponse } from '../../leaderboard.js'; const metrics = new Set(leaderboardMetrics.map(metric => metric.key)); export const MAX_LEADERBOARD_OFFSET = 1_000_000; export function isLeaderboardMetric(value: string): value is LeaderboardMetric { return metrics.has(value); } /** Aggregate persisted per-match records; filtering a player keeps their actual rank. */ export function getLeaderboard(options: LeaderboardOptions = {}): LeaderboardResponse { const handle = db(); const metric = options.metric && isLeaderboardMetric(options.metric) ? options.metric : 'highestRound'; const limit = Number.isSafeInteger(options.limit) ? Math.min(100, Math.max(1, options.limit!)) : 50; const offset = Number.isSafeInteger(options.offset) ? Math.min(MAX_LEADERBOARD_OFFSET, Math.max(0, options.offset!)) : 0; const conditions = ['mp.has_stats = 1']; const parameters: string[] = []; if (options.game) { conditions.push('m.game = ?'); parameters.push(options.game); } if (options.map) { conditions.push('m.map_key = ?'); parameters.push(options.map); } const scoped = `FROM leaderboard_match_players mp JOIN leaderboard_matches m ON m.id = mp.match_id WHERE ${conditions.join(' AND ')}`; const aggregated = `WITH totals AS ( SELECT m.game, mp.guid, MAX(mp.highest_round) AS highestRound, SUM(mp.kills) AS kills, SUM(mp.rounds_survived) AS roundsSurvived, SUM(mp.headshots) AS headshots, SUM(mp.revives) AS revives, SUM(mp.downs) AS downs, COUNT(*) AS gamesPlayed, SUM(mp.completed) AS gamesCompleted, MAX(mp.kills) AS bestKills, MAX(mp.best_score) AS bestScore, MAX(mp.last_seen_at) AS lastSeen ${scoped} GROUP BY m.game, mp.guid ), ranked AS ( SELECT totals.*, p.name, RANK() OVER (ORDER BY totals.${metric} DESC) AS rank FROM totals JOIN leaderboard_players p ON p.game = totals.game AND p.guid = totals.guid )`; const query = options.q?.trim() ?? ''; const search = query ? 'WHERE instr(lower(name), lower(?)) > 0 OR instr(lower(guid), lower(?)) > 0' : ''; const searchedParameters = query ? [...parameters, query, query] : parameters; const entries = handle.prepare(`${aggregated} SELECT * FROM ranked ${search} ORDER BY ${metric} DESC, name COLLATE NOCASE, name, game, guid LIMIT ? OFFSET ?`) .all(...searchedParameters, limit, offset) as LeaderboardEntry[]; const { total } = handle.prepare(`${aggregated} SELECT COUNT(*) AS total FROM ranked ${search}`) .get(...searchedParameters) as { total: number }; const summary = handle.prepare(`SELECT (SELECT COUNT(*) FROM (SELECT m.game, mp.guid ${scoped} GROUP BY m.game, mp.guid)) AS players, COUNT(DISTINCT m.id) AS matches, COALESCE(SUM(mp.kills), 0) AS kills, COALESCE(SUM(mp.rounds_survived), 0) AS roundsSurvived, COALESCE(MAX(mp.highest_round), 0) AS highestRound, MIN(m.started_at) AS firstTrackedAt, MAX(m.last_received_at) AS updatedAt ${scoped}`).get(...parameters, ...parameters) as LeaderboardResponse['summary']; // Keep all recorded map variants available, even after their server is removed. const maps = handle.prepare(`SELECT map_key AS key, game, map, map_name AS name FROM leaderboard_matches ${options.game ? 'WHERE game = ?' : ''} GROUP BY game, map_key HAVING id = MAX(id) ORDER BY game, name COLLATE NOCASE, map_key`) .all(...(options.game ? [options.game] : [])) as LeaderboardMap[]; return { entries, maps, total, limit, offset, metric, summary }; }