feat(ui): add /browse page and /api/scrape proxy route
- /browse calls GET /api/browse on the scraper and renders a novel grid mirroring the novelfire layout: cover, rank/rating badges, chapter count, genre/sort/status filters, and pagation controls - Scrape buttons are shown only to admin users; clicking enqueues the book via /api/scrape - /api/scrape is an admin-only SvelteKit server route that proxies POST requests to the Go scraper's /scrape/book or /scrape endpoints; returns 403 for non-admins
This commit is contained in:
52
ui/src/routes/api/scrape/+server.ts
Normal file
52
ui/src/routes/api/scrape/+server.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* POST /api/scrape
|
||||
*
|
||||
* Proxies scrape requests to the Go scraper backend.
|
||||
* Admin-only — returns 403 if the authenticated user is not an admin.
|
||||
*
|
||||
* Request body (JSON):
|
||||
* { "url": "https://novelfire.net/book/..." } — scrape a single book
|
||||
* {} — scrape the full catalogue
|
||||
*
|
||||
* 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';
|
||||
|
||||
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 } = {};
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
// empty body is fine — means "scrape all"
|
||||
}
|
||||
|
||||
// Decide which scraper endpoint to call
|
||||
const isBookScrape = typeof body.url === 'string' && body.url.length > 0;
|
||||
const endpoint = isBookScrape ? '/scrape/book' : '/scrape';
|
||||
|
||||
const upstream = `${SCRAPER_URL}${endpoint}`;
|
||||
const res = await fetch(upstream, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: isBookScrape ? JSON.stringify({ url: body.url }) : undefined
|
||||
});
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
// Pass through the status code from the Go scraper (202, 409, 400, …)
|
||||
return json(data, { status: res.status });
|
||||
};
|
||||
Reference in New Issue
Block a user