Files
flimmer/src/app/api/image/route.ts
T
Florian Wilde cc0e498fe6 feat: Katalogschicht und lokaler Bild-Cache
Der Katalog ist bewusst gemeinsam für alle Nutzer und über (Typ, TMDB-ID)
dedupliziert: zehn Leute mit derselben Serie brauchen die Episodenliste nur
einmal. Persönliches steckt ausschließlich in LibraryEntry und Watch.

Poster laufen über /api/image statt direkt zu TMDB — einmal holen, danach
vom eigenen Server ausliefern. Größe und Pfad werden streng geprüft, damit
daraus kein offener Proxy wird.

Staffel 0 (Specials) wird importiert, zählt aber nicht zum Fortschritt;
Episoden ohne eigene Laufzeit fallen auf die durchschnittliche Episodenlänge
der Serie zurück.
2026-09-01 19:00:12 +02:00

35 lines
1.1 KiB
TypeScript

import { NextResponse } from "next/server";
import { getCachedImage, isValidImagePath, isValidSize } from "@/lib/images";
/**
* Ausliefern zwischengespeicherter TMDB-Bilder.
*
* /api/image?size=w342&path=/abc123.jpg
*
* Größe und Pfad werden streng geprüft — der Endpunkt soll kein offener Proxy
* für beliebige Adressen werden.
*/
export async function GET(request: Request) {
const url = new URL(request.url);
const size = url.searchParams.get("size") ?? "w342";
const path = url.searchParams.get("path") ?? "";
if (!isValidSize(size) || !isValidImagePath(path)) {
return NextResponse.json({ error: "Ungültige Bildanfrage." }, { status: 400 });
}
try {
const image = await getCachedImage(size, path);
return new NextResponse(new Uint8Array(image.data), {
headers: {
"content-type": image.contentType,
// Bilder unter einem TMDB-Pfad ändern sich nicht mehr.
"cache-control": "public, max-age=31536000, immutable",
"x-flimmer-cache": image.fromCache ? "hit" : "miss",
},
});
} catch {
return NextResponse.json({ error: "Bild nicht verfügbar." }, { status: 404 });
}
}