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 { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { env } from "cloudflare:workers";
|
||||||
import { streamSSE } from "hono/streaming";
|
import { streamSSE } from "hono/streaming";
|
||||||
|
|
||||||
import { fetchEpisodes } from "~/controllers/episodes/getByAniListId";
|
import { fetchEpisodes } from "~/controllers/episodes/getByAniListId";
|
||||||
@@ -10,7 +11,6 @@ import { ErrorResponse, ErrorResponseSchema } from "~/types/schema";
|
|||||||
import { Title } from "~/types/title";
|
import { Title } from "~/types/title";
|
||||||
|
|
||||||
import { getUser } from "./getUser";
|
import { getUser } from "./getUser";
|
||||||
import { getWatchingTitles } from "./getWatchingTitles";
|
|
||||||
|
|
||||||
const UserSchema = z.object({
|
const UserSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -129,11 +129,15 @@ app.openapi(route, async (c) => {
|
|||||||
let hasNextPage = true;
|
let hasNextPage = true;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const { mediaList, pageInfo } = await getWatchingTitles(
|
const stub = env.ANILIST_DO.getByName(user.name!);
|
||||||
user.name!,
|
const { mediaList, pageInfo } = await stub
|
||||||
currentPage++,
|
.getTitles(
|
||||||
aniListToken,
|
user.name!,
|
||||||
);
|
currentPage,
|
||||||
|
["CURRENT", "PLANNING", "PAUSED", "REPEATING"],
|
||||||
|
aniListToken,
|
||||||
|
)
|
||||||
|
.then((data) => data!);
|
||||||
if (!mediaList) {
|
if (!mediaList) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
GetUpcomingTitlesQuery,
|
GetUpcomingTitlesQuery,
|
||||||
GetUserProfileQuery,
|
GetUserProfileQuery,
|
||||||
GetUserQuery,
|
GetUserQuery,
|
||||||
|
GetWatchingTitlesQuery,
|
||||||
MarkEpisodeAsWatchedMutation,
|
MarkEpisodeAsWatchedMutation,
|
||||||
MarkTitleAsWatchedMutation,
|
MarkTitleAsWatchedMutation,
|
||||||
NextSeasonPopularQuery,
|
NextSeasonPopularQuery,
|
||||||
@@ -260,6 +261,33 @@ export class AnilistDurableObject extends DurableObject {
|
|||||||
return data?.SaveMediaListEntry;
|
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
|
// Helper to handle caching logic
|
||||||
async handleCachedRequest(
|
async handleCachedRequest(
|
||||||
key: string,
|
key: string,
|
||||||
|
|||||||
@@ -266,3 +266,36 @@ export const NextSeasonPopularQuery = graphql(
|
|||||||
`,
|
`,
|
||||||
[HomeTitleFragment],
|
[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