diff --git a/src/lib/server/storage/r2.ts b/src/lib/server/storage/r2.ts index 1f24af6..a19f66b 100644 --- a/src/lib/server/storage/r2.ts +++ b/src/lib/server/storage/r2.ts @@ -38,10 +38,10 @@ const knownKeys = new Set(); /** * Returns a CDN URL for the given osu! image URL, uploading to R2 on first access. - * Falls back to the original URL if R2 is not configured or the upload fails. + * Returns null if R2 is not configured or the upload/fetch fails. */ -export async function proxyImage(osuUrl: string): Promise { - if (!isConfigured()) return osuUrl; +export async function proxyImage(osuUrl: string): Promise { + if (!isConfigured()) return null; const key = urlToKey(osuUrl); const cdnUrl = `${env.R2_PUBLIC_URL}/${key}`; @@ -65,6 +65,6 @@ export async function proxyImage(osuUrl: string): Promise { return cdnUrl; } catch (err: unknown) { console.error('[R2] proxyImage failed for', key, '—', err instanceof Error ? err.message : err); - return osuUrl; + return null; } } diff --git a/src/routes/(app)/mappools/[id]/+page.server.ts b/src/routes/(app)/mappools/[id]/+page.server.ts index bd90916..d914166 100644 --- a/src/routes/(app)/mappools/[id]/+page.server.ts +++ b/src/routes/(app)/mappools/[id]/+page.server.ts @@ -24,8 +24,10 @@ export const load: PageServerLoad = async ({ params, locals }) => { // Use cached metadata from DB — only fall back to osu! API for legacy slots const slotsWithBeatmaps = await Promise.all( pool.slots.map(async (slot) => { - // Fast path: metadata already stored in DB (R2 URLs) - if (slot.title && slot.coverUrl) { + // Fast path: metadata already stored in DB + if (slot.title !== null) { + // Sanitize any stale ppy.sh fallback URLs (image was unavailable when first fetched) + const coverUrl = slot.coverUrl?.includes('ppy.sh') ? null : (slot.coverUrl ?? null); return { ...slot, beatmap: { @@ -35,7 +37,7 @@ export const load: PageServerLoad = async ({ params, locals }) => { starRating: slot.starRating ?? 0, bpm: slot.bpm ?? 0, totalLength: slot.totalLength ?? 0, - coverUrl: slot.coverUrl, + coverUrl, url: `https://osu.ppy.sh/beatmaps/${slot.beatmapId}` } }; diff --git a/src/routes/(app)/matches/[id]/+page.server.ts b/src/routes/(app)/matches/[id]/+page.server.ts index 6c6eef1..af4d1ab 100644 --- a/src/routes/(app)/matches/[id]/+page.server.ts +++ b/src/routes/(app)/matches/[id]/+page.server.ts @@ -30,7 +30,10 @@ export const load: PageServerLoad = async ({ params, locals }) => { await Promise.allSettled( m.mappool.slots.map(async (slot) => { // Fast path: metadata already stored in DB - if (slot.title && slot.coverUrl) { + if (slot.title !== null) { + // Sanitize any stale ppy.sh fallback URLs (image was unavailable when first fetched) + const coverUrl = slot.coverUrl?.includes('ppy.sh') ? null : (slot.coverUrl ?? null); + const listCoverUrl = slot.listCoverUrl?.includes('ppy.sh') ? null : (slot.listCoverUrl ?? null); beatmapCache[slot.beatmapId] = { title: slot.title, artist: slot.artist ?? '', @@ -38,8 +41,8 @@ export const load: PageServerLoad = async ({ params, locals }) => { starRating: slot.starRating ?? 0, bpm: slot.bpm ?? 0, totalLength: slot.totalLength ?? 0, - coverUrl: slot.coverUrl, - listCoverUrl: slot.listCoverUrl + coverUrl, + listCoverUrl }; return; } diff --git a/src/routes/(app)/matches/[id]/+page.svelte b/src/routes/(app)/matches/[id]/+page.svelte index 6c49b82..b263b49 100644 --- a/src/routes/(app)/matches/[id]/+page.svelte +++ b/src/routes/(app)/matches/[id]/+page.svelte @@ -502,6 +502,8 @@ {game.gameNumber} {#if bm?.listCoverUrl} + {:else} +
{/if}
{#if bm}