/** * Kleiner TMDB-Ersatz für Screenshots und manuelles Ausprobieren. * * node scripts/tmdb-stub.mjs [port] * * Beantwortet die Suchendpunkte aus dem lokalen Katalog (den das Seed-Skript * angelegt hat) im Format der TMDB-API. Damit lässt sich die Suchseite auch * ohne echten TMDB-Schlüssel zeigen — für Screenshots der Anleitung und um * die Oberfläche auszuprobieren, bevor man einen Schlüssel besorgt. * * Verwendung zusammen mit: * TMDB_API_KEY=stub TMDB_BASE_URL=http://127.0.0.1:3099/3 npm run dev */ import { createServer } from "node:http"; import { PrismaPg } from "@prisma/adapter-pg"; import { PrismaClient } from "../src/generated/prisma/client.ts"; try { process.loadEnvFile(".env"); } catch { // Werte kommen aus der Umgebung. } const port = Number(process.argv[2] ?? 3099); const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }), }); function toResult(title, mediaType) { const date = title.releaseDate?.toISOString().slice(0, 10) ?? null; return { id: title.tmdbId, media_type: mediaType, name: mediaType === "tv" ? title.title : undefined, title: mediaType === "movie" ? title.title : undefined, original_name: title.originalTitle ?? undefined, original_title: title.originalTitle ?? undefined, overview: title.overview ?? "", poster_path: title.posterPath, backdrop_path: title.backdropPath, first_air_date: mediaType === "tv" ? date : undefined, release_date: mediaType === "movie" ? date : undefined, vote_average: title.voteAverage ?? 0, }; } async function search(query, type) { const titles = await prisma.title.findMany({ where: { tmdbId: { not: null }, ...(type ? { type } : {}), OR: [ { title: { contains: query, mode: "insensitive" } }, { originalTitle: { contains: query, mode: "insensitive" } }, { genres: { has: query } }, ], }, orderBy: { releaseDate: "desc" }, take: 20, }); return titles.map((title) => toResult(title, title.type === "SERIES" ? "tv" : "movie")); } const server = createServer(async (request, response) => { const url = new URL(request.url ?? "/", `http://localhost:${port}`); const query = url.searchParams.get("query") ?? ""; const send = (body, status = 200) => { response.writeHead(status, { "content-type": "application/json; charset=utf-8" }); response.end(JSON.stringify(body)); }; try { if (url.pathname.startsWith("/3/search/")) { const kind = url.pathname.split("/").pop(); const type = kind === "tv" ? "SERIES" : kind === "movie" ? "MOVIE" : null; const results = await search(query, type); return send({ page: 1, results, total_pages: 1, total_results: results.length }); } return send({ status_message: `Kein Ersatz für ${url.pathname}` }, 404); } catch (error) { return send({ status_message: String(error) }, 500); } }); server.listen(port, "127.0.0.1", () => { console.log(`TMDB-Ersatz läuft auf http://127.0.0.1:${port}/3`); }); for (const signal of ["SIGINT", "SIGTERM"]) { process.on(signal, () => { server.close(); void prisma.$disconnect(); process.exit(0); }); }