215 lines
7.4 KiB
TypeScript
215 lines
7.4 KiB
TypeScript
import { addDays, subDays } from "date-fns";
|
|
import { Book, Chapter, Editor, Announcement, Glossary } from "./types";
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL as string;
|
|
const API_TOKEN = process.env.STRAPI_API_TOKEN as string;
|
|
|
|
export async function createFromAPI<T>(
|
|
endpoint: string,
|
|
payload: string,
|
|
options: RequestInit = {},
|
|
): Promise<T> {
|
|
|
|
const headers: HeadersInit = {
|
|
Authorization: `Bearer ${API_TOKEN}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
|
|
const config: RequestInit = {
|
|
method: "POST",
|
|
headers,
|
|
body: payload,
|
|
...options,
|
|
}
|
|
|
|
|
|
const response = await fetch(`${API_URL}${endpoint}`, config)
|
|
if (!response.ok) {
|
|
throw new Error(`API request failed with status ${response.status}`)
|
|
}
|
|
return response.json()
|
|
}
|
|
|
|
export async function fetchFromAPI<T>(
|
|
endpoint: string,
|
|
options: RequestInit = {}
|
|
): Promise<T[]> {
|
|
const headers: HeadersInit = {
|
|
Authorization: `Bearer ${API_TOKEN}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
|
|
const config: RequestInit = {
|
|
method: "GET",
|
|
headers,
|
|
...options,
|
|
};
|
|
|
|
|
|
|
|
let results: T[] = [];
|
|
let currentPage = 1;
|
|
let totalPages = 1;
|
|
try {
|
|
do {
|
|
const url = `${API_URL}${endpoint}&pagination[page]=${currentPage}&pagination[pageSize]=25`;
|
|
const response = await fetch(url, { ...config, next: { revalidate: 30 } });
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
console.error(`Error fetching ${url}:`, errorData);
|
|
throw new Error(errorData.message || `API fetch error (status: ${response.status})`);
|
|
}
|
|
const responseJson = await response.json();
|
|
results = results.concat(responseJson.data);
|
|
totalPages = responseJson.meta?.pagination?.pageCount;
|
|
currentPage += 1;
|
|
} while (currentPage <= totalPages)
|
|
|
|
} catch (error) {
|
|
console.error("Fetch error:", error);
|
|
throw error;
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Fetches all books from the API.
|
|
* Populates optional fields like Chapters or Editors based on requirements.
|
|
*/
|
|
export async function fetchBooks(): Promise<Book[]> {
|
|
const data = await fetchFromAPI<Book>(`/api/books?populate=cover&sort[title]=asc&filters[release_datetime][$lte]=${new Date().toISOString()}`);
|
|
return data;
|
|
}
|
|
|
|
export async function fetchAnnouncements(): Promise<Announcement[]> {
|
|
const data = await fetchFromAPI<Announcement>(`/api/announcements?filters[datetime][$lte]=${new Date().toISOString()}&sort[datetime]=desc`);
|
|
return data;
|
|
}
|
|
|
|
export async function fetchChaptersRSS(): Promise<Chapter[]> {
|
|
const currentDateTime = new Date()
|
|
const yesterday = subDays(currentDateTime, 1);
|
|
const data = await fetchFromAPI<Chapter>
|
|
(`/api/chapters?populate=book&filters[release_datetime][$lte]=${currentDateTime.toISOString()}&filters[release_datetime][$gte]=${yesterday.toISOString()}`);
|
|
return data;
|
|
}
|
|
|
|
export async function fetchBookChapterLinks(bookId: string): Promise<Book> {
|
|
const currentDateTime = new Date().toISOString();
|
|
const data = await fetchFromAPI<Book>(`/api/books/${bookId}?populate[chapters][filters][release_datetime][$lte]=${currentDateTime}`);
|
|
return data[0]
|
|
}
|
|
|
|
/**
|
|
* Fetches a specific book by ID with its chapters.
|
|
* Filters chapters by release date to include only valid ones.
|
|
*/
|
|
export async function fetchBookById(bookId: string): Promise<Book> {
|
|
const currentDateTime = new Date().toISOString();
|
|
const nextday = addDays(new Date(), 1).toISOString();
|
|
//[chapters][filters][release_datetime][$lte]=${currentDateTime}
|
|
const data = await fetchFromAPI<Book>(
|
|
`/api/books/${bookId}?populate[chapters][filters][release_datetime][$lte]=${nextday}&populate=cover`
|
|
);
|
|
const stripped_data = []
|
|
for (const chapter of data[0].chapters) {
|
|
const stripped_chapter = chapter;
|
|
if (new Date(chapter.release_datetime).toISOString() > currentDateTime) {
|
|
stripped_chapter.content = "";
|
|
stripped_chapter.documentId = "";
|
|
}
|
|
stripped_data.push(stripped_chapter)
|
|
}
|
|
data[0].chapters = stripped_data;
|
|
//I do not know why the hell it refuse to populate glossary only 1 field is allow to be populated after ????????
|
|
const glossary = await fetchGlossaryByBookId(bookId);
|
|
data[0].glossary = glossary;
|
|
data[0].chapters = data[0].chapters.sort((a, b) => a.number - b.number);
|
|
return data[0];
|
|
}
|
|
|
|
/**
|
|
* Fetches a specific chapter by ID.
|
|
*/
|
|
export async function fetchChapterByBookId(bookId: string, chapterId: string): Promise<Chapter[]> {
|
|
const currentChapter = await fetchFromAPI<Chapter>(`/api/chapters/${chapterId}?populate[book][fields][0]=title&filters[book][documentId]=${bookId}`);
|
|
const bookWithAllChapters = await fetchFromAPI<Book>(`/api/books/${bookId}?populate[chapters][filters][number][$gte]=${currentChapter[0].number - 1
|
|
}&populate[chapters][filters][number][$lte]=${currentChapter[0].number + 1
|
|
}`);
|
|
//const nextChapter = await fetchFromAPI<Chapter>(`/api/chapters?populate[book]&filters[book][id]=${bookId}&sort[number]=asc`);
|
|
return bookWithAllChapters[0].chapters;
|
|
}
|
|
|
|
/**
|
|
* Fetches all editors.
|
|
*/
|
|
export async function fetchEditors(): Promise<Editor[]> {
|
|
const data = await fetchFromAPI<Editor>("/api/editors");
|
|
return data;
|
|
}
|
|
|
|
export async function fetchEarlyRelease(): Promise<Chapter[]> {
|
|
const current_datetime = new Date()
|
|
const data = await fetchFromAPI<Chapter>(`/api/chapters/?populate[book][fields][0]=title&fields[0]=number&fields[1]=title&fields[2]=release_datetime&filters[release_datetime][$gte]=${current_datetime.toISOString()}&filters[release_datetime][$lte]=${addDays(current_datetime, 1).toISOString()}`);
|
|
return data;
|
|
}
|
|
|
|
export type ChapterRelease = { current_chapters: Chapter[], future_chapters: Chapter[] }
|
|
export async function fetchReleases(): Promise<{ current_chapters: Chapter[], future_chapters: Chapter[] }> {
|
|
const current_datetime = new Date()
|
|
const previous_week = subDays(current_datetime, 1);
|
|
const next_week = addDays(current_datetime, 1);
|
|
|
|
const data = await fetchFromAPI<Chapter>(`/api/chapters/?populate[book][fields][0]=title&fields[0]=number&fields[1]=title&fields[2]=release_datetime&filters[release_datetime][$gte]=${previous_week.toISOString()}&filters[release_datetime][$lte]=${next_week.toISOString()}`);
|
|
const chapters: Chapter[] = data;
|
|
const future_chapters = chapters.filter(chapter => new Date(chapter.release_datetime) > new Date());
|
|
const current_chapters = chapters.filter(chapter => new Date(chapter.release_datetime) <= new Date());
|
|
return { current_chapters, future_chapters }
|
|
}
|
|
|
|
export async function fetchAnnouncementById(announcementId: string): Promise<Announcement> {
|
|
const data = await fetchFromAPI<Announcement>(`/api/announcements/${announcementId}?`);
|
|
return data[0];
|
|
}
|
|
|
|
export async function fetchGlossaryByBookId(bookId: string): Promise<Glossary> {
|
|
const data = await fetchFromAPI<Glossary>(`/api/glossaries?filters[book][documentId]=${bookId}`);
|
|
return data[0];
|
|
}
|
|
|
|
export async function createReport(
|
|
error_type: string,
|
|
details: string,
|
|
book_id: string,
|
|
chapter_id: string,
|
|
email: string
|
|
) {
|
|
const payload = {
|
|
data: {
|
|
error_type: error_type,
|
|
details: details,
|
|
book: book_id,
|
|
chapter: chapter_id,
|
|
report_email: email
|
|
}
|
|
}
|
|
|
|
const response = await fetch(
|
|
'/api/reports',
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(payload)
|
|
}
|
|
)
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API request failed with status ${response.status}`)
|
|
}
|
|
return response
|
|
} |