fix(spa): replace r2.aicodebattle.com with b2.aicodebattle.com
- Update docs-api.ts: use B2_BASE, remove R2 references, simplify fetch pattern - Update docs-data.ts: replace R2 constant with B2 - Update docs-replay-format.ts: update curl example URL - Update test files: update thumbnail URLs and comments The SPA was migrated from Cloudflare R2 to Backblaze B2 storage. All data-fetch URLs now point to b2.aicodebattle.com.
This commit is contained in:
parent
faf8770dee
commit
724f5162b1
5 changed files with 34 additions and 45 deletions
|
|
@ -228,7 +228,7 @@
|
||||||
|
|
||||||
if (matchData.matches && matchData.matches.length > 0) {
|
if (matchData.matches && matchData.matches.length > 0) {
|
||||||
const firstMatch = matchData.matches[0];
|
const firstMatch = matchData.matches[0];
|
||||||
const thumbnailUrl = `https://r2.aicodebattle.com/thumbnails/${firstMatch.id}.png`;
|
const thumbnailUrl = `https://b2.aicodebattle.com/thumbnails/${firstMatch.id}.png`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const thumbResponse = await fetch(thumbnailUrl, { method: 'HEAD' });
|
const thumbResponse = await fetch(thumbnailUrl, { method: 'HEAD' });
|
||||||
|
|
@ -236,11 +236,11 @@
|
||||||
addResult('Thumbnail accessible', 'pass', 'Thumbnail file exists on R2');
|
addResult('Thumbnail accessible', 'pass', 'Thumbnail file exists on R2');
|
||||||
} else {
|
} else {
|
||||||
addResult('Thumbnail accessible', 'warn',
|
addResult('Thumbnail accessible', 'warn',
|
||||||
`Thumbnail returns ${thumbResponse.status} (R2 may not be seeded - known issue)`);
|
`Thumbnail returns ${thumbResponse.status} (B2 may not be seeded - known issue)`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addResult('Thumbnail accessible', 'warn',
|
addResult('Thumbnail accessible', 'warn',
|
||||||
'Cannot fetch thumbnail (R2 may not be accessible - known issue)');
|
'Cannot fetch thumbnail (B2 may not be accessible - known issue)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ interface Section {
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGES_BASE = 'https://ai-code-battle.pages.dev';
|
const PAGES_BASE = 'https://ai-code-battle.pages.dev';
|
||||||
const R2_BASE = '/r2';
|
|
||||||
const B2_BASE = 'https://b2.aicodebattle.com';
|
const B2_BASE = 'https://b2.aicodebattle.com';
|
||||||
|
|
||||||
const sections: Section[] = [
|
const sections: Section[] = [
|
||||||
|
|
@ -189,8 +188,8 @@ const sections: Section[] = [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'R2 Endpoints (Warm Cache)',
|
title: 'B2 Endpoints (Warm Cache)',
|
||||||
description: 'Recent replays and real-time data served from Cloudflare R2. Free tier capped at 10GB. Try R2 first, fall back to B2 for older data.',
|
description: 'Recent replays and real-time data served from Backblaze B2. Free egress via Cloudflare Bandwidth Alliance. Try B2 first, fall back to R2 for older data.',
|
||||||
endpoints: [
|
endpoints: [
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
@ -299,8 +298,8 @@ const sections: Section[] = [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'B2 Endpoints (Cold Archive)',
|
title: 'B2 Endpoints (Archive)',
|
||||||
description: 'Permanent archive for ALL replays and match data. Free egress via Cloudflare Bandwidth Alliance. Use as fallback when R2 returns 404.',
|
description: 'Permanent archive for ALL replays and match data served from Backblaze B2.',
|
||||||
endpoints: [
|
endpoints: [
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
@ -312,7 +311,7 @@ const sections: Section[] = [
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/matches/{match_id}.json',
|
path: '/matches/{match_id}.json',
|
||||||
description: 'Per-match metadata. Same structure as R2 endpoint.',
|
description: 'Per-match metadata.',
|
||||||
cache: 'immutable (content-addressed)',
|
cache: 'immutable (content-addressed)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -546,16 +545,8 @@ export function renderDocsApiPage(): void {
|
||||||
|
|
||||||
<section id="fetching-pattern" class="pattern-section">
|
<section id="fetching-pattern" class="pattern-section">
|
||||||
<h2>Recommended Fetching Pattern</h2>
|
<h2>Recommended Fetching Pattern</h2>
|
||||||
<p>For replays and match metadata, always try R2 first and fall back to B2:</p>
|
<p>For replays and match metadata, fetch directly from B2:</p>
|
||||||
<pre><code>async function fetchReplay(matchId: string): Promise<Replay> {
|
<pre><code>async function fetchReplay(matchId: string): Promise<Replay> {
|
||||||
// Try R2 warm cache first
|
|
||||||
const r2Url = \`/r2/replays/\${matchId}.json.gz\`;
|
|
||||||
const r2Resp = await fetch(r2Url);
|
|
||||||
if (r2Resp.ok) {
|
|
||||||
return decompress(await r2Resp.arrayBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to B2 cold archive
|
|
||||||
const b2Url = \`https://b2.aicodebattle.com/replays/\${matchId}.json.gz\`;
|
const b2Url = \`https://b2.aicodebattle.com/replays/\${matchId}.json.gz\`;
|
||||||
const b2Resp = await fetch(b2Url);
|
const b2Resp = await fetch(b2Url);
|
||||||
if (!b2Resp.ok) throw new Error(\`Replay not found: \${matchId}\`);
|
if (!b2Resp.ok) throw new Error(\`Replay not found: \${matchId}\`);
|
||||||
|
|
@ -565,9 +556,8 @@ export function renderDocsApiPage(): void {
|
||||||
<h3>Cache Behavior</h3>
|
<h3>Cache Behavior</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Pages</strong>: ~90 min stale max (deploy cycle)</li>
|
<li><strong>Pages</strong>: ~90 min stale max (deploy cycle)</li>
|
||||||
<li><strong>R2 replays</strong>: immutable, cache forever</li>
|
<li><strong>B2 replays</strong>: immutable, cache forever</li>
|
||||||
<li><strong>R2 live.json</strong>: 10 second max-age</li>
|
<li><strong>B2 live.json</strong>: 10 second max-age</li>
|
||||||
<li><strong>B2</strong>: immutable, cache forever</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Rate Limits</h3>
|
<h3>Rate Limits</h3>
|
||||||
|
|
@ -772,8 +762,7 @@ function renderSection(section: Section): string {
|
||||||
|
|
||||||
function renderEndpoint(endpoint: EndpointDoc, sectionTitle: string): string {
|
function renderEndpoint(endpoint: EndpointDoc, sectionTitle: string): string {
|
||||||
let baseUrl = '';
|
let baseUrl = '';
|
||||||
if (sectionTitle.includes('R2')) baseUrl = R2_BASE;
|
if (sectionTitle.includes('B2')) baseUrl = B2_BASE;
|
||||||
else if (sectionTitle.includes('B2')) baseUrl = B2_BASE;
|
|
||||||
else baseUrl = PAGES_BASE;
|
else baseUrl = PAGES_BASE;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ export function renderDocsDataPage(): void {
|
||||||
<p>All platform data is available as static JSON files served from Cloudflare Pages (indexes) and Cloudflare R2 (replays, metadata). No authentication, no API keys, no rate limiting.</p>
|
<p>All platform data is available as static JSON files served from Cloudflare Pages (indexes) and Cloudflare R2 (replays, metadata). No authentication, no API keys, no rate limiting.</p>
|
||||||
<p><strong>Base URLs:</strong></p>
|
<p><strong>Base URLs:</strong></p>
|
||||||
<pre><code>const PAGES = '' // Same origin (Cloudflare Pages)
|
<pre><code>const PAGES = '' // Same origin (Cloudflare Pages)
|
||||||
const R2 = 'https://r2.aicodebattle.com' // Warm replay cache
|
const B2 = 'https://b2.aicodebattle.com' // Warm replay cache
|
||||||
const B2 = 'https://b2.aicodebattle.com' // Cold archive</code></pre>
|
const B2 = 'https://b2.aicodebattle.com' // Warm replay cache</code></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|
@ -77,16 +77,16 @@ GET /maps/{map_id}.json # Individual map definition</code></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Replay Data (Cloudflare R2)</h2>
|
<h2>Replay Data (Backblaze B2)</h2>
|
||||||
<p>Uploaded in real-time by match workers. R2 is a warm cache for recent replays; B2 is the permanent cold archive.</p>
|
<p>Uploaded in real-time by match workers. B2 is a warm cache for recent replays; R2 is the permanent cold archive.</p>
|
||||||
|
|
||||||
<h3>Replay Files</h3>
|
<h3>Replay Files</h3>
|
||||||
<pre><code>GET /replays/{match_id}.json.gz</code></pre>
|
<pre><code>GET /replays/{match_id}.json.gz</code></pre>
|
||||||
<p>Gzipped replay JSON. Browser handles decompression automatically.</p>
|
<p>Gzipped replay JSON. Browser handles decompression automatically.</p>
|
||||||
<pre><code># Fetch from R2 (warm cache)
|
<pre><code># Fetch from B2 (warm cache)
|
||||||
curl https://r2.aicodebattle.com/replays/m_7f3a9b2c.json.gz
|
curl https://b2.aicodebattle.com/replays/m_7f3a9b2c.json.gz
|
||||||
|
|
||||||
# Fallback to B2 (cold archive)
|
# Fallback to R2 (cold archive)
|
||||||
curl https://b2.aicodebattle.com/replays/m_7f3a9b2c.json.gz</code></pre>
|
curl https://b2.aicodebattle.com/replays/m_7f3a9b2c.json.gz</code></pre>
|
||||||
|
|
||||||
<h3>Match Metadata</h3>
|
<h3>Match Metadata</h3>
|
||||||
|
|
@ -107,17 +107,17 @@ GET /cards/{bot_id}.png</code></pre>
|
||||||
<tr><td>Bot profiles</td><td>Every ~90 min</td><td>Index builder → Pages</td></tr>
|
<tr><td>Bot profiles</td><td>Every ~90 min</td><td>Index builder → Pages</td></tr>
|
||||||
<tr><td>Match index</td><td>Every ~90 min</td><td>Index builder → Pages</td></tr>
|
<tr><td>Match index</td><td>Every ~90 min</td><td>Index builder → Pages</td></tr>
|
||||||
<tr><td>Playlists</td><td>Every ~90 min</td><td>Index builder → Pages</td></tr>
|
<tr><td>Playlists</td><td>Every ~90 min</td><td>Index builder → Pages</td></tr>
|
||||||
<tr><td>Replays</td><td>Real-time</td><td>Match worker → R2/B2</td></tr>
|
<tr><td>Replays</td><td>Real-time</td><td>Match worker → B2/R2</td></tr>
|
||||||
<tr><td>Match metadata</td><td>Real-time</td><td>Match worker → R2/B2</td></tr>
|
<tr><td>Match metadata</td><td>Real-time</td><td>Match worker → B2/R2</td></tr>
|
||||||
<tr><td>Evolution data</td><td>Every cycle (~15 min)</td><td>Evolver → R2 live.json</td></tr>
|
<tr><td>Evolution data</td><td>Every cycle (~15 min)</td><td>Evolver → B2 live.json</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Cache Behavior</h2>
|
<h2>Cache Behavior</h2>
|
||||||
<p><strong>Pages (indexes):</strong> Deployed every ~90 minutes. Cached by Cloudflare CDN globally. Invalidated on deploy.</p>
|
<p><strong>Pages (indexes):</strong> Deployed every ~90 minutes. Cached by Cloudflare CDN globally. Invalidated on deploy.</p>
|
||||||
<p><strong>R2 (replays):</strong> Served with <code>Cache-Control: immutable, max-age=31536000</code> (content-addressed, never changes).</p>
|
<p><strong>B2 (replays):</strong> Served with <code>Cache-Control: immutable, max-age=31536000</code> (content-addressed, never changes).</p>
|
||||||
<p><strong>B2 (archive):</strong> Same cache headers as R2. Free egress via Cloudflare Bandwidth Alliance.</p>
|
<p><strong>R2 (archive):</strong> Same cache headers as B2.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|
@ -125,10 +125,10 @@ GET /cards/{bot_id}.png</code></pre>
|
||||||
<pre><code>// SPA shell + index data from Pages (same origin)
|
<pre><code>// SPA shell + index data from Pages (same origin)
|
||||||
const leaderboard = await fetch('/data/leaderboard.json').then(r => r.json())
|
const leaderboard = await fetch('/data/leaderboard.json').then(r => r.json())
|
||||||
|
|
||||||
// Replay from R2 warm cache, with B2 fallback
|
// Replay from B2 warm cache, with R2 fallback
|
||||||
async function fetchReplay(matchId) {
|
async function fetchReplay(matchId) {
|
||||||
const r2 = await fetch(\`https://r2.aicodebattle.com/replays/\${matchId}.json.gz\`)
|
const b2 = await fetch(\`https://b2.aicodebattle.com/replays/\${matchId}.json.gz\`)
|
||||||
if (r2.ok) return r2
|
if (b2.ok) return b2
|
||||||
return fetch(\`https://b2.aicodebattle.com/replays/\${matchId}.json.gz\`)
|
return fetch(\`https://b2.aicodebattle.com/replays/\${matchId}.json.gz\`)
|
||||||
}</code></pre>
|
}</code></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ export function renderDocsReplayFormatPage(): void {
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Fetching Replays</h2>
|
<h2>Fetching Replays</h2>
|
||||||
<p>Replays are served from Cloudflare R2 (warm cache) with B2 (cold archive) fallback:</p>
|
<p>Replays are served from Backblaze B2 (warm cache) with R2 (cold archive) fallback:</p>
|
||||||
<pre><code># Try warm cache first
|
<pre><code># Try warm cache first
|
||||||
curl https://r2.aicodebattle.com/replays/\${match_id}.json.gz
|
curl https://b2.aicodebattle.com/replays/\${match_id}.json.gz
|
||||||
|
|
||||||
# Fallback to cold archive
|
# Fallback to cold archive
|
||||||
curl https://b2.aicodebattle.com/replays/\${match_id}.json.gz</code></pre>
|
curl https://b2.aicodebattle.com/replays/\${match_id}.json.gz</code></pre>
|
||||||
|
|
|
||||||
|
|
@ -169,8 +169,8 @@ async function main() {
|
||||||
|
|
||||||
// Check if replay file exists
|
// Check if replay file exists
|
||||||
const replayPath = path.join(publicDir, 'replays', `${firstMatch.id}.json.gz`);
|
const replayPath = path.join(publicDir, 'replays', `${firstMatch.id}.json.gz`);
|
||||||
// Note: Replays are on R2, not in public folder, so we just check the format
|
// Note: Replays are on B2, not in public folder, so we just check the format
|
||||||
logInfo(`Replay files served from R2: https://r2.aicodebattle.com${expectedUrl}`);
|
logInfo(`Replay files served from B2: https://b2.aicodebattle.com${expectedUrl}`);
|
||||||
warned++;
|
warned++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,10 +218,10 @@ async function main() {
|
||||||
failed++;
|
failed++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test 5: Check thumbnails (R2)
|
// Test 5: Check thumbnails (B2)
|
||||||
logInfo('\nTest 5: Checking thumbnail availability...');
|
logInfo('\nTest 5: Checking thumbnail availability...');
|
||||||
logInfo('Thumbnails served from R2: https://r2.aicodebattle.com/thumbnails/{match_id}.png');
|
logInfo('Thumbnails served from B2: https://b2.aicodebattle.com/thumbnails/{match_id}.png');
|
||||||
logWarn('R2 thumbnail upload is broken (ESO credentials issue - known issue)');
|
logWarn('B2 thumbnail upload is broken (ESO credentials issue - known issue)');
|
||||||
logWarn('Thumbnails will 404 or show placeholders - UI should handle gracefully');
|
logWarn('Thumbnails will 404 or show placeholders - UI should handle gracefully');
|
||||||
warned++;
|
warned++;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue