Files
libnovel/ui-v2/src/routes/api/book/[slug]/+server.ts
Admin 5825b859b7
All checks were successful
Release / Scraper / Test (push) Successful in 10s
Release / UI / Build (push) Successful in 26s
Release / v2 / Build ui-v2 (push) Successful in 17s
Release / Scraper / Docker (push) Successful in 47s
Release / UI / Docker (push) Successful in 56s
CI / Scraper / Lint (pull_request) Successful in 7s
CI / Scraper / Test (pull_request) Successful in 8s
CI / UI / Build (pull_request) Successful in 16s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
Release / v2 / Docker / ui-v2 (push) Successful in 56s
Release / v2 / Test backend (push) Successful in 4m35s
iOS CI / Build (pull_request) Successful in 4m28s
Release / v2 / Docker / backend (push) Successful in 1m29s
Release / v2 / Docker / runner (push) Successful in 1m39s
iOS CI / Test (pull_request) Successful in 9m51s
feat: add v2 stack (backend, runner, ui-v2) with release workflow
- backend/: Go API server and runner binaries with PocketBase + MinIO storage
- ui-v2/: SvelteKit frontend rewrite
- docker-compose-new.yml: compose file for the v2 stack
- .gitea/workflows/release-v2.yaml: CI/CD for backend, runner, and ui-v2 Docker Hub images
- scripts/pb-init.sh: migrate from wget to curl, add superuser bootstrap for fresh installs
- .env.example: document DOCKER_BUILDKIT=1 for Colima users
2026-03-15 19:32:40 +05:00

112 lines
3.1 KiB
TypeScript

import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getBook, listChapterIdx, getProgress, isBookSaved } from '$lib/server/pocketbase';
import { log } from '$lib/server/logger';
import { env } from '$env/dynamic/private';
const SCRAPER_URL = env.SCRAPER_API_URL ?? 'http://localhost:8080';
/**
* GET /api/book/[slug]
* Returns book metadata, chapter list, progress, and library status.
*
* If the book is not yet in PocketBase, asks the backend to enqueue a scrape
* task and returns 202 with { scraping: true, task_id }.
* The client should poll and retry once the task completes.
*/
export const GET: RequestHandler = async ({ params, locals }) => {
const { slug } = params;
// Try PocketBase first
let book = await getBook(slug).catch((e) => {
log.error('api/book', 'getBook failed', { slug, err: String(e) });
return null;
});
if (book) {
let chapters, progress, saved;
try {
[chapters, progress, saved] = await Promise.all([
listChapterIdx(slug),
getProgress(locals.sessionId, slug, locals.user?.id),
isBookSaved(locals.sessionId, slug, locals.user?.id)
]);
} catch (e) {
log.error('api/book', 'failed to load book detail data', { slug, err: String(e) });
error(500, 'Failed to load book');
}
return json({
book,
chapters,
in_lib: true,
saved,
last_chapter: progress?.chapter ?? null,
scraping: false,
task_id: null
});
}
// Fall back to backend: enqueue scrape task if not in library.
try {
const res = await fetch(`${SCRAPER_URL}/api/book-preview/${encodeURIComponent(slug)}`);
if (res.status === 202) {
const body: { task_id: string; message: string } = await res.json();
log.info('api/book', 'scrape task enqueued', { slug, task_id: body.task_id });
return json({ scraping: true, task_id: body.task_id, in_lib: false }, { status: 202 });
}
if (!res.ok) {
log.warn('api/book', 'book-preview returned error', { slug, status: res.status });
error(404, `Book "${slug}" not found`);
}
// 200 — book was already in library
const preview: {
in_lib: boolean;
meta: {
slug: string;
title: string;
author: string;
cover: string;
status: string;
genres: string[];
summary: string;
total_chapters: number;
source_url: string;
};
chapters: { number: number; title: string; date?: string }[];
} = await res.json();
const previewBook = {
id: '',
slug: preview.meta.slug || slug,
title: preview.meta.title,
author: preview.meta.author,
cover: preview.meta.cover,
status: preview.meta.status,
genres: preview.meta.genres ?? [],
summary: preview.meta.summary,
total_chapters: preview.meta.total_chapters,
source_url: preview.meta.source_url,
ranking: 0,
meta_updated: ''
};
return json({
book: previewBook,
chapters: preview.chapters,
in_lib: true,
saved: false,
last_chapter: null,
scraping: false,
task_id: null
});
} catch (e) {
if (e instanceof Error && 'status' in e) throw e;
log.error('api/book', 'book-preview fetch failed', { slug, err: String(e) });
error(404, `Book "${slug}" not found`);
}
};