69 lines
4.4 KiB
TypeScript
69 lines
4.4 KiB
TypeScript
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<MatchDetail, 'players' | 'startObserved'> & { 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<Omit<MatchPlayer, 'hasStats' | 'presentAtEnd' | 'canViewProfile'> & {
|
|
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)
|
|
})) };
|
|
}
|