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
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:
@@ -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 });
|
||||
};
|
||||
|
||||
40
ui/src/routes/api/scrape/cancel/[id]/+server.ts
Normal file
40
ui/src/routes/api/scrape/cancel/[id]/+server.ts
Normal 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 });
|
||||
};
|
||||
@@ -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(() => ({}));
|
||||
|
||||
19
ui/src/routes/api/scrape/task/[id]/+server.ts
Normal file
19
ui/src/routes/api/scrape/task/[id]/+server.ts
Normal 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 ?? '' });
|
||||
};
|
||||
Reference in New Issue
Block a user