fix: proxy Funkwhale cover images server-side to fix presigned URL expiry
Deploy Indiekit Server / deploy (push) Successful in 1m23s
Deploy Indiekit Server / deploy (push) Successful in 1m23s
Presigned Wasabi S3 cover URLs expire after ~1h. Since Funkwhale/Last.fm data is now fetched client-side (Alpine.js), the old build-time image cache no longer runs. Fix: getCoverUrl() now returns /funkwhale/api/cover-proxy?url=... and a new public route proxies the image server-side on demand. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -691,6 +691,93 @@ const patchSpecs = [
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-lastfm/lib/stats.js",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "funkwhale-cover-url-proxy",
|
||||
marker: "proxy wrapper: routes presigned S3 URLs through server-side cover proxy",
|
||||
oldSnippet: ` // Check track cover
|
||||
if (track.cover?.urls) {
|
||||
return track.cover.urls[sizeKey] || track.cover.urls.original || null;
|
||||
}
|
||||
|
||||
// Check album cover
|
||||
if (track.album?.cover?.urls) {
|
||||
return track.album.cover.urls[sizeKey] || track.album.cover.urls.original || null;
|
||||
}
|
||||
|
||||
// Check artist cover
|
||||
const artist = track.artist_credit?.[0]?.artist;
|
||||
if (artist?.cover?.urls) {
|
||||
return artist.cover.urls[sizeKey] || artist.cover.urls.original || null;
|
||||
}
|
||||
|
||||
return null;`,
|
||||
newSnippet: ` // proxy wrapper: routes presigned S3 URLs through server-side cover proxy
|
||||
const proxyCover = (url) =>
|
||||
url ? \`/funkwhale/api/cover-proxy?url=\${encodeURIComponent(url)}\` : null;
|
||||
|
||||
// Check track cover
|
||||
if (track.cover?.urls) {
|
||||
return proxyCover(track.cover.urls[sizeKey] || track.cover.urls.original || null);
|
||||
}
|
||||
|
||||
// Check album cover
|
||||
if (track.album?.cover?.urls) {
|
||||
return proxyCover(track.album.cover.urls[sizeKey] || track.album.cover.urls.original || null);
|
||||
}
|
||||
|
||||
// Check artist cover
|
||||
const artist = track.artist_credit?.[0]?.artist;
|
||||
if (artist?.cover?.urls) {
|
||||
return proxyCover(artist.cover.urls[sizeKey] || artist.cover.urls.original || null);
|
||||
}
|
||||
|
||||
return null;`,
|
||||
candidates: [
|
||||
"node_modules/@rmdes/indiekit-endpoint-funkwhale/lib/utils.js",
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-funkwhale/lib/utils.js",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "funkwhale-cover-proxy-route",
|
||||
marker: "cover-proxy: routes presigned S3 URLs through server-side proxy",
|
||||
oldSnippet: ` publicRouter.get("/api/stats/trends", statsController.apiTrends);
|
||||
|
||||
return publicRouter;`,
|
||||
newSnippet: ` publicRouter.get("/api/stats/trends", statsController.apiTrends);
|
||||
|
||||
// cover-proxy: routes presigned S3 URLs through server-side proxy
|
||||
// Presigned Wasabi URLs expire in ~1h; this endpoint fetches them server-side
|
||||
// so the browser receives stable-ish URLs (fresh presigned URL per API call).
|
||||
publicRouter.get("/api/cover-proxy", async (request, response) => {
|
||||
const rawUrl = request.query.url;
|
||||
if (!rawUrl) return response.status(400).send("Missing url parameter");
|
||||
let urlObj;
|
||||
try { urlObj = new URL(rawUrl); } catch { return response.status(400).send("Invalid url"); }
|
||||
try {
|
||||
const upstream = await fetch(rawUrl, {
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
headers: { "User-Agent": "indiekit/1.0 (cover-proxy)" },
|
||||
});
|
||||
if (!upstream.ok) return response.status(upstream.status).send("Upstream error");
|
||||
const contentType = upstream.headers.get("content-type") || "image/jpeg";
|
||||
const buffer = Buffer.from(await upstream.arrayBuffer());
|
||||
response.set({
|
||||
"Content-Type": contentType,
|
||||
"Cache-Control": "public, max-age=86400, stale-while-revalidate=604800",
|
||||
"X-Cover-Path": urlObj.pathname,
|
||||
});
|
||||
return response.send(buffer);
|
||||
} catch {
|
||||
return response.status(502).send("Proxy error");
|
||||
}
|
||||
});
|
||||
|
||||
return publicRouter;`,
|
||||
candidates: [
|
||||
"node_modules/@rmdes/indiekit-endpoint-funkwhale/index.js",
|
||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-funkwhale/index.js",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
async function exists(filePath) {
|
||||
|
||||
Reference in New Issue
Block a user