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.
54 lines
1.8 KiB
TypeScript
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);
|
|
}
|
|
});
|