Streaming-Verfügbarkeit beantwortet nur die halbe Frage: manche Filme laufen nirgends mehr, die hat man gekauft und liegen zu Hause — in Jellyfin, Plex, Emby, auf dem NAS oder als Scheibe. Bewusst getrennt vom Feld „wo schaue ich das": das eine sagt, wo ein Titel gerade läuft, und das ändert sich ständig; das andere sagt, was einem gehört, und das bleibt. Auf der Detailseite steht die Sammlung deshalb vor der Verfügbarkeit — was man hat, ist die verlässlichere Antwort. Durchgezogen bis in Filter, Export, CSV-Import und REST-API.
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
import { z } from "zod";
|
|
import { readJson, readPaging, withApi } from "@/lib/api";
|
|
import { getLibrary } from "@/lib/queries";
|
|
import { addToLibrary } from "@/lib/library";
|
|
import { ensureTitle } from "@/lib/catalog";
|
|
import type { EntryStatus, TitleType } from "@/generated/prisma/client";
|
|
|
|
const STATUSES = ["WATCHING", "PAUSED", "COMPLETED", "DROPPED", "WATCHLIST"] as const;
|
|
|
|
/** GET /api/v1/bibliothek — Bibliothek lesen, mit denselben Filtern wie im UI. */
|
|
export const GET = withApi(async (request, user) => {
|
|
const url = new URL(request.url);
|
|
const { limit, offset } = readPaging(request);
|
|
|
|
const status = url.searchParams.get("status");
|
|
const type = url.searchParams.get("typ");
|
|
|
|
const items = await getLibrary(user.id, {
|
|
query: url.searchParams.get("q") ?? undefined,
|
|
status: STATUSES.includes(status as EntryStatus) ? (status as EntryStatus) : undefined,
|
|
type: type === "SERIES" || type === "MOVIE" ? (type as TitleType) : undefined,
|
|
genre: url.searchParams.get("genre") ?? undefined,
|
|
tag: url.searchParams.get("tag") ?? undefined,
|
|
provider: url.searchParams.get("dienst") ?? undefined,
|
|
owned: url.searchParams.get("sammlung") === "1" ? true : undefined,
|
|
});
|
|
|
|
return {
|
|
total: items.length,
|
|
limit,
|
|
offset,
|
|
items: items.slice(offset, offset + limit),
|
|
};
|
|
});
|
|
|
|
const addSchema = z
|
|
.object({
|
|
titleId: z.string().min(1).optional(),
|
|
tmdbId: z.number().int().positive().optional(),
|
|
type: z.enum(["SERIES", "MOVIE"]).optional(),
|
|
status: z.enum(STATUSES).default("WATCHLIST"),
|
|
})
|
|
.refine((value) => Boolean(value.titleId) || Boolean(value.tmdbId && value.type), {
|
|
message: "Entweder titleId oder tmdbId zusammen mit type angeben.",
|
|
});
|
|
|
|
/** POST /api/v1/bibliothek — Titel aufnehmen, per interner ID oder TMDB-ID. */
|
|
export const POST = withApi(async (request, user) => {
|
|
const body = await readJson(request, addSchema);
|
|
|
|
let titleId = body.titleId;
|
|
if (!titleId && body.tmdbId && body.type) {
|
|
const title = await ensureTitle(body.type, body.tmdbId, { region: user.region });
|
|
titleId = title.id;
|
|
}
|
|
if (!titleId) {
|
|
throw new z.ZodError([
|
|
{ code: "custom", path: ["titleId"], message: "Kein Titel angegeben." },
|
|
]);
|
|
}
|
|
|
|
const entry = await addToLibrary(user.id, titleId, body.status);
|
|
return { id: entry.id, titleId: entry.titleId, status: entry.status };
|
|
});
|