chore: migrate to v3, Doppler secrets, clean up legacy code
Some checks failed
CI / v3 / Check ui (pull_request) Failing after 15s
CI / v3 / Test backend (pull_request) Failing after 16s
CI / v3 / Docker / backend (pull_request) Has been skipped
CI / v3 / Docker / runner (pull_request) Has been skipped
CI / v3 / Docker / ui (pull_request) Has been skipped

- Remove all pre-v3 code: scraper, ui-v2, backend v1, ios v1+v2, legacy CI workflows
- Flatten v3/ contents to repo root
- Add Doppler secrets management (project=libnovel, config=prd)
- Add justfile with doppler run wrappers for all docker compose commands
- Strip hardcoded env fallbacks from docker-compose.yml
- Add minimal README.md
- Clean up .gitignore
This commit is contained in:
Admin
2026-03-23 17:21:12 +05:00
parent 1118392811
commit 59e8cdb19a
522 changed files with 5259 additions and 80365 deletions

View File

@@ -1,7 +1,7 @@
/**
* POST /api/scrape
*
* Proxies scrape requests to the Go scraper backend.
* Proxies scrape requests to the Go backend.
* Admin-only — returns 403 if the authenticated user is not an admin.
*
* Request body (JSON):
@@ -17,10 +17,8 @@
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';
import { backendFetch } from '$lib/server/scraper';
export const POST: RequestHandler = async ({ request, locals }) => {
// Admin guard
@@ -39,26 +37,25 @@ export const POST: RequestHandler = async ({ request, locals }) => {
const isBookScrape = typeof body.url === 'string' && body.url.length > 0;
const endpoint = isBookScrape ? '/scrape/book' : '/scrape';
const upstream = `${SCRAPER_URL}${endpoint}`;
let res: Response;
try {
res = await fetch(upstream, {
res = await backendFetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: isBookScrape ? JSON.stringify({ url: body.url }) : undefined
});
} catch (e) {
log.error('scrape', 'scraper proxy network error', { endpoint, err: String(e) });
throw error(502, 'Could not reach scraper');
log.error('scrape', 'backend proxy network error', { endpoint, err: String(e) });
throw error(502, 'Could not reach backend');
}
if (!res.ok && res.status >= 500) {
const text = await res.text().catch(() => '');
log.error('scrape', 'scraper returned error', { endpoint, status: res.status, body: text });
log.error('scrape', 'backend returned error', { endpoint, status: res.status, body: text });
}
const data = await res.json().catch(() => ({}));
// Pass through the status code from the Go scraper (202, 409, 400, …)
// Pass through the status code from the Go backend (202, 409, 400, …)
return json(data, { status: res.status });
};

View File

@@ -0,0 +1,40 @@
/**
* POST /api/scrape/cancel/[id]
*
* Admin-only proxy that cancels a pending scrape (or audio) task by ID.
* Forwards the request to the Go backend POST /api/cancel-task/{id}.
*
* Responses:
* 200 OK — task cancelled
* 403 Forbidden — not an admin
* 409 Conflict — task cannot be cancelled (already running/done/not found)
*/
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { log } from '$lib/server/logger';
import { backendFetch } from '$lib/server/scraper';
export const POST: RequestHandler = async ({ params, locals }) => {
if (!locals.user || locals.user.role !== 'admin') {
throw error(403, 'Forbidden');
}
const { id } = params;
if (!id) {
throw error(400, 'Missing task id');
}
let res: Response;
try {
res = await backendFetch(`/api/cancel-task/${encodeURIComponent(id)}`, {
method: 'POST'
});
} catch (e) {
log.error('scrape/cancel', 'network error cancelling task', { id, err: String(e) });
throw error(502, 'Could not reach backend');
}
const data = await res.json().catch(() => ({}));
return json(data, { status: res.status });
};

View File

@@ -1,7 +1,7 @@
/**
* POST /api/scrape/range
*
* Proxies range-scrape requests to the Go scraper backend at POST /scrape/book/range.
* Proxies range-scrape requests to the Go backend at POST /scrape/book/range.
* Admin-only.
*
* Request body (JSON):
@@ -17,10 +17,8 @@
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';
import { backendFetch } from '$lib/server/scraper';
export const POST: RequestHandler = async ({ request, locals }) => {
// Admin guard
@@ -39,22 +37,21 @@ export const POST: RequestHandler = async ({ request, locals }) => {
throw error(400, 'url and from are required');
}
const upstream = `${SCRAPER_URL}/scrape/book/range`;
let res: Response;
try {
res = await fetch(upstream, {
res = await backendFetch('/scrape/book/range', {
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');
log.error('scrape/range', 'backend proxy network error', { err: String(e) });
throw error(502, 'Could not reach backend');
}
if (!res.ok && res.status >= 500) {
const text = await res.text().catch(() => '');
log.error('scrape/range', 'scraper returned error', { status: res.status, body: text });
log.error('scrape/range', 'backend returned error', { status: res.status, body: text });
}
const data = await res.json().catch(() => ({}));

View File

@@ -0,0 +1,19 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getScrapingTask } from '$lib/server/pocketbase';
/**
* GET /api/scrape/task/[id]
*
* Returns { id, status, error_message } for a single scraping task.
* Used by the book detail page to poll for task completion.
*/
export const GET: RequestHandler = async ({ params }) => {
const { id } = params;
if (!id) throw error(400, 'Missing task id');
const task = await getScrapingTask(id).catch(() => null);
if (!task) throw error(404, 'Task not found');
return json({ id: task.id, status: task.status, error_message: task.error_message ?? '' });
};