Files
animethemes-web/src/lib/client/search.js
T
Manuel S 31aee782b0 feat: Migrated to Next.js (#87)
* Rewrote data layer to work on-demand.
* Added several favicon formats.
* Added image for SEO page previews.
* Disabled GitHub pages as it no longer works.
* Several other adjustments.
2022-01-16 04:09:29 +01:00

258 lines
6.2 KiB
JavaScript

import { baseUrl } from "lib/client/api";
const entityConfigs = {
anime: {
includes: [
"animethemes.animethemeentries.videos",
"images"
],
fields: {
anime: [
"name",
"slug",
"year",
"season"
],
animetheme: [
"type",
"sequence",
"slug"
],
animethemeentry: [
"version"
],
video: [
"tags"
],
image: [
"facet",
"link"
]
}
},
theme: {
singular: "animetheme",
plural: "animethemes",
includes: [
"animethemeentries.videos",
"anime.images",
"song.artists"
],
fields: {
anime: [
"name",
"slug",
"year",
"season"
],
animetheme: [
"type",
"sequence",
"slug"
],
animethemeentry: [
"version"
],
video: [
"tags"
],
image: [
"facet",
"link"
],
song: [
"title"
],
artist: [
"name",
"slug",
"as"
]
}
},
artist: {
plural: "artists",
includes: [
"images",
"songs"
],
fields: {
artist: [
"name",
"slug"
],
image: [
"facet",
"link"
],
// TODO: Wait till we can specify deep field property lists
// song: []
}
},
series: {
fields: {
series: [
"name",
"slug"
]
}
},
studio: {
plural: "studios",
fields: {
studio: [
"name",
"slug"
]
}
}
};
export async function fetchGlobalSearchResults(query, limit, entities) {
const parameters = generateGlobalSearchParameters(entities);
const res = await fetch(`${baseUrl}/api/search?${parameters.join("&")}&limit=${limit}&q=${encodeURIComponent(query)}`);
const json = await res.json();
if (!res.ok) {
throw json;
}
if (json.search.anime) {
applyAnimeSchema(json.search.anime);
}
if (json.search.animethemes) {
applyThemeSchema(json.search.animethemes);
}
if (json.search.artists) {
applyArtistSchema(json.search.artists);
}
return Object.fromEntries(entities.map((entity) => [ entity, json.search[entityConfigs[entity].plural || entity] ]));
}
export async function fetchEntitySearchResults({
query,
entity,
page = 1,
limit = 15,
filters = {},
sortBy = null
}) {
const parameters = [
...generateEntitySearchParameters(entity),
...generateFilterAndSortParameters(filters, sortBy)
];
let url = `${baseUrl}/api/${entityConfigs[entity].singular || entity}?${parameters.join("&")}&page[size]=${limit}&page[number]=${page}`;
if (query) {
url += `&q=${encodeURIComponent(query)}`;
}
const res = await fetch(url);
const json = await res.json();
if (!res.ok) {
throw json;
}
const results = json[entityConfigs[entity].plural || entity];
const hasNext = !!json.links.next;
if (entity === "anime") {
applyAnimeSchema(results);
} else if (entity === "theme") {
applyThemeSchema(results);
} else if (entity === "artist") {
applyArtistSchema(results);
}
return {
data: results,
hasNextPage: hasNext ? page + 1 : undefined
};
}
function generateGlobalSearchParameters(entities) {
const parameters = [];
parameters.push(`fields[search]=${entities.map((entity) => entityConfigs[entity].plural || entity)}`);
parameters.push(
...entities
.flatMap((entity) => generateEntitySearchParameters(entity, true))
.filter((parameter, index, parameters) => parameters.indexOf(parameter) === index)
);
return parameters;
}
function generateEntitySearchParameters(entity, includeScoped = false) {
const parameters = [];
const config = entityConfigs[entity];
if (config.includes) {
const includesJoined = config.includes.join(",");
if (includeScoped) {
parameters.push(`include[${config.singular || entity}]=${includesJoined}`);
} else {
parameters.push(`include=${includesJoined}`);
}
}
if (config.fields) {
parameters.push(...Object.entries(config.fields).map(([key, fields]) => `fields[${key}]=${fields.join(",")}`));
}
return parameters;
}
function generateFilterAndSortParameters(filters, sortBy) {
const parameters = [];
for (const [ key, value ] of Object.entries(filters)) {
if (value) {
parameters.push(`filter[${key}]=${encodeURIComponent(value)}`);
}
}
if (sortBy) {
parameters.push(`sort=${sortBy}`);
}
return parameters;
}
function applyAnimeSchema(allAnime) {
for (const anime of allAnime) {
anime.themes = anime.animethemes;
delete anime.animethemes;
for (const theme of anime.themes) {
theme.entries = theme.animethemeentries;
delete theme.animethemeentries;
}
}
}
// Map artists and songs to performances to comply with the application schema
function applyThemeSchema(themes) {
for (const theme of themes) {
theme.song.performances = theme.song.artists.map((artist) => ({
artist,
as: artist.as
}));
theme.entries = theme.animethemeentries;
delete theme.animethemeentries;
}
}
function applyArtistSchema(artists) {
for (const artist of artists) {
artist.performances = artist.songs.map((song) => ({
song,
as: song.as
}));
}
}