Files
flimmer/src/app/api/v1/suche/route.ts
T
Florian Wilde bb63827bf7 feat: REST-API unter /api/v1 mit OpenAPI-Beschreibung
Dieselben Endpunkte bedienen Skripte und das eigene Frontend: erst wird ein
Bearer-Token geprüft, sonst das Sitzungscookie.

Sichtungsaktionen geben immer watchIds zurück — damit lässt sich genau diese
Aktion über DELETE zurücknehmen, ohne raten zu müssen, was dazugehörte.

Die OpenAPI-Beschreibung ist handgeschrieben statt aus Zod erzeugt: die
Schnittstelle ist überschaubar, und ein Generator brächte eine weitere
Abhängigkeit für einen Nutzen, den ein Dutzend Zeilen JSON auch hergeben.
2026-09-01 20:24:48 +02:00

54 lines
1.8 KiB
TypeScript

import { apiError, withApi } from "@/lib/api";
import { env } from "@/lib/env";
import { searchMovies, searchMulti, searchSeries, TmdbNotConfiguredError } from "@/lib/tmdb";
/**
* GET /api/v1/suche?q=…&typ=serie|film
*
* Durchsucht TMDB. Die Treffer tragen die TMDB-ID — damit lässt sich der
* Titel über POST /api/v1/bibliothek direkt aufnehmen.
*/
export const GET = withApi(async (request) => {
const url = new URL(request.url);
const query = (url.searchParams.get("q") ?? "").trim();
const type = url.searchParams.get("typ");
if (query.length < 2) {
return apiError("Der Suchbegriff braucht mindestens zwei Zeichen.", 400);
}
if (!env.tmdb.isConfigured) {
return apiError("Auf dieser Instanz ist kein TMDB-Schlüssel hinterlegt.", 503);
}
try {
const data =
type === "serie"
? await searchSeries(query)
: type === "film"
? await searchMovies(query)
: await searchMulti(query);
return {
query,
total: data.total_results,
items: data.results.map((item) => {
const isSeries =
item.media_type === "tv" || (type === "serie" && item.media_type === undefined);
const date = isSeries ? item.first_air_date : item.release_date;
return {
tmdbId: item.id,
type: isSeries ? "SERIES" : "MOVIE",
title: item.name ?? item.title ?? "Ohne Titel",
overview: item.overview ?? null,
posterPath: item.poster_path ?? null,
year: date ? Number.parseInt(date.slice(0, 4), 10) : null,
voteAverage: item.vote_average ?? null,
};
}),
};
} catch (error) {
if (error instanceof TmdbNotConfiguredError) return apiError(error.message, 503);
return apiError(error instanceof Error ? error.message : "Die Suche ist fehlgeschlagen.", 502);
}
});