refactor: move getWatchingTitles to AniList Durable Object
introduces caching to that method
This commit is contained in:
@@ -1,142 +0,0 @@
|
||||
import { graphql } from "gql.tada";
|
||||
import { GraphQLClient } from "graphql-request";
|
||||
|
||||
import { sleep } from "~/libs/sleep";
|
||||
|
||||
const GetWatchingTitlesQuery = graphql(`
|
||||
query GetWatchingTitles($userName: String!, $page: Int!) {
|
||||
Page(page: $page, perPage: 50) {
|
||||
mediaList(
|
||||
userName: $userName
|
||||
type: ANIME
|
||||
sort: UPDATED_TIME_DESC
|
||||
status_in: [CURRENT, REPEATING, PLANNING]
|
||||
) {
|
||||
media {
|
||||
id
|
||||
idMal
|
||||
title {
|
||||
english
|
||||
userPreferred
|
||||
}
|
||||
description
|
||||
episodes
|
||||
genres
|
||||
status
|
||||
bannerImage
|
||||
averageScore
|
||||
coverImage {
|
||||
extraLarge
|
||||
large
|
||||
medium
|
||||
}
|
||||
countryOfOrigin
|
||||
mediaListEntry {
|
||||
id
|
||||
progress
|
||||
status
|
||||
updatedAt
|
||||
}
|
||||
nextAiringEpisode {
|
||||
timeUntilAiring
|
||||
airingAt
|
||||
episode
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
currentPage
|
||||
hasNextPage
|
||||
perPage
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export function getWatchingTitles(
|
||||
username: string,
|
||||
page: number,
|
||||
aniListToken: string,
|
||||
): Promise<GetWatchingTitles> {
|
||||
const client = new GraphQLClient("https://graphql.anilist.co/");
|
||||
|
||||
return client
|
||||
.request(
|
||||
GetWatchingTitlesQuery,
|
||||
{ userName: username, page },
|
||||
{ Authorization: `Bearer ${aniListToken}` },
|
||||
)
|
||||
.then((data) => data?.Page!)
|
||||
.catch((err) => {
|
||||
console.error("Failed to get watching titles");
|
||||
console.error(err);
|
||||
|
||||
const response = err.response;
|
||||
if (response.status === 429) {
|
||||
console.log("429, retrying in", response.headers.get("Retry-After"));
|
||||
return sleep(Number(response.headers.get("Retry-After")!) * 1000).then(
|
||||
() => getWatchingTitles(username, page, aniListToken),
|
||||
);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
type GetWatchingTitles = {
|
||||
mediaList:
|
||||
| ({
|
||||
media: {
|
||||
id: number;
|
||||
idMal: number | null;
|
||||
title: {
|
||||
english: string | null;
|
||||
userPreferred: string | null;
|
||||
} | null;
|
||||
description: string | null;
|
||||
episodes: number | null;
|
||||
genres: (string | null)[] | null;
|
||||
status:
|
||||
| "FINISHED"
|
||||
| "RELEASING"
|
||||
| "NOT_YET_RELEASED"
|
||||
| "CANCELLED"
|
||||
| "HIATUS"
|
||||
| null;
|
||||
bannerImage: string | null;
|
||||
averageScore: number | null;
|
||||
coverImage: {
|
||||
extraLarge: string | null;
|
||||
large: string | null;
|
||||
medium: string | null;
|
||||
} | null;
|
||||
countryOfOrigin: unknown;
|
||||
mediaListEntry: {
|
||||
id: number;
|
||||
progress: number | null;
|
||||
status:
|
||||
| "CURRENT"
|
||||
| "REPEATING"
|
||||
| "PLANNING"
|
||||
| "COMPLETED"
|
||||
| "DROPPED"
|
||||
| "PAUSED"
|
||||
| null;
|
||||
updatedAt: number;
|
||||
} | null;
|
||||
nextAiringEpisode: {
|
||||
timeUntilAiring: number;
|
||||
airingAt: number;
|
||||
episode: number;
|
||||
} | null;
|
||||
} | null;
|
||||
} | null)[]
|
||||
| null;
|
||||
pageInfo: {
|
||||
currentPage: number | null;
|
||||
hasNextPage: boolean | null;
|
||||
perPage: number | null;
|
||||
total: number | null;
|
||||
} | null;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
||||
import { env } from "cloudflare:workers";
|
||||
import { streamSSE } from "hono/streaming";
|
||||
|
||||
import { fetchEpisodes } from "~/controllers/episodes/getByAniListId";
|
||||
@@ -10,7 +11,6 @@ import { ErrorResponse, ErrorResponseSchema } from "~/types/schema";
|
||||
import { Title } from "~/types/title";
|
||||
|
||||
import { getUser } from "./getUser";
|
||||
import { getWatchingTitles } from "./getWatchingTitles";
|
||||
|
||||
const UserSchema = z.object({
|
||||
name: z.string(),
|
||||
@@ -129,11 +129,15 @@ app.openapi(route, async (c) => {
|
||||
let hasNextPage = true;
|
||||
|
||||
do {
|
||||
const { mediaList, pageInfo } = await getWatchingTitles(
|
||||
const stub = env.ANILIST_DO.getByName(user.name!);
|
||||
const { mediaList, pageInfo } = await stub
|
||||
.getTitles(
|
||||
user.name!,
|
||||
currentPage++,
|
||||
currentPage,
|
||||
["CURRENT", "PLANNING", "PAUSED", "REPEATING"],
|
||||
aniListToken,
|
||||
);
|
||||
)
|
||||
.then((data) => data!);
|
||||
if (!mediaList) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
GetUpcomingTitlesQuery,
|
||||
GetUserProfileQuery,
|
||||
GetUserQuery,
|
||||
GetWatchingTitlesQuery,
|
||||
MarkEpisodeAsWatchedMutation,
|
||||
MarkTitleAsWatchedMutation,
|
||||
NextSeasonPopularQuery,
|
||||
@@ -260,6 +261,33 @@ export class AnilistDurableObject extends DurableObject {
|
||||
return data?.SaveMediaListEntry;
|
||||
}
|
||||
|
||||
async getTitles(
|
||||
userName: string,
|
||||
page: number,
|
||||
statusFilters: (
|
||||
| "CURRENT"
|
||||
| "COMPLETED"
|
||||
| "PLANNING"
|
||||
| "DROPPED"
|
||||
| "PAUSED"
|
||||
| "REPEATING"
|
||||
)[],
|
||||
aniListToken: string,
|
||||
) {
|
||||
return await this.handleCachedRequest(
|
||||
`titles:${JSON.stringify({ page, statusFilters })}`,
|
||||
async () => {
|
||||
const data = await this.fetchFromAnilist(
|
||||
GetWatchingTitlesQuery,
|
||||
{ userName, page, statusFilters },
|
||||
aniListToken,
|
||||
);
|
||||
return data?.Page;
|
||||
},
|
||||
60 * 60 * 1000,
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to handle caching logic
|
||||
async handleCachedRequest(
|
||||
key: string,
|
||||
|
||||
@@ -266,3 +266,36 @@ export const NextSeasonPopularQuery = graphql(
|
||||
`,
|
||||
[HomeTitleFragment],
|
||||
);
|
||||
|
||||
export const GetWatchingTitlesQuery = graphql(
|
||||
`
|
||||
query GetWatchingTitles(
|
||||
$userName: String!
|
||||
$page: Int!
|
||||
$statusFilters: [MediaListStatus!]
|
||||
) {
|
||||
Page(page: $page, perPage: 50) {
|
||||
mediaList(
|
||||
userName: $userName
|
||||
type: ANIME
|
||||
sort: UPDATED_TIME_DESC
|
||||
status_in: $statusFilters
|
||||
) {
|
||||
media {
|
||||
...Media
|
||||
mediaListEntry {
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
currentPage
|
||||
hasNextPage
|
||||
perPage
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
[MediaFragment],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user