Commit 8a0227c2 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(leaderboard): test accounts never rank; shoot.mjs cleans up late rows

Accounts named ZZZ-... are test accounts (every script in tests/ and
tools/ tags them so); the leaderboard skips them in every game instead of
showing eight ZZZ-CYCLE rows at the top. shoot.mjs waits for requests the
closing page had in flight and repeats its cleanup until the profile is
gone: a bot match created at the last moment had kept one alive.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 003f4625
......@@ -26,7 +26,9 @@ if ($gameKey === 'chess') {
'select' => "id,username,display_name,avatar_url,country_code,level,{$ratingCol},games_played",
'order' => "{$ratingCol}.desc.nullslast",
'limit' => $limit,
$ratingCol => 'not.is.null'
$ratingCol => 'not.is.null',
// Test accounts are named ZZZ-... (tests/, tools/); they never rank.
'or' => '(display_name.is.null,display_name.not.like.ZZZ-*)'
]);
if (!is_array($players) || isset($players['error'])) {
......@@ -40,12 +42,18 @@ if ($gameKey === 'chess') {
jsonResponse(['leaderboard' => $result]);
} else {
$ratings = $db->get('player_game_ratings', [
$params = [
'game_key' => 'eq.' . $gameKey,
'select' => 'player_id,rating,games_played',
'order' => 'rating.desc',
'limit' => $limit
]);
];
// Test accounts (ZZZ-...) never rank.
$tests = $db->get('profiles', ['display_name' => 'like.ZZZ-*', 'select' => 'id', 'limit' => 500]);
if (is_array($tests) && !isset($tests['error']) && $tests) {
$params['player_id'] = 'not.in.(' . implode(',', array_column($tests, 'id')) . ')';
}
$ratings = $db->get('player_game_ratings', $params);
jsonResponse(['leaderboard' => is_array($ratings) && !isset($ratings['error']) ? $ratings : []]);
}
......@@ -118,7 +118,12 @@ async function removeAccount() {
if (!account) return;
const id = account.id;
// Bot games and matchmaking write rows of their own; every one goes with the
// account, or production fills with orphans.
// account, or production fills with orphans. A request the page sent just
// before it closed can still land after the first pass (a bot match created
// mid-shot), so the pass runs again until the profile is gone.
// Let requests the closing page already sent finish on the server first.
await new Promise(r => setTimeout(r, 1500));
for (let pass = 0; pass < 3; pass++) {
for (const p of [`chat_messages?sender_id=eq.${id}`, `matchmaking_queue?player_id=eq.${id}`,
`mp_log?player_id=eq.${id}`, `matches?white_player_id=eq.${id}`,
`matches?black_player_id=eq.${id}`, `player_achievements?player_id=eq.${id}`,
......@@ -132,6 +137,10 @@ async function removeAccount() {
`profiles?id=eq.${id}`]) {
await sb(p, { method: 'DELETE' }).catch(() => {});
}
const left = await sb(`profiles?id=eq.${id}&select=id`).catch(() => null);
if (!left?.length) break;
await new Promise(r => setTimeout(r, 2000));
}
await fetch(`${SB}/auth/v1/admin/users/${id}`, { method: 'DELETE', headers: { apikey: SK, Authorization: `Bearer ${SK}` } }).catch(() => {});
console.log('removed throwaway account');
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment