Files
libnovel/ui-v2/src/routes/api/scrape/range/+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

63 lines
1.8 KiB
TypeScript

/**
* 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 });
};