feat: enhance image URL handling; sanitize fallback URLs and improve cover image display logic
This commit is contained in:
@@ -38,10 +38,10 @@ const knownKeys = new Set<string>();
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a CDN URL for the given osu! image URL, uploading to R2 on first access.
|
* 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<string> {
|
export async function proxyImage(osuUrl: string): Promise<string | null> {
|
||||||
if (!isConfigured()) return osuUrl;
|
if (!isConfigured()) return null;
|
||||||
|
|
||||||
const key = urlToKey(osuUrl);
|
const key = urlToKey(osuUrl);
|
||||||
const cdnUrl = `${env.R2_PUBLIC_URL}/${key}`;
|
const cdnUrl = `${env.R2_PUBLIC_URL}/${key}`;
|
||||||
@@ -65,6 +65,6 @@ export async function proxyImage(osuUrl: string): Promise<string> {
|
|||||||
return cdnUrl;
|
return cdnUrl;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.error('[R2] proxyImage failed for', key, '—', err instanceof Error ? err.message : err);
|
console.error('[R2] proxyImage failed for', key, '—', err instanceof Error ? err.message : err);
|
||||||
return osuUrl;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// Use cached metadata from DB — only fall back to osu! API for legacy slots
|
||||||
const slotsWithBeatmaps = await Promise.all(
|
const slotsWithBeatmaps = await Promise.all(
|
||||||
pool.slots.map(async (slot) => {
|
pool.slots.map(async (slot) => {
|
||||||
// Fast path: metadata already stored in DB (R2 URLs)
|
// 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);
|
||||||
return {
|
return {
|
||||||
...slot,
|
...slot,
|
||||||
beatmap: {
|
beatmap: {
|
||||||
@@ -35,7 +37,7 @@ export const load: PageServerLoad = async ({ params, locals }) => {
|
|||||||
starRating: slot.starRating ?? 0,
|
starRating: slot.starRating ?? 0,
|
||||||
bpm: slot.bpm ?? 0,
|
bpm: slot.bpm ?? 0,
|
||||||
totalLength: slot.totalLength ?? 0,
|
totalLength: slot.totalLength ?? 0,
|
||||||
coverUrl: slot.coverUrl,
|
coverUrl,
|
||||||
url: `https://osu.ppy.sh/beatmaps/${slot.beatmapId}`
|
url: `https://osu.ppy.sh/beatmaps/${slot.beatmapId}`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ export const load: PageServerLoad = async ({ params, locals }) => {
|
|||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
m.mappool.slots.map(async (slot) => {
|
m.mappool.slots.map(async (slot) => {
|
||||||
// Fast path: metadata already stored in DB
|
// 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] = {
|
beatmapCache[slot.beatmapId] = {
|
||||||
title: slot.title,
|
title: slot.title,
|
||||||
artist: slot.artist ?? '',
|
artist: slot.artist ?? '',
|
||||||
@@ -38,8 +41,8 @@ export const load: PageServerLoad = async ({ params, locals }) => {
|
|||||||
starRating: slot.starRating ?? 0,
|
starRating: slot.starRating ?? 0,
|
||||||
bpm: slot.bpm ?? 0,
|
bpm: slot.bpm ?? 0,
|
||||||
totalLength: slot.totalLength ?? 0,
|
totalLength: slot.totalLength ?? 0,
|
||||||
coverUrl: slot.coverUrl,
|
coverUrl,
|
||||||
listCoverUrl: slot.listCoverUrl
|
listCoverUrl
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -502,6 +502,8 @@
|
|||||||
<span class="w-6 text-center text-xs font-600 text-text-secondary">{game.gameNumber}</span>
|
<span class="w-6 text-center text-xs font-600 text-text-secondary">{game.gameNumber}</span>
|
||||||
{#if bm?.listCoverUrl}
|
{#if bm?.listCoverUrl}
|
||||||
<img src={bm.listCoverUrl} alt="" class="h-8 w-16 rounded object-cover" />
|
<img src={bm.listCoverUrl} alt="" class="h-8 w-16 rounded object-cover" />
|
||||||
|
{:else}
|
||||||
|
<div class="h-8 w-16 flex-shrink-0 rounded bg-surface-700"></div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
{#if bm}
|
{#if bm}
|
||||||
|
|||||||
Reference in New Issue
Block a user