feat(scrape-range): add chapter range scraping for admin users
Adds FromChapter/ToChapter fields to orchestrator.Config and skips out-of-range chapters in processBook. Exposes POST /scrape/book/range Go endpoint and a matching UI proxy at /api/scrape/range. The book detail page now shows admins a range input (from/to chapter) and a per-chapter 'scrape from here up' button.
This commit is contained in:
62
ui/src/routes/api/scrape/range/+server.ts
Normal file
62
ui/src/routes/api/scrape/range/+server.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* POST /api/scrape/range
|
||||
*
|
||||
* Proxies range-scrape requests to the Go scraper backend at POST /scrape/book/range.
|
||||
* Admin-only.
|
||||
*
|
||||
* Request body (JSON):
|
||||
* { "url": "https://novelfire.net/book/...", "from": 50, "to": 100 }
|
||||
* "to" is optional — omit to scrape from "from" to the end.
|
||||
*
|
||||
* Responses mirror the Go scraper:
|
||||
* 202 Accepted — job enqueued
|
||||
* 409 Conflict — a scrape job is already running
|
||||
* 400 Bad Request
|
||||
* 403 Forbidden — not an admin
|
||||
*/
|
||||
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { log } from '$lib/server/logger';
|
||||
|
||||
const SCRAPER_URL = env.SCRAPER_API_URL ?? 'http://localhost:8080';
|
||||
|
||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
// Admin guard
|
||||
if (!locals.user || locals.user.role !== 'admin') {
|
||||
throw error(403, 'Forbidden');
|
||||
}
|
||||
|
||||
let body: { url?: string; from?: number; to?: number } = {};
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
throw error(400, 'Invalid JSON body');
|
||||
}
|
||||
|
||||
if (!body.url || typeof body.from !== 'number') {
|
||||
throw error(400, 'url and from are required');
|
||||
}
|
||||
|
||||
const upstream = `${SCRAPER_URL}/scrape/book/range`;
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(upstream, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: body.url, from: body.from, to: body.to })
|
||||
});
|
||||
} catch (e) {
|
||||
log.error('scrape/range', 'scraper proxy network error', { err: String(e) });
|
||||
throw error(502, 'Could not reach scraper');
|
||||
}
|
||||
|
||||
if (!res.ok && res.status >= 500) {
|
||||
const text = await res.text().catch(() => '');
|
||||
log.error('scrape/range', 'scraper returned error', { status: res.status, body: text });
|
||||
}
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
return json(data, { status: res.status });
|
||||
};
|
||||
Reference in New Issue
Block a user