feat: create routes to load popular titles

This commit is contained in:
2024-10-27 13:59:49 -04:00
parent 99963083f0
commit 592cc08853
13 changed files with 554 additions and 21 deletions

View File

@@ -0,0 +1,157 @@
import { graphql } from "gql.tada";
import { GraphQLClient } from "graphql-request";
import { getCurrentAndNextSeason } from "~/libs/getCurrentAndNextSeason";
import { sleep } from "~/libs/sleep";
import { HomeTitleFragment } from "~/types/title/homeTitle";
import { mapTitle } from "../mapTitle";
const BrowsePopularQuery = graphql(
`
query BrowsePopular(
$season: MediaSeason!
$seasonYear: Int!
$nextSeason: MediaSeason!
$nextYear: Int!
$limit: Int!
) {
trending: Page(page: 1, perPage: $limit) {
media(sort: TRENDING_DESC, type: ANIME, isAdult: false) {
...HomeTitle
}
}
season: Page(page: 1, perPage: $limit) {
media(
season: $season
seasonYear: $seasonYear
sort: POPULARITY_DESC
type: ANIME
isAdult: false
) {
...HomeTitle
}
}
nextSeason: Page(page: 1, perPage: 1) {
media(
season: $nextSeason
seasonYear: $nextYear
sort: START_DATE
type: ANIME
isAdult: false
) {
nextAiringEpisode {
airingAt
timeUntilAiring
}
}
}
}
`,
[HomeTitleFragment],
);
const NextSeasonPopularQuery = graphql(`
query NextSeasonPopular(
$nextSeason: MediaSeason
$nextYear: Int
$limit: Int!
) {
Page(page: 1, perPage: $limit) {
media(
season: $nextSeason
seasonYear: $nextYear
sort: POPULARITY_DESC
type: ANIME
isAdult: false
) {
...media
}
}
}
fragment media on Media {
id
title {
english
userPreferred
}
coverImage {
extraLarge
large
medium
}
}
`);
export async function fetchPopularTitlesFromAnilist(
limit: number,
): Promise<any> {
const client = new GraphQLClient("https://graphql.anilist.co/");
const {
current: { season: currentSeason, year: currentYear },
next: { season: nextSeason, year: nextYear },
} = getCurrentAndNextSeason();
try {
const data = await client.request(BrowsePopularQuery, {
limit,
season: currentSeason,
seasonYear: currentYear,
nextSeason,
nextYear,
});
if (!data) return undefined;
const trendingTitles = data.trending?.media?.map((title) =>
mapTitle(title),
);
const popularSeasonTitles = data.season?.media?.map((title) =>
mapTitle(title),
);
if (!data.nextSeason?.media?.[0]?.nextAiringEpisode) {
return {
trending: trendingTitles,
season: popularSeasonTitles,
};
}
return await client
.request(NextSeasonPopularQuery, {
limit,
nextSeason,
nextYear,
})
.then((data) => ({
trending: trendingTitles,
season: popularSeasonTitles,
upcoming: data?.Page?.media?.map((title) => mapTitle(title)),
}));
} catch (error) {
const response = error.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(
() => fetchPopularTitlesFromAnilist(limit),
);
}
throw error;
}
}
type SearchResultsResponse = {
results:
| ({
id: number;
title: { userPreferred: string | null; english: string | null } | null;
coverImage: {
extraLarge: string | null;
large: string | null;
medium: string | null;
} | null;
} | null)[]
| null;
hasNextPage: boolean | null | undefined;
};

View File

@@ -0,0 +1,56 @@
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import { ErrorResponse, SuccessResponseSchema } from "~/types/schema";
import { HomeTitle } from "~/types/title/homeTitle";
import { fetchPopularTitlesFromAnilist } from "./anilist";
const BrowsePopularResponse = SuccessResponseSchema(
z.object({
trending: z.array(HomeTitle),
popular: z.array(HomeTitle),
upcoming: z.array(HomeTitle).optional(),
}),
);
const app = new OpenAPIHono();
const route = createRoute({
tags: ["aniplay", "title"],
operationId: "browsePopularTitles",
summary: "Get a preview of popular titles",
method: "get",
path: "/",
request: {
query: z.object({
limit: z
.number({ coerce: true })
.int()
.default(10)
.describe("The number of titles to return"),
}),
},
responses: {
200: {
content: {
"application/json": {
schema: BrowsePopularResponse,
},
},
description: "Returns an object containing a preview of popular titles",
},
},
});
app.openapi(route, async (c) => {
const limit = Number(c.req.query("limit") ?? 10);
const response = await fetchPopularTitlesFromAnilist(limit);
if (!response) {
return c.json(ErrorResponse, { status: 500 });
}
return c.json({ success: true, result: response });
});
export default app;