This commit is contained in:
@@ -17,6 +17,7 @@ import { migrateHostSchema } from '../hosts/schema.js';
|
||||
import { migrateAuthSchema, migrateSuperViewerRole } from '../auth/schema.js';
|
||||
import { migrateGameFileSchema } from '../game-files/schema.js';
|
||||
import { migrateLeaderboardReports } from './leaderboard-reports.js';
|
||||
import { migratePlayerScoreTotal } from './player-score-total.js';
|
||||
import type { Migration } from './migration-runner.js';
|
||||
|
||||
// Append only: never renumber entries or change an applied migration to add an
|
||||
@@ -103,6 +104,11 @@ export const migrations: readonly Migration[] = [
|
||||
version: 18,
|
||||
name: 'durable leaderboard report identity and sequences',
|
||||
run: migrateLeaderboardReports
|
||||
},
|
||||
{
|
||||
version: 19,
|
||||
name: 'cumulative player score per match',
|
||||
run: migratePlayerScoreTotal
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
|
||||
/** Old score balances cannot be used to reconstruct cumulative points. */
|
||||
export function migratePlayerScoreTotal(handle: Database.Database): void {
|
||||
const columns = handle.prepare('PRAGMA table_info(leaderboard_match_players)').all() as {
|
||||
name: string;
|
||||
}[];
|
||||
if (!columns.some((column) => column.name === 'score_total'))
|
||||
handle.exec(`ALTER TABLE leaderboard_match_players ADD COLUMN score_total INTEGER
|
||||
CHECK (score_total IS NULL OR (typeof(score_total) = 'integer' AND score_total >= 0))`);
|
||||
}
|
||||
@@ -71,7 +71,21 @@ export function recordLeaderboardTelemetry(
|
||||
report: GameTelemetry
|
||||
): { matchId: number; current: boolean } {
|
||||
if (report.match !== undefined) return recordKeyedTelemetry(handle, report);
|
||||
return { matchId: recordLegacyTelemetry(handle, report), current: true };
|
||||
const matchId = recordLegacyTelemetry(handle, report);
|
||||
saveScoreTotals(handle, report, matchId);
|
||||
return { matchId, current: true };
|
||||
}
|
||||
|
||||
// Separate from applyReport so the historical version-0 backfill still works
|
||||
// before the score_total migration. Missing totals never erase known values.
|
||||
function saveScoreTotals(handle: Database.Database, report: GameTelemetry, matchId: number): void {
|
||||
if (report.event === 'game_start') return;
|
||||
const players = report.players.filter((player) => player.score_total !== undefined);
|
||||
if (!players.length) return;
|
||||
const save = handle.prepare(`UPDATE leaderboard_match_players
|
||||
SET score_total = MAX(COALESCE(score_total, 0), ?)
|
||||
WHERE match_id = ? AND guid = ?`);
|
||||
for (const player of players) save.run(player.score_total, matchId, player.guid);
|
||||
}
|
||||
|
||||
// Also used by the version-0 snapshot backfill, before keyed schema exists.
|
||||
@@ -325,6 +339,7 @@ function recordKeyedTelemetry(
|
||||
.get(report.server) as { id: number } | undefined;
|
||||
result.current = latest?.id === match.id && match.server_deleted_at === null;
|
||||
}
|
||||
if (applied) saveScoreTotals(handle, report, match.id);
|
||||
if (applied && report.seq !== undefined) {
|
||||
handle
|
||||
.prepare(
|
||||
|
||||
@@ -125,6 +125,7 @@ export function getMatchDetail(id: number): MatchDetail | null {
|
||||
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,
|
||||
p.score_total AS scoreTotal,
|
||||
EXISTS (SELECT 1 FROM leaderboard_players identity
|
||||
WHERE identity.guid = p.guid AND identity.game IN ('t5', 't6')) AS canViewProfile
|
||||
FROM leaderboard_match_players p WHERE p.match_id = ?
|
||||
|
||||
Reference in New Issue
Block a user