furhter bug fixes and updates

This commit is contained in:
jenz
2026-08-22 17:10:27 +02:00
parent 9e8e8378a2
commit 16bb9e14a5
18 changed files with 1070 additions and 256 deletions
@@ -92,25 +92,29 @@ export interface MapPopulationPoint {
map_id: number;
map_name: string;
map_start_dt: string;
players_online_at_start: number;
map_end_dt: string | null;
total_players: number;
}
export async function getMapPopulationSeries(limit = 40): Promise<MapPopulationPoint[]> {
// Player count at the moment each map period started — one data point per
// map, letting the frontend chart whether population trends up or down
// across the rotation. Fetched most-recent-first (for the LIMIT), then
// the caller should reverse it back to chronological order for display.
export async function getMapPopulationSeries(limit = 40, before?: string): Promise<MapPopulationPoint[]> {
// Total DISTINCT players who were connected at any point during each map's
// full session (not just at its start) — one data point per map. Fetched
// most-recent-first (for the LIMIT), then the caller should reverse it
// back to chronological order for display.
// `before` (a DB-formatted datetime string) lets the caller page further
// back into history than the initial batch, for pan-to-load-more charts.
const rows = await query<MapPopulationPoint>(
`SELECT m.map_id, m.map_name, m.map_start_dt,
(SELECT COUNT(*) FROM playtime_display_sessions s
WHERE s.session_start_dt < m.map_start_dt
`SELECT m.map_id, m.map_name, m.map_start_dt, ${NEXT_MAP_START('m')} AS map_end_dt,
(SELECT COUNT(DISTINCT s.steamid) FROM playtime_display_sessions s
WHERE s.session_start_dt < COALESCE(${NEXT_MAP_START('m')}, NOW())
AND (s.session_end_dt IS NULL OR s.session_end_dt > m.map_start_dt)
) AS players_online_at_start
) AS total_players
FROM playtime_display_map_history m
WHERE ${REAL_MAP_PERIOD}
${before ? 'AND m.map_start_dt < ?' : ''}
ORDER BY m.map_start_dt DESC
LIMIT ?`,
[limit],
before ? [before, limit] : [limit],
);
return rows.reverse();
}
@@ -118,17 +122,32 @@ export async function getMapPopulationSeries(limit = 40): Promise<MapPopulationP
export interface CountrySummary {
current_country: string;
player_count: number;
total_minutes: number;
avg_minutes: number;
}
export async function getCountriesSummary(): Promise<CountrySummary[]> {
// Players with no resolved country are excluded — there's no meaningful
// "unknown" country page to select.
// "unknown" country page to select. Each player's playtime is the same
// "latest session counter snapshot" logic used everywhere else, wrapped
// in a derived table so it can be aggregated per country.
return query<CountrySummary>(
`SELECT current_country, COUNT(*) AS player_count
FROM playtime_display_players
WHERE current_country IS NOT NULL
GROUP BY current_country
ORDER BY player_count DESC, current_country ASC`,
`SELECT p.current_country, COUNT(*) AS player_count,
SUM(pt.total_minutes) AS total_minutes,
AVG(pt.total_minutes) AS avg_minutes
FROM playtime_display_players p
JOIN (
SELECT p2.steamid,
(SELECT COALESCE(s.session_end_playtime_minutes, s.session_start_playtime_minutes)
FROM playtime_display_sessions s
WHERE s.steamid = p2.steamid
ORDER BY s.session_id DESC
LIMIT 1) AS total_minutes
FROM playtime_display_players p2
) pt ON pt.steamid = p.steamid
WHERE p.current_country IS NOT NULL
GROUP BY p.current_country
ORDER BY player_count DESC, p.current_country ASC`,
);
}
@@ -164,6 +183,7 @@ export interface MapSummary {
map_name: string;
times_played: number;
last_played: string;
total_minutes: number;
}
export interface MapPeriod {
@@ -171,6 +191,7 @@ export interface MapPeriod {
map_name: string;
map_start_dt: string;
map_end_dt: string | null;
player_count: number;
}
export interface MapPresentPlayer {
@@ -194,7 +215,8 @@ export interface MapVoteRow {
export async function searchMapNames(q: string, limit = 50): Promise<MapSummary[]> {
const like = `%${q}%`;
return query<MapSummary>(
`SELECT m.map_name, COUNT(*) AS times_played, MAX(m.map_start_dt) AS last_played
`SELECT m.map_name, COUNT(*) AS times_played, MAX(m.map_start_dt) AS last_played,
SUM(TIMESTAMPDIFF(MINUTE, m.map_start_dt, COALESCE(${NEXT_MAP_START('m')}, NOW()))) AS total_minutes
FROM playtime_display_map_history m
WHERE m.map_name LIKE ?
AND ${REAL_MAP_PERIOD}
@@ -207,7 +229,11 @@ export async function searchMapNames(q: string, limit = 50): Promise<MapSummary[
export async function getMapPeriods(mapName: string): Promise<MapPeriod[]> {
return query<MapPeriod>(
`SELECT m.map_id, m.map_name, m.map_start_dt, ${NEXT_MAP_START('m')} AS map_end_dt
`SELECT m.map_id, m.map_name, m.map_start_dt, ${NEXT_MAP_START('m')} AS map_end_dt,
(SELECT COUNT(DISTINCT s.steamid) FROM playtime_display_sessions s
WHERE s.session_start_dt < COALESCE(${NEXT_MAP_START('m')}, NOW())
AND (s.session_end_dt IS NULL OR s.session_end_dt > m.map_start_dt)
) AS player_count
FROM playtime_display_map_history m
WHERE m.map_name = ?
AND ${REAL_MAP_PERIOD}
@@ -218,7 +244,11 @@ export async function getMapPeriods(mapName: string): Promise<MapPeriod[]> {
export async function getMapPeriod(mapId: number): Promise<MapPeriod | null> {
const rows = await query<MapPeriod>(
`SELECT m.map_id, m.map_name, m.map_start_dt, ${NEXT_MAP_START('m')} AS map_end_dt
`SELECT m.map_id, m.map_name, m.map_start_dt, ${NEXT_MAP_START('m')} AS map_end_dt,
(SELECT COUNT(DISTINCT s.steamid) FROM playtime_display_sessions s
WHERE s.session_start_dt < COALESCE(${NEXT_MAP_START('m')}, NOW())
AND (s.session_end_dt IS NULL OR s.session_end_dt > m.map_start_dt)
) AS player_count
FROM playtime_display_map_history m
WHERE m.map_id = ?`,
[mapId],
@@ -15,9 +15,12 @@ export function steamProfileUrl(steamId2: string): string | null {
return id64 ? `https://steamcommunity.com/profiles/${id64}` : null;
}
// Formats total minutes as "123h 45m" for display.
// Formats total minutes as "123h 45m" for display. Negative values (which
// shouldn't be possible, but have shown up from anomalous underlying data —
// e.g. a player_time counter that decreased between two session snapshots)
// are shown as "—" rather than a nonsensical negative duration.
export function formatMinutes(totalMinutes: number | null): string {
if (totalMinutes == null || Number.isNaN(totalMinutes)) return '—';
if (totalMinutes == null || Number.isNaN(totalMinutes) || totalMinutes < 0) return '—';
const h = Math.floor(totalMinutes / 60);
const m = totalMinutes % 60;
return `${h}h ${m}m`;