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:
Admin
2026-03-03 14:03:27 +05:00
parent f265d9d020
commit 9fa0776258
3 changed files with 338 additions and 0 deletions

View 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 });
};