feat(v3): add v3 stack — backend rewrite, renamed env vars, docs

- New Go backend binary (backend + runner) replacing old scraper/
- Rename SCRAPER_API_URL → BACKEND_API_URL in UI env and docker-compose
- Rename scraperFetch → backendFetch across all 19 UI server files
- Remove SCRAPER_PROXY env var and proxy transport from browser.Config
- Add Meilisearch, Valkey, Caddy to docker-compose
- Add docs/: api-endpoints.md, request-flow.mermaid.md, data-flow.mermaid.md
This commit is contained in:
Admin
2026-03-22 17:27:32 +05:00
parent 29d0eeb7e8
commit a85636d5db
178 changed files with 25951 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { presignAudio } from '$lib/server/minio';
import { log } from '$lib/server/logger';
import * as cache from '$lib/server/presignCache';
/**
* GET /api/presign/audio?slug=...&n=...&voice=...
* Returns a presigned MinIO URL for the audio file so the browser
* can stream it directly without going through the server.
* Returns 404 when the audio has not been generated yet.
*
* Results are cached in-process for 50 minutes (MinIO URLs are valid 1 hour)
* to avoid a backend + MinIO round-trip on every "Play" click.
*/
export const GET: RequestHandler = async ({ url }) => {
const slug = url.searchParams.get('slug');
// Accept both 'n' (web) and 'chapter' (iOS) as the chapter number param
const n = parseInt(url.searchParams.get('n') ?? url.searchParams.get('chapter') ?? '', 10);
const voice = url.searchParams.get('voice') ?? '';
if (!slug || !n || n < 1) {
error(400, 'Missing slug or n');
}
const cacheKey = cache.audioKey(slug, n, voice);
// Fast path: return cached URL if still valid.
const cached = await cache.get(cacheKey);
if (cached) {
return json({ url: cached });
}
// Slow path: call backend → MinIO presign.
try {
const presignedUrl = await presignAudio(slug, n, voice || undefined);
await cache.set(cacheKey, presignedUrl);
return json({ url: presignedUrl });
} catch (e) {
const status = (e as { status?: number }).status;
if (status === 404) {
error(404, 'Audio not found');
}
log.error('presign', 'presign audio failed', { slug, n, err: String(e) });
error(500, `Could not get presigned URL: ${e}`);
}
};

View File

@@ -0,0 +1,40 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { presignVoiceSample } from '$lib/server/minio';
import * as cache from '$lib/server/presignCache';
/**
* GET /api/presign/voice-sample?voice=af_bella
* Returns a presigned URL for the voice sample audio file.
* Returns 404 if the sample has not been generated yet.
*
* Results are cached in-process for 50 minutes to avoid a backend + MinIO
* round-trip on every voice-selection preview play.
*/
export const GET: RequestHandler = async ({ url }) => {
const voice = url.searchParams.get('voice');
if (!voice) {
error(400, 'Missing voice parameter');
}
const cacheKey = cache.sampleKey(voice);
// Fast path: return cached URL if still valid.
const cached = await cache.get(cacheKey);
if (cached) {
return json({ url: cached });
}
// Slow path: call backend → MinIO presign.
try {
const presignedUrl = await presignVoiceSample(voice);
await cache.set(cacheKey, presignedUrl);
return json({ url: presignedUrl });
} catch (e) {
const status = (e as { status?: number }).status;
if (status === 404) {
error(404, 'Voice sample not found');
}
error(502, `Failed to presign voice sample: ${e}`);
}
};