import { z } from "zod"; import { apiError, readJson, withApi } from "@/lib/api"; import { prisma } from "@/lib/db"; import { markEpisodesWatched, markMovieWatched, markSeasonWatched, markUpTo, undoWatches, unmarkEpisodes, unmarkMovie, watchNextEpisode, } from "@/lib/library"; /** GET — die Sichtungen eines Eintrags, jüngste zuerst. */ export const GET = withApi(async (_request, user, context) => { const { entryId } = await context.params; if (!entryId) return apiError("Es fehlt die Kennung des Eintrags.", 400); const watches = await prisma.watch.findMany({ where: { userId: user.id, entryId }, orderBy: { watchedAt: "desc" }, take: 500, include: { episode: { select: { seasonNumber: true, episodeNumber: true, name: true } } }, }); return { total: watches.length, items: watches.map((watch) => ({ id: watch.id, run: watch.run, watchedAt: watch.watchedAt, minutes: watch.minutes, episode: watch.episode, })), }; }); const postSchema = z.union([ z.object({ action: z.literal("next") }), z.object({ action: z.literal("movie") }), z.object({ action: z.literal("episodes"), episodeIds: z.array(z.string().min(1)).min(1).max(500), }), z.object({ action: z.literal("season"), seasonId: z.string().min(1) }), z.object({ action: z.literal("upTo"), episodeId: z.string().min(1) }), ]); /** * POST — etwas als gesehen markieren. * * Die Antwort enthält immer `watchIds`. Damit lässt sich die Aktion über * DELETE gezielt zurücknehmen, ohne raten zu müssen, was dazugehörte. */ export const POST = withApi(async (request, user, context) => { const { entryId } = await context.params; if (!entryId) return apiError("Es fehlt die Kennung des Eintrags.", 400); const body = await readJson(request, postSchema); switch (body.action) { case "next": return watchNextEpisode(user.id, entryId); case "movie": return markMovieWatched(user.id, entryId); case "episodes": return markEpisodesWatched(user.id, entryId, body.episodeIds); case "season": return markSeasonWatched(user.id, entryId, body.seasonId); case "upTo": return markUpTo(user.id, entryId, body.episodeId); } }); const deleteSchema = z.union([ z.object({ watchIds: z.array(z.string().min(1)).min(1).max(500) }), z.object({ episodeIds: z.array(z.string().min(1)).min(1).max(500) }), z.object({ action: z.literal("movie") }), ]); /** * DELETE — Sichtungen zurücknehmen. * * Drei Wege: gezielt über `watchIds` (die Antwort eines POST), über * `episodeIds`, oder mit `{"action":"movie"}` als Gegenstück zum * gleichnamigen POST — dafür braucht man die `watchIds` nicht zu kennen. * Zurückgenommen wird immer nur der laufende Durchlauf. */ export const DELETE = withApi(async (request, user, context) => { const { entryId } = await context.params; if (!entryId) return apiError("Es fehlt die Kennung des Eintrags.", 400); const body = await readJson(request, deleteSchema); if ("watchIds" in body) { await undoWatches(user.id, entryId, body.watchIds); } else if ("episodeIds" in body) { await unmarkEpisodes(user.id, entryId, body.episodeIds); } else { await unmarkMovie(user.id, entryId); } return { ok: true }; });