Some checks failed
Deploy / Deploy Production (push) Has been skipped
Deploy / Cleanup Preview (push) Has been skipped
Deploy / Deploy Preview (push) Failing after 0s
CI / Scraper / Test (pull_request) Successful in 11s
CI / UI / Build (pull_request) Failing after 14s
CI / Scraper / Lint (pull_request) Successful in 23s
CI / Scraper / Build (pull_request) Successful in 24s
iOS CI / Build (push) Has been cancelled
iOS CI / Test (push) Has been cancelled
iOS CI / Build (pull_request) Failing after 2s
iOS CI / Test (pull_request) Has been skipped
- iOS SwiftUI app (ios/LibNovel/) targeting iOS 17+, generated via xcodegen - Full feature set: auth, home, library, book detail, chapter reader, browse, audio player, profile - Kingfisher for image loading, swift-markdown-ui for chapter rendering - Base URL: https://v2.libnovel.kalekber.cc - SvelteKit JSON API routes (ui/src/routes/api/) for iOS consumption: auth/login, auth/register, auth/me, auth/logout, auth/change-password, home, library, book/[slug], chapter/[slug]/[n], search, ranking, progress/[slug], presign/audio (updated) - Gitea Actions CI: .gitea/workflows/ios.yaml (build + test on macos-latest) - justfile: ios-gen, ios-build, ios-test recipes
34 lines
1.2 KiB
TypeScript
34 lines
1.2 KiB
TypeScript
import { json, error } from '@sveltejs/kit';
|
|
import type { RequestHandler } from './$types';
|
|
import { presignAudio } from '$lib/server/minio';
|
|
import { log } from '$lib/server/logger';
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
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') ?? undefined;
|
|
|
|
if (!slug || !n || n < 1) {
|
|
error(400, 'Missing slug or n');
|
|
}
|
|
|
|
try {
|
|
const presignedUrl = await presignAudio(slug, n, voice);
|
|
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}`);
|
|
}
|
|
};
|