Archived
- Backend: Replace PR's Book-only models with main's full models (EBook, BookChapter, DownloadRecord, ReadingProgress, ReadingSettings) - Backend: Add reader app (ReadingSettings model, serializer, view, URL) - Frontend: Port reader feature files from web/ to frontend/ (api, hooks, components/reader/, pages/ReadingPage, types/reader, reader.css) - Frontend: Add /read/:id route to App.tsx for chapter-based reading view - All imports adjusted for frontend/src directory structure
112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
/**
|
|
* API client for the reader module — reading settings, chapters, and progress.
|
|
*/
|
|
|
|
import type {
|
|
ChapterDetail,
|
|
ChapterSummary,
|
|
ReadingProgress,
|
|
ReadingSettings,
|
|
} from "../types/reader";
|
|
|
|
const API_BASE = "/api";
|
|
|
|
/**
|
|
* Fetch the current user's reading settings.
|
|
* Auto-creates defaults on the server if none exist.
|
|
*/
|
|
export async function getReadingSettings(): Promise<ReadingSettings> {
|
|
const response = await fetch(`${API_BASE}/reader/settings/`);
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to fetch reading settings: ${response.status} ${response.statusText}`
|
|
);
|
|
}
|
|
return response.json() as Promise<ReadingSettings>;
|
|
}
|
|
|
|
/**
|
|
* Update (full or partial) the user's reading settings.
|
|
*/
|
|
export async function updateReadingSettings(
|
|
settings: Partial<ReadingSettings>
|
|
): Promise<ReadingSettings> {
|
|
const method = settings.theme !== undefined ? "PUT" : "PATCH";
|
|
const response = await fetch(`${API_BASE}/reader/settings/`, {
|
|
method,
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(settings),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to update reading settings: ${response.status} ${response.statusText}`
|
|
);
|
|
}
|
|
return response.json() as Promise<ReadingSettings>;
|
|
}
|
|
|
|
/**
|
|
* Fetch the table of contents (chapter list) for a book.
|
|
*/
|
|
export async function getChapters(bookId: number): Promise<ChapterSummary[]> {
|
|
const response = await fetch(`${API_BASE}/books/${bookId}/chapters/`);
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to fetch chapters: ${response.status} ${response.statusText}`
|
|
);
|
|
}
|
|
return response.json() as Promise<ChapterSummary[]>;
|
|
}
|
|
|
|
/**
|
|
* Fetch a specific chapter with full content for reading.
|
|
*/
|
|
export async function getChapterContent(
|
|
bookId: number,
|
|
chapterNumber: number
|
|
): Promise<ChapterDetail> {
|
|
const response = await fetch(
|
|
`${API_BASE}/books/${bookId}/chapters/${chapterNumber}/`
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to fetch chapter ${chapterNumber}: ${response.status} ${response.statusText}`
|
|
);
|
|
}
|
|
return response.json() as Promise<ChapterDetail>;
|
|
}
|
|
|
|
/**
|
|
* Fetch reading progress for a book.
|
|
*/
|
|
export async function getReadingProgress(
|
|
bookId: number
|
|
): Promise<ReadingProgress> {
|
|
const response = await fetch(`${API_BASE}/books/${bookId}/progress/`);
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to fetch reading progress: ${response.status} ${response.statusText}`
|
|
);
|
|
}
|
|
return response.json() as Promise<ReadingProgress>;
|
|
}
|
|
|
|
/**
|
|
* Update reading progress for a book.
|
|
*/
|
|
export async function updateReadingProgress(
|
|
bookId: number,
|
|
progress: Partial<ReadingProgress>
|
|
): Promise<ReadingProgress> {
|
|
const response = await fetch(`${API_BASE}/books/${bookId}/progress/`, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(progress),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Failed to update reading progress: ${response.status} ${response.statusText}`
|
|
);
|
|
}
|
|
return response.json() as Promise<ReadingProgress>;
|
|
} |