mirror of
https://github.com/AnimeThemes/animethemes-web.git
synced 2026-07-11 01:24:31 +02:00
feat: Migrated to API resolvers (#115)
- All data is now fetched from the API instead of the database. - Added staging mode. - Fixed cover images still showing old images when switching pages. - Fixed first letter filter not working.
This commit is contained in:
@@ -13,16 +13,12 @@ root directory.
|
||||
This is a list of all available options:
|
||||
|
||||
```ini
|
||||
; Datebase configuration
|
||||
; Only username and password are required.
|
||||
; All other fields take the specified values by default, but can be changed if wanted.
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=...
|
||||
DB_PASSWORD=...
|
||||
DB_DATABASE=animethemes
|
||||
; Back-end API configuration
|
||||
; These values are required.
|
||||
ANIMETHEMES_API_URL=http://localhost
|
||||
ANIMETHEMES_API_KEY=...
|
||||
|
||||
; (Optional) The URL to the AnimeThemes API to use.
|
||||
; (Optional) The URL to the AnimeThemes API to use on the front-end.
|
||||
; For development this is "http://localhost" by default.
|
||||
; For production this is "https://staging.animethemes.moe" by default.
|
||||
NEXT_PUBLIC_API_URL=http://localhost
|
||||
|
||||
+8
-1
@@ -1,12 +1,19 @@
|
||||
const { info } = require("next/dist/build/output/log");
|
||||
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
});
|
||||
|
||||
if (process.env.NEXT_PUBLIC_STAGING) {
|
||||
info("Running animethemes-web in staging mode!");
|
||||
}
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "",
|
||||
reactStrictMode: true,
|
||||
compiler: {
|
||||
styledComponents: true
|
||||
},
|
||||
swcMinify: true
|
||||
swcMinify: true,
|
||||
staticPageGenerationTimeout: 3600
|
||||
});
|
||||
|
||||
Generated
+243
-566
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -22,12 +22,12 @@
|
||||
"common-tags": "^1.8.0",
|
||||
"framer-motion": "^6.2.1",
|
||||
"graphql": "^15.6.1",
|
||||
"graphql-parse-resolve-info": "^4.12.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"knex": "^0.95.11",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^4.0.12",
|
||||
"mysql2": "^2.3.3",
|
||||
"next": "^12.1.0",
|
||||
"next": "^12.1.5",
|
||||
"p-limit": "^3.1.0",
|
||||
"prismjs": "^1.27.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
||||
@@ -7,7 +7,7 @@ export function CoverImage({ resourceWithImages, ...props }) {
|
||||
|
||||
return (
|
||||
<AspectRatio ratio={2 / 3}>
|
||||
<FullWidthImage src={largeCover} style={{ backgroundImage: `url(${smallCover})` }} {...props}/>
|
||||
<FullWidthImage key={largeCover} src={largeCover} style={{ backgroundImage: `url(${smallCover})` }} {...props}/>
|
||||
</AspectRatio>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export function SearchAnime({ searchQuery }) {
|
||||
|
||||
const entitySearch = useEntitySearch("anime", searchQuery, {
|
||||
filters: {
|
||||
"name][like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
"name-like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
season: filter.season,
|
||||
year: filter.year
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ export function SearchArtist({ searchQuery }) {
|
||||
|
||||
const entitySearch = useEntitySearch("artist", searchQuery, {
|
||||
filters: {
|
||||
"name][like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
"name-like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
},
|
||||
sortBy: searchQuery ? null : filter.sortBy
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export function SearchSeries({ searchQuery }) {
|
||||
|
||||
const entitySearch = useEntitySearch("series", searchQuery, {
|
||||
filters: {
|
||||
"name][like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
"name-like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
},
|
||||
sortBy: searchQuery ? null : filter.sortBy
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export function SearchStudio({ searchQuery }) {
|
||||
|
||||
const entitySearch = useEntitySearch("studio", searchQuery, {
|
||||
filters: {
|
||||
"name][like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
"name-like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
},
|
||||
sortBy: searchQuery ? null : filter.sortBy
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ export function SearchTheme({ searchQuery }) {
|
||||
const entitySearch = useEntitySearch("theme", searchQuery, {
|
||||
filters: {
|
||||
has: "song",
|
||||
"song][title][like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
"song][title-like": filter.firstLetter ? `${filter.firstLetter}%` : null,
|
||||
type: filter.type
|
||||
},
|
||||
sortBy: searchQuery ? null : filter.sortBy
|
||||
|
||||
@@ -22,8 +22,6 @@ export default function useImage(resourceWithImages) {
|
||||
}
|
||||
|
||||
useImage.fragment = gql`
|
||||
#graphql
|
||||
|
||||
fragment useImage_resourceWithImages on ResourceWithImages {
|
||||
images {
|
||||
link
|
||||
|
||||
@@ -1,370 +1,506 @@
|
||||
const { videoSource, imageFacet, animeSeason, resourceSite, themeType, videoOverlap } = require("lib/server/animethemes/enums");
|
||||
const pLimit = require("p-limit");
|
||||
const { parseResolveInfo } = require("graphql-parse-resolve-info");
|
||||
const devLog = require("utils/devLog");
|
||||
|
||||
const knex = require("knex")({
|
||||
client: "mysql2",
|
||||
connection: {
|
||||
host: process.env.DB_HOST || "127.0.0.1",
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE || "animethemes"
|
||||
const limit = pLimit(5);
|
||||
|
||||
const API_BASE_URL = `${process.env.ANIMETHEMES_API_URL}/api`;
|
||||
|
||||
const INCLUDES = {
|
||||
Anime: {
|
||||
synonyms: "animesynonyms",
|
||||
themes: "animethemes",
|
||||
series: "series",
|
||||
studios: "studios",
|
||||
resources: "resources",
|
||||
images: "images"
|
||||
},
|
||||
Theme: {
|
||||
song: "song",
|
||||
anime: "anime",
|
||||
entries: "animethemeentries"
|
||||
},
|
||||
Artist: {
|
||||
performances: "songs",
|
||||
resources: "resources",
|
||||
images: "images",
|
||||
groups: "groups",
|
||||
members: "members"
|
||||
},
|
||||
Song: {
|
||||
themes: "animethemes",
|
||||
performances: "artists"
|
||||
},
|
||||
Entry: {
|
||||
videos: "videos",
|
||||
theme: "animetheme"
|
||||
},
|
||||
Video: {
|
||||
entries: "animethemeentries"
|
||||
},
|
||||
Series: {
|
||||
anime: "anime"
|
||||
},
|
||||
Studio: {
|
||||
anime: "anime",
|
||||
resources: "resources"
|
||||
},
|
||||
ResourceWithImages: {
|
||||
images: "images"
|
||||
},
|
||||
Performance: {
|
||||
song: "_",
|
||||
artist: "_"
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const ALLOWED_INCLUDES = {
|
||||
Anime: [
|
||||
"animesynonyms",
|
||||
"series",
|
||||
"animethemes.song.artists",
|
||||
"animethemes.song.artists.images",
|
||||
"images",
|
||||
"resources",
|
||||
"studios",
|
||||
"animethemes.animethemeentries.videos.animethemeentries.animetheme.anime",
|
||||
"animethemes.animethemeentries.videos.animethemeentries.animetheme.anime.images",
|
||||
"animethemes.animethemeentries.videos.animethemeentries.animetheme.animethemeentries.videos",
|
||||
"animethemes.animethemeentries.videos.animethemeentries.animetheme.song.artists"
|
||||
],
|
||||
Theme: [
|
||||
"anime.images",
|
||||
"animethemeentries.videos",
|
||||
"song.artists"
|
||||
],
|
||||
Artist: [
|
||||
"songs.animethemes.anime",
|
||||
"members",
|
||||
"groups",
|
||||
"resources",
|
||||
"images",
|
||||
"songs.artists",
|
||||
"songs.animethemes.song",
|
||||
"songs.animethemes.song.artists",
|
||||
"songs.animethemes.anime.images",
|
||||
"songs.animethemes.animethemeentries",
|
||||
"songs.animethemes.animethemeentries.videos"
|
||||
],
|
||||
Series: [
|
||||
"anime",
|
||||
"anime.images",
|
||||
"anime.animethemes.animethemeentries.videos",
|
||||
"anime.animethemes.song"
|
||||
],
|
||||
Studio: [
|
||||
"anime",
|
||||
"anime.images",
|
||||
"anime.animethemes.animethemeentries.videos",
|
||||
"anime.animethemes.song",
|
||||
"resources"
|
||||
],
|
||||
Song: [
|
||||
"animethemes.anime",
|
||||
"artists"
|
||||
],
|
||||
Video: [
|
||||
"animethemeentries.animetheme.anime"
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
anime: (_, { id, slug }) => knex("anime")
|
||||
.where((builder) => {
|
||||
builder.where("deleted_at", null);
|
||||
if (id) {
|
||||
builder.where("anime_id", id);
|
||||
}
|
||||
if (slug) {
|
||||
builder.where("slug", slug);
|
||||
}
|
||||
})
|
||||
.first(),
|
||||
animeAll: (_, { limit, year, season }) => {
|
||||
const query = knex("anime");
|
||||
|
||||
query.where("deleted_at", null);
|
||||
|
||||
if (year) {
|
||||
query.where("year", year);
|
||||
}
|
||||
if (season) {
|
||||
query.where(
|
||||
"season",
|
||||
[ ...animeSeason.values() ]
|
||||
.findIndex((seasonName) => seasonName.toLowerCase() === season.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
if (limit) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
return query.select();
|
||||
},
|
||||
theme: (_, { id }) => knex("anime_themes")
|
||||
.where((builder) => {
|
||||
builder.where("deleted_at", null);
|
||||
if (id) {
|
||||
builder.where("theme_id", id);
|
||||
}
|
||||
})
|
||||
.first(),
|
||||
themeAll: (_, { limit, orderBy, orderDesc }) => {
|
||||
const query = knex("anime_themes");
|
||||
|
||||
query.where("deleted_at", null);
|
||||
|
||||
if (orderBy) {
|
||||
query.orderBy(orderBy, orderDesc ? "desc" : "asc");
|
||||
}
|
||||
|
||||
if (limit) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
return query.select();
|
||||
},
|
||||
artist: (_, { id, slug }) => knex("artists")
|
||||
.where((builder) => {
|
||||
builder.where("deleted_at", null);
|
||||
if (id) {
|
||||
builder.where("artist_id", id);
|
||||
}
|
||||
if (slug) {
|
||||
builder.where("slug", slug);
|
||||
}
|
||||
})
|
||||
.first(),
|
||||
artistAll: (_, { limit }) => {
|
||||
const query = knex("artists");
|
||||
|
||||
query.where("deleted_at", null);
|
||||
|
||||
if (limit) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
return query.select();
|
||||
},
|
||||
series: (_, { id, slug }) => knex("series")
|
||||
.where((builder) => {
|
||||
builder.where("deleted_at", null);
|
||||
if (id) {
|
||||
builder.where("series_id", id);
|
||||
}
|
||||
if (slug) {
|
||||
builder.where("slug", slug);
|
||||
}
|
||||
})
|
||||
.first(),
|
||||
seriesAll: (_, { limit }) => {
|
||||
const query = knex("series");
|
||||
|
||||
query.where("deleted_at", null);
|
||||
|
||||
if (limit) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
return query.select();
|
||||
},
|
||||
studio: (_, { id, slug }) => knex("studios")
|
||||
.where((builder) => {
|
||||
builder.where("deleted_at", null);
|
||||
if (id) {
|
||||
builder.where("studio_id", id);
|
||||
}
|
||||
if (slug) {
|
||||
builder.where("slug", slug);
|
||||
}
|
||||
})
|
||||
.first(),
|
||||
studioAll: (_, { limit }) => {
|
||||
const query = knex("studios");
|
||||
|
||||
query.where("deleted_at", null);
|
||||
|
||||
if (limit) {
|
||||
query.limit(limit);
|
||||
}
|
||||
|
||||
return query.select();
|
||||
},
|
||||
year: (_, { value }) => ({ value }),
|
||||
yearAll: () => knex("anime")
|
||||
.where("deleted_at", null)
|
||||
.groupBy("year")
|
||||
.select("year")
|
||||
.then((results) => results.map((anime) => ({ value: anime.year }))),
|
||||
season: (_, { value, year }) => ({
|
||||
value: [ ...animeSeason.values() ]
|
||||
.findIndex((seasonName) => seasonName.toLowerCase() === value.toLowerCase()),
|
||||
year: { value: year }
|
||||
anime: apiResolver({
|
||||
endpoint: (_, { slug }) => `/anime/${slug}`,
|
||||
extractor: (result) => result.anime
|
||||
}),
|
||||
animeAll: apiResolver({
|
||||
endpoint: () => `/anime`,
|
||||
extractor: (result) => result.anime,
|
||||
pagination: true
|
||||
}),
|
||||
theme: apiResolver({
|
||||
endpoint: (_, { id }) => `/animetheme/${id}`,
|
||||
extractor: (result) => result.animetheme
|
||||
}),
|
||||
themeAll: apiResolver({
|
||||
endpoint: (_, { limit, orderBy, orderDesc }) => `/animetheme?sort=${orderDesc ? "-" : ""}${orderBy}&page[size]=${limit}`,
|
||||
extractor: (result) => result.animethemes
|
||||
}),
|
||||
artist: apiResolver({
|
||||
endpoint: (_, { slug }) => `/artist/${slug}`,
|
||||
extractor: (result) => result.artist
|
||||
}),
|
||||
artistAll: apiResolver({
|
||||
endpoint: () => `/artist`,
|
||||
extractor: (result) => result.artists,
|
||||
pagination: true
|
||||
}),
|
||||
series: apiResolver({
|
||||
endpoint: (_, { slug }) => `/series/${slug}`,
|
||||
extractor: (result) => result.series
|
||||
}),
|
||||
seriesAll: apiResolver({
|
||||
endpoint: () => `/series`,
|
||||
extractor: (result) => result.series,
|
||||
pagination: true
|
||||
}),
|
||||
studio: apiResolver({
|
||||
endpoint: (_, { slug }) => `/studio/${slug}`,
|
||||
extractor: (result) => result.studio
|
||||
}),
|
||||
studioAll: apiResolver({
|
||||
endpoint: () => `/studio`,
|
||||
extractor: (result) => result.studios,
|
||||
pagination: true
|
||||
}),
|
||||
year: (_, { value }) => ({ value }),
|
||||
yearAll: apiResolver({
|
||||
endpoint: () => `/animeyear`,
|
||||
transformer: (yearList) => yearList.map((year) => ({ value: year }))
|
||||
}),
|
||||
season: (_, { year, value }) => ({ value, year: { value: year } }),
|
||||
seasonAll: apiResolver({
|
||||
endpoint: (_, { year }) => `/animeyear/${year}`,
|
||||
extractor: (result) => Object.keys(result),
|
||||
transformer: (seasons, _, { year }) => seasons.map((season) => ({ value: season, year: { value: year } }))
|
||||
}),
|
||||
page: apiResolver({
|
||||
endpoint: (_, { slug }) => `/page/${slug}`,
|
||||
extractor: (result) => result.page
|
||||
}),
|
||||
pageAll: apiResolver({
|
||||
endpoint: () => `/page`,
|
||||
extractor: (result) => result.pages,
|
||||
pagination: true
|
||||
}),
|
||||
seasonAll: (_, { year }) => knex("anime")
|
||||
.where((builder) => {
|
||||
builder.where("deleted_at", null);
|
||||
if (year) {
|
||||
builder.where("year", year);
|
||||
}
|
||||
})
|
||||
.groupBy([ "season", "year" ])
|
||||
.select([ "season", "year" ])
|
||||
.then((results) => results.map((anime) => ({ value: anime.season, year: { value: year } }))),
|
||||
page: (_, { id, slug }) => knex("pages")
|
||||
.where((builder) => {
|
||||
builder.where("deleted_at", null);
|
||||
if (id) {
|
||||
builder.where("page_id", id);
|
||||
}
|
||||
if (slug) {
|
||||
builder.where("slug", slug);
|
||||
}
|
||||
})
|
||||
.first(),
|
||||
pageAll: () => {
|
||||
const query = knex("pages");
|
||||
|
||||
query.where("deleted_at", null);
|
||||
|
||||
return query.select();
|
||||
},
|
||||
counter: () => ({})
|
||||
},
|
||||
Year: {
|
||||
seasons: (year) => knex("anime")
|
||||
.where("deleted_at", null)
|
||||
.where("year", year.value)
|
||||
.groupBy("season")
|
||||
.select("season")
|
||||
.then((results) => results.map((anime) => ({ value: anime.season, year }))),
|
||||
seasons: apiResolver({
|
||||
endpoint: (year) => `/animeyear/${year.value}`,
|
||||
extractor: (result) => Object.keys(result),
|
||||
transformer: (seasons, year) => seasons.map((season) => ({ value: season, year }))
|
||||
}),
|
||||
},
|
||||
Season: {
|
||||
value: (season) => animeSeason.get(season.value),
|
||||
anime: (season) => knex("anime")
|
||||
.where((builder) => {
|
||||
builder.where("deleted_at", null);
|
||||
|
||||
builder.where("season", season.value);
|
||||
|
||||
if (season.year) {
|
||||
builder.where("year", season.year.value);
|
||||
}
|
||||
})
|
||||
.select(),
|
||||
anime: apiResolver({
|
||||
endpoint: (season) => `/anime?filter[year]=${season.year.value}&filter[season]=${season.value}`,
|
||||
extractor: (result) => result.anime,
|
||||
pagination: true
|
||||
}),
|
||||
},
|
||||
Counter: {
|
||||
anime: () => knex("anime").where("deleted_at", null).count({ count: "*" }).first().then((first) => first.count),
|
||||
artist: () => knex("artists").where("deleted_at", null).count({ count: "*" }).first().then((first) => first.count),
|
||||
series: () => knex("series").where("deleted_at", null).count({ count: "*" }).first().then((first) => first.count),
|
||||
studio: () => knex("studios").where("deleted_at", null).count({ count: "*" }).first().then((first) => first.count),
|
||||
video: () => knex("videos").where("deleted_at", null).count({ count: "*" }).first().then((first) => first.count),
|
||||
year: () => knex("anime").where("deleted_at", null).countDistinct({ count: "year" }).first().then((first) => first.count),
|
||||
season: () => knex("anime").where("deleted_at", null).countDistinct({ count: [ "year", "season" ] }).first().then((first) => first.count)
|
||||
anime: () => 0,
|
||||
artist: () => 0,
|
||||
series: () => 0,
|
||||
studio: () => 0,
|
||||
video: () => 0,
|
||||
year: () => 0,
|
||||
season: () => 0,
|
||||
},
|
||||
Anime: {
|
||||
id: (anime) => anime.anime_id,
|
||||
season: (anime) => animeSeason.get(anime.season),
|
||||
synonyms: (anime) => knex("anime_synonyms").where("deleted_at", null).where({ anime_id: anime.anime_id }).select(),
|
||||
themes: (anime) => knex("anime_themes").where("deleted_at", null).where({ anime_id: anime.anime_id }).select(),
|
||||
series: (anime) => knex("series")
|
||||
.innerJoin("anime_series", "anime_series.series_id", "series.series_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "anime_series.anime_id": anime.anime_id })
|
||||
.select("series.*"),
|
||||
studios: (anime) => knex("studios")
|
||||
.innerJoin("anime_studio", "anime_studio.studio_id", "studios.studio_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "anime_studio.anime_id": anime.anime_id })
|
||||
.select("studios.*"),
|
||||
resources: (anime) => knex("resources")
|
||||
.innerJoin("anime_resource", "anime_resource.resource_id", "resources.resource_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "anime_resource.anime_id": anime.anime_id })
|
||||
.select("resources.*"),
|
||||
images: (anime) => knex("images")
|
||||
.innerJoin("anime_image", "anime_image.image_id", "images.image_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "anime_image.anime_id": anime.anime_id })
|
||||
.select("images.*")
|
||||
synonyms: apiResolver({
|
||||
endpoint: (anime) => `/anime/${anime.slug}`,
|
||||
field: "animesynonyms",
|
||||
extractor: (result) => result.anime.animesynonyms,
|
||||
type: "Anime",
|
||||
baseInclude: INCLUDES.Anime.synonyms
|
||||
}),
|
||||
themes: apiResolver({
|
||||
endpoint: (anime) => `/anime/${anime.slug}`,
|
||||
field: "animethemes",
|
||||
extractor: (result) => result.anime.animethemes,
|
||||
transformer: (themes, anime) => themes.map((theme) => ({ ...theme, anime })),
|
||||
type: "Anime",
|
||||
baseInclude: INCLUDES.Anime.themes
|
||||
}),
|
||||
series: apiResolver({
|
||||
endpoint: (anime) => `/anime/${anime.slug}`,
|
||||
field: "series",
|
||||
extractor: (result) => result.anime.series,
|
||||
type: "Anime",
|
||||
baseInclude: INCLUDES.Anime.series
|
||||
}),
|
||||
studios: apiResolver({
|
||||
endpoint: (anime) => `/anime/${anime.slug}`,
|
||||
field: "studios",
|
||||
extractor: (result) => result.anime.studios,
|
||||
type: "Anime",
|
||||
baseInclude: INCLUDES.Anime.studios
|
||||
}),
|
||||
resources: apiResolver({
|
||||
endpoint: (anime) => `/anime/${anime.slug}`,
|
||||
field: "resources",
|
||||
extractor: (result) => result.anime.resources,
|
||||
type: "Anime",
|
||||
baseInclude: INCLUDES.Anime.resources
|
||||
}),
|
||||
images: apiResolver({
|
||||
endpoint: (anime) => `/anime/${anime.slug}`,
|
||||
field: "images",
|
||||
extractor: (result) => result.anime.images,
|
||||
type: "Anime",
|
||||
baseInclude: INCLUDES.Anime.images
|
||||
}),
|
||||
},
|
||||
Theme: {
|
||||
id: (theme) => theme.theme_id,
|
||||
type: (theme) => themeType.get(theme.type),
|
||||
sequence: (theme) => theme.sequence || 0,
|
||||
song: (theme) => knex("songs").where("deleted_at", null).where({ song_id: theme.song_id }).first(),
|
||||
anime: (theme) => knex("anime").where("deleted_at", null).where({ anime_id: theme.anime_id }).first(),
|
||||
entries: (theme) => knex("anime_theme_entries").where("deleted_at", null).where({ theme_id: theme.theme_id }).select()
|
||||
song: apiResolver({
|
||||
endpoint: (theme) => `/animetheme/${theme.id}`,
|
||||
field: "song",
|
||||
extractor: (result) => result.animetheme.song,
|
||||
type: "Theme",
|
||||
baseInclude: INCLUDES.Theme.song
|
||||
}),
|
||||
anime: apiResolver({
|
||||
endpoint: (theme) => `/animetheme/${theme.id}`,
|
||||
field: "anime",
|
||||
extractor: (result) => result.animetheme.anime,
|
||||
type: "Theme",
|
||||
baseInclude: INCLUDES.Theme.anime
|
||||
}),
|
||||
entries: apiResolver({
|
||||
endpoint: (theme) => `/animetheme/${theme.id}`,
|
||||
field: "animethemeentries",
|
||||
extractor: (result) => result.animetheme.animethemeentries,
|
||||
type: "Theme",
|
||||
baseInclude: INCLUDES.Theme.entries
|
||||
}),
|
||||
},
|
||||
Artist: {
|
||||
performances: (artist) => knex("artist_song")
|
||||
.innerJoin("songs", "artist_song.song_id", "songs.song_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "artist_song.artist_id": artist.artist_id })
|
||||
.select("artist_song.*"),
|
||||
resources: (artist) => knex("resources")
|
||||
.innerJoin("artist_resource", "artist_resource.resource_id", "resources.resource_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "artist_resource.artist_id": artist.artist_id })
|
||||
.select("resources.*"),
|
||||
images: (artist) => knex("images")
|
||||
.innerJoin("artist_image", "artist_image.image_id", "images.image_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "artist_image.artist_id": artist.artist_id })
|
||||
.select("images.*"),
|
||||
groups: (artist) => knex("artists")
|
||||
.innerJoin("artist_member", "artist_member.artist_id", "artists.artist_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "artist_member.member_id": artist.artist_id })
|
||||
.select("artist_member.*"),
|
||||
members: (artist) => knex("artists")
|
||||
.innerJoin("artist_member", "artist_member.member_id", "artists.artist_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "artist_member.artist_id": artist.artist_id })
|
||||
.select("artist_member.*")
|
||||
},
|
||||
ArtistMembership: {
|
||||
group: (membership) => knex("artists").where("deleted_at", null).where({ artist_id: membership.artist_id }).first(),
|
||||
member: (membership) => knex("artists").where("deleted_at", null).where({ artist_id: membership.member_id }).first(),
|
||||
performances: apiResolver({
|
||||
endpoint: (artist) => `/artist/${artist.slug}`,
|
||||
field: "songs",
|
||||
extractor: (result) => result.artist.songs,
|
||||
transformer: (songs, artist) => songs.map(({ as, ...song }) => ({ as, song, artist })),
|
||||
type: "Artist",
|
||||
baseInclude: INCLUDES.Artist.performances
|
||||
}),
|
||||
resources: apiResolver({
|
||||
endpoint: (artist) => `/artist/${artist.slug}`,
|
||||
field: "resources",
|
||||
extractor: (result) => result.artist.resources,
|
||||
type: "Artist",
|
||||
baseInclude: INCLUDES.Artist.resources
|
||||
}),
|
||||
images: apiResolver({
|
||||
endpoint: (artist) => `/artist/${artist.slug}`,
|
||||
field: "images",
|
||||
extractor: (result) => result.artist.images,
|
||||
type: "Artist",
|
||||
baseInclude: INCLUDES.Artist.images
|
||||
}),
|
||||
groups: apiResolver({
|
||||
endpoint: (artist) => `/artist/${artist.slug}`,
|
||||
field: "groups",
|
||||
extractor: (result) => result.artist.groups,
|
||||
transformer: (groups, artist) => groups.map((group) => ({ group, member: artist })),
|
||||
type: "Artist",
|
||||
baseInclude: INCLUDES.Artist.groups
|
||||
}),
|
||||
members: apiResolver({
|
||||
endpoint: (artist) => `/artist/${artist.slug}`,
|
||||
field: "members",
|
||||
extractor: (result) => result.artist.members,
|
||||
transformer: (members, artist) => members.map((member) => ({ member, group: artist })),
|
||||
type: "Artist",
|
||||
baseInclude: INCLUDES.Artist.members
|
||||
}),
|
||||
},
|
||||
Song: {
|
||||
themes: (song) => knex("anime_themes").where("deleted_at", null).where({ song_id: song.song_id }).select(),
|
||||
performances: (song) => knex("artist_song")
|
||||
.innerJoin("artists", "artist_song.artist_id", "artists.artist_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "artist_song.song_id": song.song_id })
|
||||
.select("artist_song.*"),
|
||||
},
|
||||
Performance: {
|
||||
artist: (performance) => knex("artists").where("deleted_at", null).where({ artist_id: performance.artist_id }).first(),
|
||||
song: (performance) => knex("songs").where("deleted_at", null).where({ song_id: performance.song_id }).first()
|
||||
themes: apiResolver({
|
||||
endpoint: (song) => `/song/${song.id}`,
|
||||
field: "animethemes",
|
||||
extractor: (result) => result.song.animethemes,
|
||||
type: "Song",
|
||||
baseInclude: INCLUDES.Song.themes
|
||||
}),
|
||||
performances: apiResolver({
|
||||
endpoint: (song) => `/song/${song.id}`,
|
||||
field: "artists",
|
||||
extractor: (result) => result.song.artists,
|
||||
transformer: (artists, song) => artists.map(({ as, ...artist }) => ({ as, artist, song })),
|
||||
type: "Song",
|
||||
baseInclude: INCLUDES.Song.performances
|
||||
}),
|
||||
},
|
||||
Entry: {
|
||||
version: (entry) => entry.version || 1,
|
||||
videos: (entry) => knex("videos")
|
||||
.innerJoin("anime_theme_entry_video", "anime_theme_entry_video.video_id", "videos.video_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "anime_theme_entry_video.entry_id": entry.entry_id })
|
||||
.select("videos.*"),
|
||||
theme: (entry) => knex("anime_themes").where("deleted_at", null).where({ theme_id: entry.theme_id }).first(),
|
||||
videos: apiResolver({
|
||||
endpoint: (entry) => `/animethemeentry/${entry.id}`,
|
||||
field: "videos",
|
||||
extractor: (result) => result.animethemeentry.videos,
|
||||
type: "Entry",
|
||||
baseInclude: INCLUDES.Entry.videos
|
||||
}),
|
||||
theme: apiResolver({
|
||||
endpoint: (entry) => `/animethemeentry/${entry.id}`,
|
||||
field: "animetheme",
|
||||
extractor: (result) => result.animethemeentry.animetheme,
|
||||
type: "Entry",
|
||||
baseInclude: INCLUDES.Entry.theme
|
||||
}),
|
||||
},
|
||||
Video: {
|
||||
source: (video) => videoSource.get(video.source),
|
||||
overlap: (video) => videoOverlap.get(video.overlap),
|
||||
tags: (video) => {
|
||||
const tags = [];
|
||||
|
||||
if (video.nc) {
|
||||
tags.push("NC");
|
||||
}
|
||||
if (video.source === 2 || video.source === 3) {
|
||||
tags.push(videoSource.get(video.source));
|
||||
}
|
||||
if (video.resolution && video.resolution !== 720) {
|
||||
tags.push(video.resolution);
|
||||
}
|
||||
|
||||
if (video.subbed) {
|
||||
tags.push("Subbed");
|
||||
} else if (video.lyrics) {
|
||||
tags.push("Lyrics");
|
||||
}
|
||||
|
||||
return tags.join("");
|
||||
},
|
||||
entries: (video) => knex("anime_theme_entries")
|
||||
.innerJoin("anime_theme_entry_video", "anime_theme_entry_video.entry_id", "anime_theme_entries.entry_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "anime_theme_entry_video.video_id": video.video_id })
|
||||
.select("anime_theme_entries.*")
|
||||
},
|
||||
Image: {
|
||||
facet: (image) => imageFacet.get(image.facet),
|
||||
link: (image) => `https://animethemes-stag-images.fra1.cdn.digitaloceanspaces.com/${image.path}`
|
||||
},
|
||||
Resource: {
|
||||
site: (resource) => resourceSite.get(resource.site)
|
||||
entries: apiResolver({
|
||||
endpoint: (video) => `/video/${video.basename}`,
|
||||
field: "animethemeentries",
|
||||
extractor: (result) => result.video.animethemeentries,
|
||||
type: "Video",
|
||||
baseInclude: INCLUDES.Video.entries
|
||||
}),
|
||||
},
|
||||
Series: {
|
||||
anime: (series) => knex("anime")
|
||||
.innerJoin("anime_series", "anime_series.anime_id", "anime.anime_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "anime_series.series_id": series.series_id })
|
||||
.select("anime.*")
|
||||
anime: apiResolver({
|
||||
endpoint: (series) => `/series/${series.slug}`,
|
||||
field: "anime",
|
||||
extractor: (result) => result.series.anime,
|
||||
type: "Series",
|
||||
baseInclude: INCLUDES.Series.anime
|
||||
}),
|
||||
},
|
||||
Studio: {
|
||||
anime: (studio) => knex("anime")
|
||||
.innerJoin("anime_studio", "anime_studio.anime_id", "anime.anime_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "anime_studio.studio_id": studio.studio_id })
|
||||
.select("anime.*"),
|
||||
resources: (studio) => knex("resources")
|
||||
.innerJoin("studio_resource", "studio_resource.resource_id", "resources.resource_id")
|
||||
.where("deleted_at", null)
|
||||
.where({ "studio_resource.studio_id": studio.studio_id })
|
||||
.select("resources.*")
|
||||
anime: apiResolver({
|
||||
endpoint: (studio) => `/studio/${studio.slug}`,
|
||||
field: "anime",
|
||||
extractor: (result) => result.studio.anime,
|
||||
type: "Studio",
|
||||
baseInclude: INCLUDES.Studio.anime
|
||||
}),
|
||||
resources: apiResolver({
|
||||
endpoint: (studio) => `/studio/${studio.slug}`,
|
||||
field: "resources",
|
||||
extractor: (result) => result.studio.resources,
|
||||
type: "Studio",
|
||||
baseInclude: INCLUDES.Studio.resources
|
||||
}),
|
||||
},
|
||||
BracketCharacter: {
|
||||
theme: (character) => knex("anime_themes")
|
||||
.where("deleted_at", null)
|
||||
.where("theme_id", character.theme)
|
||||
.first()
|
||||
theme: apiResolver({
|
||||
endpoint: (character) => `/animetheme/${character.theme}`,
|
||||
extractor: (result) => result.animetheme
|
||||
}),
|
||||
},
|
||||
AnimeListEntry: {
|
||||
anime: (animeListEntry, _, context) => knex("anime")
|
||||
.innerJoin("anime_resource", "anime_resource.anime_id", "anime.anime_id")
|
||||
.innerJoin("resources", "resources.resource_id", "anime_resource.resource_id")
|
||||
.where({
|
||||
"anime.deleted_at": null,
|
||||
"resources.deleted_at": null,
|
||||
"resources.site": context.externalSite,
|
||||
"resources.external_id": animeListEntry.externalId
|
||||
})
|
||||
.first("anime.*")
|
||||
anime: () => null,
|
||||
}
|
||||
};
|
||||
|
||||
function apiResolver(config) {
|
||||
const {
|
||||
endpoint,
|
||||
field,
|
||||
extractor = (a) => a,
|
||||
transformer = (a) => a,
|
||||
pagination = false,
|
||||
type: defaultType = null,
|
||||
baseInclude = null
|
||||
} = config;
|
||||
|
||||
return async (parent, args, context, info) => {
|
||||
if (field && parent[field] !== undefined) {
|
||||
return transformer(parent[field], parent, args);
|
||||
}
|
||||
|
||||
const type = defaultType || info.returnType.name || info.returnType.ofType.name;
|
||||
const path = pathToString(info.path);
|
||||
|
||||
if (info.path.prev) {
|
||||
devLog.warn(`Deep fetch at: ${path}`);
|
||||
}
|
||||
|
||||
let url = `${API_BASE_URL}${endpoint(parent, args)}`;
|
||||
|
||||
const includes = getIncludes(info, baseInclude);
|
||||
if (baseInclude) {
|
||||
includes.push(baseInclude);
|
||||
}
|
||||
const allowedIncludes = includes
|
||||
// Remove includes that are not allowed by the API
|
||||
.filter((include) => ALLOWED_INCLUDES[type]?.find((allowedInclude) => allowedInclude === include || allowedInclude.startsWith(include + ".")))
|
||||
// Remove includes which are already included with another include
|
||||
.filter((include, _, includes) => !includes.find((otherInclude) => otherInclude.startsWith(include + ".")))
|
||||
// Remove duplicates
|
||||
.filter((include, index, includes) => includes.lastIndexOf(include) === index);
|
||||
const disallowedIncludes = includes.filter((include) => !ALLOWED_INCLUDES[type]?.find((allowedInclude) => allowedInclude === include || allowedInclude.startsWith(include + ".")));
|
||||
|
||||
if (disallowedIncludes.length) {
|
||||
devLog.warn(`Disallowed includes for ${url}:`);
|
||||
devLog.warn(disallowedIncludes);
|
||||
devLog.warn(`Or at least:`);
|
||||
devLog.warn(disallowedIncludes.filter((include, _, includes) => !includes.find((otherInclude) => otherInclude.startsWith(include + "."))));
|
||||
}
|
||||
|
||||
if (allowedIncludes.length) {
|
||||
url += `${url.includes("?") ? "&" : "?"}include=${allowedIncludes.join()}`;
|
||||
}
|
||||
|
||||
devLog.info(path + ": " + url);
|
||||
|
||||
return await limit(() => (async () => {
|
||||
if (!pagination) {
|
||||
const json = await fetchJson(url);
|
||||
|
||||
return transformer(extractor(json, parent, args), parent, args);
|
||||
} else {
|
||||
devLog.info(`Collecting: ${url}`);
|
||||
const results = [];
|
||||
let nextUrl = `${url}${url.includes("?") ? "&" : "?"}page[size]=100`;
|
||||
while (nextUrl) {
|
||||
const json = await fetchJson(nextUrl);
|
||||
results.push(...transformer(extractor(json, parent, args), parent, args));
|
||||
devLog.info(`Collecting: ${url}, Got ${results.length}`);
|
||||
nextUrl = json.links.next;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
})());
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchJson(url) {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.ANIMETHEMES_API_KEY}`
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`API returned with non-ok status code: ${response.status} (${response.statusText})`);
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
function pathToString(path) {
|
||||
let parts = [];
|
||||
for (let part = path; part; part = part.prev) {
|
||||
parts.push(part.key);
|
||||
}
|
||||
return parts.reverse().join(".");
|
||||
}
|
||||
|
||||
function getIncludes(info, baseInclude = null) {
|
||||
const infoFragment = parseResolveInfo(info);
|
||||
return getIncludesRecursive(infoFragment, baseInclude ? (baseInclude + ".") : "");
|
||||
}
|
||||
|
||||
function getIncludesRecursive(infoFragment, parent = "") {
|
||||
const includes = [];
|
||||
|
||||
for (const [type, fields] of Object.entries(infoFragment.fieldsByTypeName)) {
|
||||
for (const [fieldName, field] of Object.entries(fields)) {
|
||||
if (INCLUDES[type]?.[fieldName]) {
|
||||
if (INCLUDES[type][fieldName] === "_") {
|
||||
includes.push(...getIncludesRecursive(field, parent));
|
||||
} else {
|
||||
const include = parent + INCLUDES[type][fieldName];
|
||||
includes.push(include);
|
||||
includes.push(...getIncludesRecursive(field, include + "."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return includes;
|
||||
}
|
||||
|
||||
+19
-11
@@ -1,21 +1,29 @@
|
||||
const { graphql, print } = require("graphql");
|
||||
const { makeExecutableSchema } = require("@graphql-tools/schema");
|
||||
const { mergeTypeDefs, mergeResolvers } = require("@graphql-tools/merge");
|
||||
import { graphql, print } from "graphql";
|
||||
import { makeExecutableSchema } from "@graphql-tools/schema";
|
||||
import { mergeResolvers, mergeTypeDefs } from "@graphql-tools/merge";
|
||||
|
||||
import typeDefsAnimeThemes from "lib/server/animethemes/type-defs";
|
||||
import typeDefsAnimeBracket from "lib/server/animebracket/type-defs";
|
||||
import typeDefsAnimeList from "lib/server/animelist/type-defs";
|
||||
|
||||
import resolversAnimeThemes from "lib/server/animethemes/resolvers";
|
||||
import resolversAnimeBracket from "lib/server/animebracket/resolvers";
|
||||
import resolversAnimeList from "lib/server/animelist/resolvers";
|
||||
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs: mergeTypeDefs([
|
||||
require("lib/server/animethemes/type-defs"),
|
||||
require("lib/server/animebracket/type-defs"),
|
||||
require("lib/server/animelist/type-defs")
|
||||
typeDefsAnimeThemes,
|
||||
typeDefsAnimeBracket,
|
||||
typeDefsAnimeList
|
||||
]),
|
||||
resolvers: mergeResolvers([
|
||||
require("lib/server/animethemes/resolvers"),
|
||||
require("lib/server/animebracket/resolvers"),
|
||||
require("lib/server/animelist/resolvers")
|
||||
resolversAnimeThemes,
|
||||
resolversAnimeBracket,
|
||||
resolversAnimeList
|
||||
])
|
||||
});
|
||||
|
||||
exports.fetchData = async (query, args = {}) => {
|
||||
export async function fetchData(query, args = {}) {
|
||||
const result = await graphql(schema, typeof query === "string" ? query : print(query), null, {}, args);
|
||||
|
||||
if (result.errors) {
|
||||
@@ -23,4 +31,4 @@ exports.fetchData = async (query, args = {}) => {
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import styled from "styled-components";
|
||||
import theme from "theme";
|
||||
import createVideoSlug from "utils/createVideoSlug";
|
||||
import gql from "graphql-tag";
|
||||
import fetchStaticPaths from "utils/fetchStaticPaths";
|
||||
|
||||
const StyledVideoInfo = styled.div`
|
||||
display: grid;
|
||||
@@ -106,7 +107,7 @@ export default function VideoPage({ anime, theme, entry, video }) {
|
||||
]
|
||||
});
|
||||
}
|
||||
}, [ anime, theme, smallCover ]);
|
||||
}, [ anime, theme, smallCover, songTitle ]);
|
||||
|
||||
const otherEntries = theme.entries.map(otherEntry => {
|
||||
const videos = otherEntry.videos.filter((otherVideo) => otherVideo.filename !== video.filename);
|
||||
@@ -388,44 +389,46 @@ export async function getStaticProps({ params: { animeSlug, videoSlug } }) {
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const { data } = await fetchData(`
|
||||
#graphql
|
||||
|
||||
query {
|
||||
animeAll {
|
||||
slug
|
||||
themes {
|
||||
return fetchStaticPaths(async (isStaging) => {
|
||||
const { data } = await fetchData(gql`
|
||||
query {
|
||||
animeAll {
|
||||
slug
|
||||
entries {
|
||||
id
|
||||
version
|
||||
videos {
|
||||
themes {
|
||||
slug
|
||||
entries {
|
||||
id
|
||||
tags
|
||||
version
|
||||
videos {
|
||||
id
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const paths = data.animeAll.flatMap(
|
||||
(anime) => anime.themes.flatMap(
|
||||
(theme) => theme.entries.flatMap(
|
||||
(entry) => entry.videos.flatMap(
|
||||
(video) => ({
|
||||
params: {
|
||||
animeSlug: anime.slug,
|
||||
videoSlug: createVideoSlug(theme, entry, video)
|
||||
}
|
||||
})
|
||||
let anime = data.animeAll;
|
||||
|
||||
if (isStaging) {
|
||||
// In staging, we only want to pre-render the video pages for the neweset 100 anime.
|
||||
anime = anime.sort((a, b) => b.id - a.id).slice(0, 100);
|
||||
}
|
||||
|
||||
return anime.flatMap(
|
||||
(anime) => anime.themes.flatMap(
|
||||
(theme) => theme.entries.flatMap(
|
||||
(entry) => entry.videos.flatMap(
|
||||
(video) => ({
|
||||
params: {
|
||||
animeSlug: anime.slug,
|
||||
videoSlug: createVideoSlug(theme, entry, video)
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import { fetchData } from "lib/server";
|
||||
import { SEO } from "components/seo";
|
||||
import useImage from "hooks/useImage";
|
||||
import { resourceSiteComparator, seriesNameComparator, studioNameComparator } from "utils/comparators";
|
||||
import fetchStaticPaths from "utils/fetchStaticPaths";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
const StyledList = styled.div`
|
||||
display: flex;
|
||||
@@ -212,24 +214,27 @@ export async function getStaticProps({ params: { animeSlug } }) {
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const { data } = await fetchData(`
|
||||
#graphql
|
||||
|
||||
query {
|
||||
animeAll {
|
||||
slug
|
||||
return fetchStaticPaths(async (isStaging) => {
|
||||
const { data } = await fetchData(gql`
|
||||
query {
|
||||
animeAll {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const paths = data.animeAll.map((anime) => ({
|
||||
params: {
|
||||
animeSlug: anime.slug
|
||||
}
|
||||
}));
|
||||
let anime = data.animeAll;
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
if (isStaging) {
|
||||
// In staging, we only want to pre-render the newest 100 anime pages.
|
||||
anime = anime.sort((a, b) => b.id - a.id).slice(0, 100);
|
||||
}
|
||||
|
||||
return anime.map((anime) => ({
|
||||
params: {
|
||||
animeSlug: anime.slug
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import { fetchData } from "lib/server";
|
||||
import { SEO } from "components/seo";
|
||||
import useImage from "hooks/useImage";
|
||||
import gql from "graphql-tag";
|
||||
import fetchStaticPaths from "utils/fetchStaticPaths";
|
||||
|
||||
const StyledList = styled.div`
|
||||
display: flex;
|
||||
@@ -248,24 +249,19 @@ export async function getStaticProps({ params: { artistSlug } }) {
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const { data } = await fetchData(`
|
||||
#graphql
|
||||
|
||||
query {
|
||||
artistAll {
|
||||
slug
|
||||
return fetchStaticPaths(async () => {
|
||||
const { data } = await fetchData(gql`
|
||||
query {
|
||||
artistAll {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const paths = data.artistAll.map((artist) => ({
|
||||
params: {
|
||||
artistSlug: artist.slug
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
return data.artistAll.map((artist) => ({
|
||||
params: {
|
||||
artistSlug: artist.slug
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -198,10 +198,10 @@ export async function getStaticProps() {
|
||||
${ThemeSummaryCard.fragments.theme}
|
||||
|
||||
query {
|
||||
featuredTheme: theme(id: 10968) {
|
||||
featuredTheme: theme(id: 11388) {
|
||||
...FeaturedTheme_theme
|
||||
}
|
||||
recentlyAdded: themeAll(orderBy: "theme_id", orderDesc: true, limit: 10) {
|
||||
recentlyAdded: themeAll(orderBy: "id", orderDesc: true, limit: 10) {
|
||||
...ThemeSummaryCard_theme
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import styled from "styled-components";
|
||||
import { motion } from "framer-motion";
|
||||
import theme from "theme";
|
||||
import { SEO } from "components/seo";
|
||||
import fetchStaticPaths from "utils/fetchStaticPaths";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
const StyledGrid = styled.div`
|
||||
display: flex;
|
||||
@@ -223,7 +225,7 @@ function TableOfContents({ headings }) {
|
||||
return (
|
||||
<StyledTableOfContents>
|
||||
{headings.map(({ text, slug, depth }) => (
|
||||
<StyledTableOfContentsHeading key={text} $depth={depth}>
|
||||
<StyledTableOfContentsHeading key={slug} $depth={depth}>
|
||||
{slug === currentSlug && (
|
||||
<StyledDot layoutId="dot"/>
|
||||
)}
|
||||
@@ -266,24 +268,19 @@ export async function getStaticProps({ params: { pageSlug } }) {
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const { data } = await fetchData(`
|
||||
#graphql
|
||||
|
||||
query {
|
||||
pageAll {
|
||||
slug
|
||||
return fetchStaticPaths(async () => {
|
||||
const { data } = await fetchData(gql`
|
||||
query {
|
||||
pageAll {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const paths = data.pageAll.map((page) => ({
|
||||
params: {
|
||||
pageSlug: page.slug.split("/")
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
return data.pageAll.map((page) => ({
|
||||
params: {
|
||||
pageSlug: page.slug.split("/")
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { SEO } from "components/seo";
|
||||
import theme from "theme";
|
||||
import { MultiCoverImage } from "components/image";
|
||||
import gql from "graphql-tag";
|
||||
import fetchStaticPaths from "utils/fetchStaticPaths";
|
||||
|
||||
const StyledDesktopOnly = styled.div`
|
||||
gap: 24px;
|
||||
@@ -97,10 +98,6 @@ export async function getStaticProps({ params: { seriesSlug } }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
resources {
|
||||
site
|
||||
link
|
||||
}
|
||||
images {
|
||||
facet
|
||||
link
|
||||
@@ -127,24 +124,19 @@ export async function getStaticProps({ params: { seriesSlug } }) {
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const { data } = await fetchData(`
|
||||
#graphql
|
||||
|
||||
query {
|
||||
seriesAll {
|
||||
slug
|
||||
return fetchStaticPaths(async () => {
|
||||
const { data } = await fetchData(gql`
|
||||
query {
|
||||
seriesAll {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const paths = data.seriesAll.map((series) => ({
|
||||
params: {
|
||||
seriesSlug: series.slug
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
return data.seriesAll.map((series) => ({
|
||||
params: {
|
||||
seriesSlug: series.slug
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SEO } from "components/seo";
|
||||
import theme from "theme";
|
||||
import { MultiCoverImage } from "components/image";
|
||||
import gql from "graphql-tag";
|
||||
import fetchStaticPaths from "utils/fetchStaticPaths";
|
||||
|
||||
const StyledDesktopOnly = styled.div`
|
||||
@media (max-width: ${theme.breakpoints.tabletMax}) {
|
||||
@@ -127,10 +128,6 @@ export async function getStaticProps({ params: { studioSlug } }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
resources {
|
||||
site
|
||||
link
|
||||
}
|
||||
images {
|
||||
facet
|
||||
link
|
||||
@@ -161,24 +158,19 @@ export async function getStaticProps({ params: { studioSlug } }) {
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const { data } = await fetchData(`
|
||||
#graphql
|
||||
|
||||
query {
|
||||
studioAll {
|
||||
slug
|
||||
return fetchStaticPaths(async () => {
|
||||
const { data } = await fetchData(gql`
|
||||
query {
|
||||
studioAll {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const paths = data.studioAll.map((studio) => ({
|
||||
params: {
|
||||
studioSlug: studio.slug
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
return data.studioAll.map((studio) => ({
|
||||
params: {
|
||||
studioSlug: studio.slug
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Column } from "components/box";
|
||||
import { fetchData } from "lib/server";
|
||||
import { SEO } from "components/seo";
|
||||
import gql from "graphql-tag";
|
||||
import fetchStaticPaths from "utils/fetchStaticPaths";
|
||||
|
||||
const seasonOrder = [ "Winter", "Spring", "Summer", "Fall" ];
|
||||
|
||||
@@ -82,30 +83,25 @@ export async function getStaticProps({ params: { year, season } }) {
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const { data } = await fetchData(`
|
||||
#graphql
|
||||
|
||||
query {
|
||||
yearAll {
|
||||
value
|
||||
seasons {
|
||||
return fetchStaticPaths(async () => {
|
||||
const { data } = await fetchData(gql`
|
||||
query {
|
||||
yearAll {
|
||||
value
|
||||
seasons {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const paths = data.yearAll.flatMap(
|
||||
(year) => year.seasons.map((season) => ({
|
||||
params: {
|
||||
year: String(year.value),
|
||||
season: season.value
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
return data.yearAll.flatMap(
|
||||
(year) => year.seasons.map((season) => ({
|
||||
params: {
|
||||
year: String(year.value),
|
||||
season: season.value
|
||||
}
|
||||
}))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { fetchData } from "lib/server";
|
||||
import { SEO } from "components/seo";
|
||||
import gql from "graphql-tag";
|
||||
import { ANIME_A_Z, getComparator } from "utils/comparators";
|
||||
import fetchStaticPaths from "utils/fetchStaticPaths";
|
||||
|
||||
const seasonOrder = [ "Winter", "Spring", "Summer", "Fall" ];
|
||||
|
||||
@@ -101,24 +102,19 @@ export async function getStaticProps({ params: { year } }) {
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const { data } = await fetchData(`
|
||||
#graphql
|
||||
|
||||
query {
|
||||
yearAll {
|
||||
value
|
||||
return fetchStaticPaths(async () => {
|
||||
const { data } = await fetchData(gql`
|
||||
query {
|
||||
yearAll {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const paths = data.yearAll.map((year) => ({
|
||||
params: {
|
||||
year: String(year.value)
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
return data.yearAll.map((year) => ({
|
||||
params: {
|
||||
year: String(year.value)
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
const log = require("next/dist/build/output/log");
|
||||
|
||||
const devLog = {};
|
||||
|
||||
for (const [name, fn] of Object.entries(log)) {
|
||||
devLog[name] = (...args) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
fn(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = devLog;
|
||||
@@ -0,0 +1,18 @@
|
||||
export default async function fetchStaticPaths(fetchPaths) {
|
||||
// In development all pages should be fetched on-demand. This speeds up page generation a lot.
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return {
|
||||
paths: [],
|
||||
fallback: "blocking"
|
||||
};
|
||||
}
|
||||
|
||||
const isStaging = !!process.env.NEXT_PUBLIC_STAGING;
|
||||
|
||||
const paths = await fetchPaths(isStaging);
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking"
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user