- New Go backend binary (backend + runner) replacing old scraper/ - Rename SCRAPER_API_URL → BACKEND_API_URL in UI env and docker-compose - Rename scraperFetch → backendFetch across all 19 UI server files - Remove SCRAPER_PROXY env var and proxy transport from browser.Config - Add Meilisearch, Valkey, Caddy to docker-compose - Add docs/: api-endpoints.md, request-flow.mermaid.md, data-flow.mermaid.md
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import { json } from '@sveltejs/kit';
|
|
import type { RequestHandler } from './$types';
|
|
import { listBooks, allProgress, getSavedSlugs } from '$lib/server/pocketbase';
|
|
import { log } from '$lib/server/logger';
|
|
|
|
/**
|
|
* GET /api/library
|
|
* Returns the user's library: books they have started reading or explicitly saved.
|
|
* Each item includes the book record, the last chapter read, and saved_at timestamp.
|
|
*
|
|
* Response shape mirrors LibraryItem in the iOS APIClient.
|
|
*/
|
|
export const GET: RequestHandler = async ({ locals }) => {
|
|
let allBooks: Awaited<ReturnType<typeof listBooks>>;
|
|
let progressList: Awaited<ReturnType<typeof allProgress>>;
|
|
let savedSlugs: Set<string>;
|
|
|
|
try {
|
|
[allBooks, progressList, savedSlugs] = await Promise.all([
|
|
listBooks(),
|
|
allProgress(locals.sessionId, locals.user?.id),
|
|
getSavedSlugs(locals.sessionId, locals.user?.id)
|
|
]);
|
|
} catch (e) {
|
|
log.error('api/library', 'failed to load library data', { err: String(e) });
|
|
allBooks = [];
|
|
progressList = [];
|
|
savedSlugs = new Set();
|
|
}
|
|
|
|
const progressMap: Record<string, number> = {};
|
|
const progressUpdatedMap: Record<string, string> = {};
|
|
for (const p of progressList) {
|
|
progressMap[p.slug] = p.chapter;
|
|
progressUpdatedMap[p.slug] = p.updated;
|
|
}
|
|
|
|
const progressSlugs = new Set(progressList.map((p) => p.slug));
|
|
const books = allBooks.filter((b) => progressSlugs.has(b.slug) || savedSlugs.has(b.slug));
|
|
|
|
const withProgress = books.filter((b) => progressSlugs.has(b.slug));
|
|
const savedOnly = books
|
|
.filter((b) => !progressSlugs.has(b.slug))
|
|
.sort((a, b) => (a.title ?? '').localeCompare(b.title ?? ''));
|
|
|
|
withProgress.sort((a, b) => {
|
|
const ta = progressUpdatedMap[a.slug] ?? '';
|
|
const tb = progressUpdatedMap[b.slug] ?? '';
|
|
return tb.localeCompare(ta);
|
|
});
|
|
|
|
const ordered = [...withProgress, ...savedOnly];
|
|
|
|
const items = ordered.map((book) => ({
|
|
book,
|
|
last_chapter: progressMap[book.slug] ?? null,
|
|
saved_at: progressUpdatedMap[book.slug] ?? new Date().toISOString()
|
|
}));
|
|
|
|
return json(items);
|
|
};
|