import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi"; import type { HonoRequest } from "hono"; import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode"; import { buildNewEpisodeTaskId } from "~/libs/tasks/id"; import { queueTask } from "~/libs/tasks/queueTask"; import { removeTask } from "~/libs/tasks/removeTask"; import { setWatchStatus } from "~/models/watchStatus"; import { AniListIdSchema, ErrorResponse, ErrorResponseSchema, SuccessResponse, SuccessResponseSchema, } from "~/types/schema"; import { WatchStatus } from "~/types/title/watchStatus"; import { maybeUpdateWatchStatusOnAnilist } from "./anilist"; const app = new OpenAPIHono(); const UpdateWatchStatusRequest = z.object({ deviceId: z.string(), watchStatus: WatchStatus.nullable(), titleId: AniListIdSchema, isRetrying: z.boolean().optional().default(false), }); const route = createRoute({ tags: ["aniplay", "title"], operationId: "updateWatchStatus", summary: "Update watch status for a title", description: "Updates the watch status for a title. If the user sets the watch status to 'watching', they'll start getting notified about new episodes.", method: "post", path: "/", request: { body: { content: { "application/json": { schema: UpdateWatchStatusRequest, }, }, }, headers: z.object({ "x-anilist-token": z.string().nullish() }), }, responses: { 200: { content: { "application/json": { schema: SuccessResponseSchema(), }, }, description: "Watch status was successfully updated", }, 500: { content: { "application/json": { schema: ErrorResponseSchema, }, }, description: "Failed to update watch status", }, }, }); export async function updateWatchStatus( req: HonoRequest, deviceId: string, titleId: number, watchStatus: WatchStatus | null, ) { const { wasAdded, wasDeleted } = await setWatchStatus( deviceId, Number(titleId), watchStatus, ); if (wasAdded) { await maybeScheduleNextAiringEpisode(req, titleId); } else if (wasDeleted) { await removeTask("new-episode", buildNewEpisodeTaskId(titleId)); } } app.openapi(route, async (c) => { const { deviceId, watchStatus, titleId, isRetrying = false, } = await c.req.json(); const aniListToken = c.req.header("X-AniList-Token"); if (!isRetrying) { try { await updateWatchStatus(c.req, deviceId, titleId, watchStatus); } catch (error) { console.error("Error setting watch status"); console.error(error); return c.json(ErrorResponse, { status: 500 }); } } try { await maybeUpdateWatchStatusOnAnilist( Number(titleId), watchStatus, aniListToken, ); } catch (error) { console.error("Failed to update watch status on Anilist"); console.error(error); if (isRetrying) { return c.json(ErrorResponse, { status: 500 }); } await queueTask( "anilist-updates", { deviceId, watchStatus, titleId, isRetrying: true, nameSuffix: "watch-status", }, { req: c.req, scheduleConfig: { delay: { minute: 1 } } }, ); } return c.json(SuccessResponse, { status: 200 }); }); export default app;