feat: add MinIO presign endpoints to scraper API and SvelteKit presign helper
This commit is contained in:
67
ui/src/lib/server/minio.ts
Normal file
67
ui/src/lib/server/minio.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Server-side MinIO presign helper.
|
||||
* Calls the scraper API to get presigned URLs, then optionally rewrites
|
||||
* the MinIO host to the public-facing URL for browser use.
|
||||
*
|
||||
* Never import this from client-side code.
|
||||
*/
|
||||
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { env as pubEnv } from '$env/dynamic/public';
|
||||
|
||||
const SCRAPER_URL = env.SCRAPER_API_URL ?? 'http://localhost:8080';
|
||||
// Public MinIO URL — used to rewrite presigned URLs so the browser can reach MinIO directly.
|
||||
// In docker-compose this would differ from the internal endpoint.
|
||||
const MINIO_PUBLIC_URL = pubEnv.PUBLIC_MINIO_PUBLIC_URL ?? 'http://localhost:9000';
|
||||
|
||||
/**
|
||||
* Rewrites the MinIO host in a presigned URL to the public-facing URL.
|
||||
* The presigned URL is signed against the internal endpoint (e.g. minio:9000),
|
||||
* but the browser needs the public URL (e.g. localhost:9000 in dev, or a CDN in prod).
|
||||
* Rewriting the host preserves all query params (signature, expiry, etc).
|
||||
*/
|
||||
function rewriteHost(presignedUrl: string): string {
|
||||
try {
|
||||
const u = new URL(presignedUrl);
|
||||
const pub = new URL(MINIO_PUBLIC_URL);
|
||||
u.protocol = pub.protocol;
|
||||
u.hostname = pub.hostname;
|
||||
u.port = pub.port;
|
||||
return u.toString();
|
||||
} catch {
|
||||
return presignedUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a presigned URL for a chapter markdown file.
|
||||
* URL is valid for ~15 minutes (set by the scraper).
|
||||
* The returned URL points to the public MinIO endpoint and can be used
|
||||
* server-side (in a +page.server.ts load function) to fetch the markdown content.
|
||||
*/
|
||||
export async function presignChapter(slug: string, n: number): Promise<string> {
|
||||
const res = await fetch(`${SCRAPER_URL}/api/presign/chapter/${slug}/${n}`);
|
||||
if (!res.ok) throw new Error(`presign chapter ${slug}/${n}: ${res.status}`);
|
||||
const data = (await res.json()) as { url: string };
|
||||
return rewriteHost(data.url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a presigned URL for an audio file.
|
||||
* URL is valid for ~1 hour. The URL is returned to the browser for direct streaming.
|
||||
*/
|
||||
export async function presignAudio(
|
||||
slug: string,
|
||||
n: number,
|
||||
voice?: string,
|
||||
speed?: number
|
||||
): Promise<string> {
|
||||
const params = new URLSearchParams();
|
||||
if (voice) params.set('voice', voice);
|
||||
if (speed) params.set('speed', String(speed));
|
||||
const qs = params.toString() ? `?${params.toString()}` : '';
|
||||
const res = await fetch(`${SCRAPER_URL}/api/presign/audio/${slug}/${n}${qs}`);
|
||||
if (!res.ok) throw new Error(`presign audio ${slug}/${n}: ${res.status}`);
|
||||
const data = (await res.json()) as { url: string };
|
||||
return rewriteHost(data.url);
|
||||
}
|
||||
Reference in New Issue
Block a user