import { db } from '../db/index.js'; import { MIN_RECORDED_ROUND, type MatchDetail, type MatchPlayer, type ServerMatch } from '../../match-history.js'; const matchSummary = `m.id, m.server, m.server_deleted_at, m.map_name AS map, m.started_at AS startedAt, m.status, CASE WHEN m.status = 'completed' THEN m.ended_at END AS endedAt, m.start_players AS startPlayers, (SELECT COUNT(*) FROM leaderboard_match_players p WHERE p.match_id = m.id) AS uniquePlayers, CASE WHEN m.status = 'completed' THEN (SELECT COUNT(*) FROM leaderboard_match_players p WHERE p.match_id = m.id AND p.present = 1) END AS endPlayers, CASE WHEN m.status = 'completed' AND m.start_observed = 1 THEN MAX(0, ROUND((julianday(m.ended_at) - julianday(m.started_at)) * 86400000)) END AS durationMs, CASE WHEN m.status = 'completed' THEN m.highest_round END AS finalRound`; export function serverMatchHistory(server?: string, limit = 25): { matches: ServerMatch[]; total: number } { const handle = db(); const where = `WHERE m.highest_round >= ${MIN_RECORDED_ROUND}${server === undefined ? '' : ' AND m.server = ?'}`; const parameters = server === undefined ? [] : [server]; const total = (handle.prepare(`SELECT COUNT(*) AS total FROM leaderboard_matches m ${where}`).get(...parameters) as { total: number }).total; const matches = handle.prepare(`SELECT ${matchSummary} FROM leaderboard_matches m ${where} ORDER BY m.started_at DESC, m.id DESC LIMIT ?`) .all(...parameters, limit) as ServerMatch[]; return { matches, total }; } /** Read durable match records without depending on a current server or scoreboard. */ export function getMatchDetail(id: number): MatchDetail | null { if (!Number.isSafeInteger(id) || id < 1) return null; const handle = db(); const match = handle.prepare(`SELECT ${matchSummary}, m.game, m.start_observed AS startObserved FROM leaderboard_matches m WHERE m.id = ? AND m.status = 'completed' AND m.highest_round >= ${MIN_RECORDED_ROUND}`) .get(id) as (Omit & { startObserved: number }) | undefined; if (!match) return null; const players = handle.prepare(`SELECT p.guid, COALESCE((SELECT identity.name FROM leaderboard_players identity WHERE identity.guid = p.guid AND identity.game IN ('t5', 't6') ORDER BY identity.last_seen_at DESC, identity.game LIMIT 1), (SELECT identity.name FROM leaderboard_players identity WHERE identity.guid = p.guid AND identity.game = ?), p.guid) AS name, p.has_stats AS hasStats, p.present AS presentAtEnd, p.first_seen_at AS firstSeenAt, p.last_seen_at AS lastSeenAt, CASE WHEN p.has_stats = 1 THEN p.highest_round END AS highestRound, CASE WHEN p.has_stats = 1 THEN p.rounds_survived END AS roundsSurvived, CASE WHEN p.has_stats = 1 THEN p.kills END AS kills, CASE WHEN p.has_stats = 1 THEN p.headshots END AS headshots, CASE WHEN p.has_stats = 1 THEN p.downs END AS downs, CASE WHEN p.has_stats = 1 THEN p.revives END AS revives, CASE WHEN p.has_stats = 1 THEN p.best_score END AS bestScore, (EXISTS (SELECT 1 FROM leaderboard_players identity WHERE identity.guid = p.guid AND identity.game IN ('t5', 't6')) AND EXISTS (SELECT 1 FROM leaderboard_match_players recorded JOIN leaderboard_matches history ON history.id = recorded.match_id WHERE recorded.guid = p.guid AND history.game IN ('t5', 't6') AND recorded.has_stats = 1 AND history.status = 'completed' AND history.highest_round >= ${MIN_RECORDED_ROUND} AND recorded.highest_round >= ${MIN_RECORDED_ROUND})) AS canViewProfile FROM leaderboard_match_players p WHERE p.match_id = ? ORDER BY hasStats DESC, kills DESC, highestRound DESC, name COLLATE NOCASE, p.guid`) .all(match.game, id) as Array & { hasStats: number; presentAtEnd: number; canViewProfile: number; }>; return { ...match, startObserved: Boolean(match.startObserved), players: players.map(player => ({ ...player, hasStats: Boolean(player.hasStats), presentAtEnd: Boolean(player.presentAtEnd), canViewProfile: Boolean(player.canViewProfile) })) }; }