// Receipt times come from the receiver, never the game server's clock. A late // queued report is fresh receipt evidence, not proof that the server is online. export async function dashboardOverview(db, serverId) { const where = serverId ? ' WHERE server_id = ?' : ''; const values = serverId ? [serverId] : []; const results = await db.batch([ db.prepare(`SELECT COUNT(*) AS missions, COALESCE(SUM(outcome = 'victory'), 0) AS wins, COALESCE(SUM(outcome = 'defeat'), 0) AS losses, COALESCE(SUM(terminal = 0), 0) AS unfinished, COALESCE(SUM(partial), 0) AS partialMissions FROM missions${where}`).bind(...values), db.prepare(`SELECT COUNT(*) AS servers FROM servers${where}`).bind(...values), db.prepare(`SELECT COUNT(*) AS events, MAX(received_at) AS lastReceivedAt FROM events${where}`).bind(...values), db.prepare(`SELECT CASE WHEN terminal = 1 THEN outcome ELSE 'unfinished' END AS outcome, COUNT(*) AS missions FROM missions${where} GROUP BY terminal, outcome`).bind(...values), db.prepare(`SELECT lower(json_extract(payload, '$.terrain')) AS terrain, COUNT(*) AS missions, SUM(outcome = 'victory') AS wins, SUM(outcome = 'defeat') AS losses, SUM(terminal = 0) AS unfinished FROM missions${where} GROUP BY lower(json_extract(payload, '$.terrain')) ORDER BY missions DESC, terrain LIMIT 101`).bind(...values), ]); const totals = {...results[0].results[0], ...results[1].results[0], ...results[2].results[0]}; const completed = totals.wins + totals.losses; totals.winRate = completed ? Math.round(1000 * totals.wins / completed) / 10 : null; return {serverId, generatedAt: Math.floor(Date.now() / 1000), totals, outcomes: results[3].results, maps: results[4].results.slice(0, 100), mapsTruncated: results[4].results.length > 100}; } export async function dashboardServers(db, {limit, before}, query) { const result = await db.prepare(`WITH page AS ( SELECT rowid AS cursor, * FROM servers WHERE rowid < ? AND (? = '' OR instr(lower(name), lower(?)) > 0 OR instr(server_id, lower(?)) > 0) ORDER BY rowid DESC LIMIT ? ) SELECT s.cursor, s.server_id AS serverId, s.name, s.active, s.registration, s.created_at AS createdAt, e.received_at AS lastReceivedAt, json_extract(e.payload, '$.occurredAt') AS lastReportedAt, json_extract(e.payload, '$.type') AS lastEventType, e.mission_id AS lastMissionId, json_extract(e.payload, '$.terrain') AS terrain, json_extract(e.payload, '$.connectedPlayers') AS lastReportedPlayers, COUNT(m.id) AS missions, COALESCE(SUM(m.outcome = 'victory'), 0) AS wins, COALESCE(SUM(m.outcome = 'defeat'), 0) AS losses, COALESCE(SUM(m.terminal = 0), 0) AS unfinished, COALESCE(SUM(m.partial), 0) AS partialMissions FROM page s LEFT JOIN events e ON e.id = ( SELECT id FROM events WHERE server_id = s.server_id ORDER BY received_at DESC, id DESC LIMIT 1) LEFT JOIN missions m ON m.server_id = s.server_id GROUP BY s.cursor ORDER BY s.cursor DESC`).bind(before, query, query, query, limit).all(); return {servers: result.results, nextBefore: result.results.length === limit ? result.results.at(-1).cursor : null}; }