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,47 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { env } from '$env/dynamic/private';
const SCRAPER_URL = env.SCRAPER_API_URL ?? 'http://localhost:8080';
export interface NovelListing {
slug: string;
title: string;
cover: string;
rank: string;
rating: string;
chapters: string;
url: string;
}
export const load: PageServerLoad = async ({ url, locals }) => {
const page = url.searchParams.get('page') ?? '1';
const genre = url.searchParams.get('genre') ?? 'all';
const sort = url.searchParams.get('sort') ?? 'popular';
const status = url.searchParams.get('status') ?? 'all';
const params = new URLSearchParams({ page, genre, sort, status });
const apiURL = `${SCRAPER_URL}/api/browse?${params.toString()}`;
let data: { novels: NovelListing[]; page: number; hasNext: boolean };
try {
const res = await fetch(apiURL);
if (!res.ok) {
throw error(502, `Browse fetch failed: ${res.status}`);
}
data = await res.json();
} catch (e) {
if (e instanceof Error && 'status' in e) throw e;
throw error(502, 'Could not load browse page');
}
return {
novels: data.novels ?? [],
page: data.page ?? 1,
hasNext: data.hasNext ?? false,
genre,
sort,
status,
isAdmin: locals.user?.role === 'admin'
};
};