136 lines
3.7 KiB
TypeScript
136 lines
3.7 KiB
TypeScript
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
|
import { Client } from "@upstash/qstash";
|
|
import { env } from "hono/adapter";
|
|
|
|
import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode";
|
|
import { verifyQstashHeader } from "~/libs/qstash/verifyQstashHeader";
|
|
import { readEnvVariable } from "~/libs/readEnvVariable";
|
|
import { deleteTitleMessage, getTitleMessage } from "~/models/titleMessages";
|
|
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",
|
|
},
|
|
},
|
|
});
|
|
|
|
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");
|
|
const client = new Client({ token: readEnvVariable(c.env, "QSTASH_TOKEN") });
|
|
|
|
if (isRetrying) {
|
|
if (!(await verifyQstashHeader(env<Env, typeof c>(c, "workerd"), c.req))) {
|
|
return c.json(ErrorResponse, { status: 401 });
|
|
}
|
|
} else {
|
|
try {
|
|
const { wasAdded, wasDeleted } = await setWatchStatus(
|
|
env<Env, typeof c>(c, "workerd"),
|
|
deviceId,
|
|
Number(titleId),
|
|
watchStatus,
|
|
);
|
|
if (wasAdded) {
|
|
await maybeScheduleNextAiringEpisode(
|
|
env<Env, typeof c>(c, "workerd"),
|
|
c.req,
|
|
titleId,
|
|
);
|
|
} else if (wasDeleted) {
|
|
const messageId = await getTitleMessage(
|
|
env<Env, typeof c>(c, "workerd"),
|
|
titleId,
|
|
);
|
|
if (messageId) {
|
|
await client.messages.delete(messageId);
|
|
await deleteTitleMessage(env<Env, typeof c>(c, "workerd"), titleId);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(new Error("Error setting watch status", { cause: error }));
|
|
console.error(error);
|
|
return c.json(ErrorResponse, { status: 500 });
|
|
}
|
|
}
|
|
|
|
try {
|
|
await maybeUpdateWatchStatusOnAnilist(
|
|
Number(titleId),
|
|
watchStatus,
|
|
aniListToken,
|
|
);
|
|
} catch (error) {
|
|
console.error(
|
|
new Error("Failed to update watch status on Anilist", { cause: error }),
|
|
);
|
|
client.publishJSON({
|
|
url: c.req.url,
|
|
body: { deviceId, watchStatus, titleId, isRetrying: true },
|
|
retries: 0,
|
|
delay: 60,
|
|
});
|
|
}
|
|
|
|
return c.json(SuccessResponse, { status: 200 });
|
|
});
|
|
|
|
export default app;
|