Files
aniplay-api/src/controllers/watch-status/index.ts

145 lines
3.5 KiB
TypeScript

import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import type { HonoRequest } from "hono";
import { env } from "hono/adapter";
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 type { Env } from "~/types/env";
import {
AniListIdSchema,
ErrorResponse,
ErrorResponseSchema,
SuccessResponse,
SuccessResponseSchema,
} from "~/types/schema";
import { WatchStatus } from "~/types/title/watchStatus";
import { maybeUpdateWatchStatusOnAnilist } from "./anilist";
const app = new OpenAPIHono<Env>();
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(
env: Env,
req: HonoRequest,
deviceId: string,
titleId: number,
watchStatus: WatchStatus | null,
) {
const { wasAdded, wasDeleted } = await setWatchStatus(
env,
deviceId,
Number(titleId),
watchStatus,
);
if (wasAdded) {
await maybeScheduleNextAiringEpisode(env, req, titleId);
} else if (wasDeleted) {
await removeTask(env, "new-episode", buildNewEpisodeTaskId(titleId));
}
}
app.openapi(route, async (c) => {
const {
deviceId,
watchStatus,
titleId,
isRetrying = false,
} = await c.req.json<typeof UpdateWatchStatusRequest._type>();
const aniListToken = c.req.header("X-AniList-Token");
if (!isRetrying) {
try {
await updateWatchStatus(
env(c, "workerd"),
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(
env(c, "workerd"),
"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;