Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+92 -27
View File
@@ -1,6 +1,12 @@
import { db } from '../db/index.js';
import { matchChatTimeline } from './chat.js';
import { MIN_RECORDED_ROUND, type MatchDetail, type MatchPlayer, type MatchHistoryResponse, type ServerMatch } from '../../match-history.js';
import {
MIN_RECORDED_ROUND,
type MatchDetail,
type MatchPlayer,
type MatchHistoryResponse,
type ServerMatch
} from '../../match-history.js';
import { readMatchQuery, type MatchHistoryQuery } from '../../match-history-query.js';
@@ -15,29 +21,69 @@ const matchSummary = `m.id, m.game, m.highest_round AS observedRound, m.server,
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, query: MatchHistoryQuery = readMatchQuery(new URLSearchParams()), now = Date.now()): MatchHistoryResponse {
export function serverMatchHistory(
server?: string,
limit = 25,
query: MatchHistoryQuery = readMatchQuery(new URLSearchParams()),
now = Date.now()
): MatchHistoryResponse {
const handle = db();
const clauses = [`m.highest_round >= ${MIN_RECORDED_ROUND}`];
const parameters: Array<string | number> = [];
if (server !== undefined) { clauses.push('m.server = ?'); parameters.push(server); }
if (query.game) { clauses.push('m.game = ?'); parameters.push(query.game); }
const maps = handle.prepare(`SELECT m.map_key AS key, MAX(m.map_name) AS name, m.game
FROM leaderboard_matches m WHERE ${clauses.join(' AND ')} GROUP BY m.map_key, m.game ORDER BY name COLLATE NOCASE, game`)
if (server !== undefined) {
clauses.push('m.server = ?');
parameters.push(server);
}
if (query.game) {
clauses.push('m.game = ?');
parameters.push(query.game);
}
const maps = handle
.prepare(
`SELECT m.map_key AS key, MAX(m.map_name) AS name, m.game
FROM leaderboard_matches m WHERE ${clauses.join(' AND ')} GROUP BY m.map_key, m.game ORDER BY name COLLATE NOCASE, game`
)
.all(...parameters) as MatchHistoryResponse['maps'];
if (query.map) { clauses.push('m.map_key = ?'); parameters.push(query.map); }
if (query.status) { clauses.push('m.status = ?'); parameters.push(query.status); }
if (query.days) { clauses.push('julianday(m.started_at) >= julianday(?)'); parameters.push(new Date(now - query.days * 86400000).toISOString()); }
if (query.map) {
clauses.push('m.map_key = ?');
parameters.push(query.map);
}
if (query.status) {
clauses.push('m.status = ?');
parameters.push(query.status);
}
if (query.days) {
clauses.push('julianday(m.started_at) >= julianday(?)');
parameters.push(new Date(now - query.days * 86400000).toISOString());
}
// Search readable instance names as well as their stored identifiers. Tokens allow "Town Lobby 1".
for (const token of query.q.toLowerCase().replace(/\blobby\s+(?=\d+\b)/g, '').split(/[\s_]+/).filter(Boolean)) {
clauses.push("instr(lower(m.server || ' ' || m.map_name), ?) > 0"); parameters.push(token);
for (const token of query.q
.toLowerCase()
.replace(/\blobby\s+(?=\d+\b)/g, '')
.split(/[\s_]+/)
.filter(Boolean)) {
clauses.push("instr(lower(m.server || ' ' || m.map_name), ?) > 0");
parameters.push(token);
}
const where = `WHERE ${clauses.join(' AND ')}`;
const total = (handle.prepare(`SELECT COUNT(*) AS total FROM leaderboard_matches m ${where}`).get(...parameters) as { total: number }).total;
const total = (
handle
.prepare(`SELECT COUNT(*) AS total FROM leaderboard_matches m ${where}`)
.get(...parameters) as { total: number }
).total;
const page = Math.min(query.page, Math.max(1, Math.ceil(total / limit)));
const column = { started: 'startedAt', round: 'observedRound', duration: 'durationMs', players: 'uniquePlayers' }[query.sort];
const column = {
started: 'startedAt',
round: 'observedRound',
duration: 'durationMs',
players: 'uniquePlayers'
}[query.sort];
const direction = query.direction === 'asc' ? 'ASC' : 'DESC';
const matches = handle.prepare(`SELECT ${matchSummary} FROM leaderboard_matches m
${where} ORDER BY ${column} IS NULL ASC, ${column} ${direction}, m.id DESC LIMIT ? OFFSET ?`)
const matches = handle
.prepare(
`SELECT ${matchSummary} FROM leaderboard_matches m
${where} ORDER BY ${column} IS NULL ASC, ${column} ${direction}, m.id DESC LIMIT ? OFFSET ?`
)
.all(...parameters, limit, (page - 1) * limit) as ServerMatch[];
return { matches, total, page, limit, maps };
}
@@ -46,13 +92,20 @@ export function serverMatchHistory(server?: string, limit = 25, query: MatchHist
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.start_observed AS startObserved
const match = handle
.prepare(
`SELECT ${matchSummary}, 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' | 'chat' | 'startObserved'> & { startObserved: number }) | undefined;
AND m.highest_round >= ${MIN_RECORDED_ROUND}`
)
.get(id) as
| (Omit<MatchDetail, 'players' | 'chat' | 'startObserved'> & { startObserved: number })
| undefined;
if (!match) return null;
const players = handle.prepare(`SELECT p.guid,
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),
@@ -76,12 +129,24 @@ export function getMatchDetail(id: number): MatchDetail | null {
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, chat: matchChatTimeline(id), startObserved: Boolean(match.startObserved), players: players.map(player => ({
...player, hasStats: Boolean(player.hasStats), presentAtEnd: Boolean(player.presentAtEnd),
canViewProfile: Boolean(player.canViewProfile)
})) };
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,
chat: matchChatTimeline(id),
startObserved: Boolean(match.startObserved),
players: players.map((player) => ({
...player,
hasStats: Boolean(player.hasStats),
presentAtEnd: Boolean(player.presentAtEnd),
canViewProfile: Boolean(player.canViewProfile)
}))
};
}