feature/backend-rewrite #2
Reference in New Issue
Block a user
Delete Branch "feature/backend-rewrite"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
new backend rewrite:
arch
- GET /api/browse fetches novelfire.net catalogue page and parses it with golang.org/x/net/html; returns JSON {novels, page, hasNext} with per-novel slug/title/cover/rank/rating/chapters/url. Supports page, genre, sort, status query params. - GET /api/scrape/status returns {"running": bool} for polling job state from the UIReplace selected={data.x === opt.value} on individual <option> elements with value={data.x} on the <select> — the idiomatic Svelte approach that ensures correct hydration and form submission of the current filter values.- ui/Dockerfile: copy package-lock.json and run npm ci --omit=dev in the runtime stage so marked (and other runtime deps) are available to adapter-node at startup — fixes ERR_MODULE_NOT_FOUND for 'marked' - storage: add ReindexChapters to Store interface and HybridStore — walks MinIO objects for a slug, reads chapter titles, upserts chapters_idx - server: add POST /api/reindex/{slug} to rebuild chapters_idx from MinIO- Replace BrowsePageKey(genre/sort/status/type/page) with BrowseHTMLKey(domain, page) -> {domain}/html/page-{n}.html - Add BrowseCoverKey(domain, slug) -> {domain}/assets/book-covers/{slug}.jpg - Add SaveBrowseAsset/GetBrowseAsset for binary assets in browse bucket - Rewrite triggerBrowseSnapshot: after storing HTML, parse it, upsert ranking records with MinIO cover keys, fire per-novel cover download goroutines - Add handleGetCover endpoint (GET /api/cover/{domain}/{slug}) to proxy cover images from MinIO - handleGetRanking rewrites MinIO cover keys to /api/cover/... proxy URLs - Update save-browse CLI to use BrowseHTMLKey, populate ranking, and download covers- Add audio.svelte.ts: module singleton AudioStore (Svelte 5 runes) with slug/chapter/title metadata, status, progress, playback state, and toggleRequest/seekRequest signals for layout<->audio element communication - Rewrite AudioPlayer.svelte as a store controller: no <audio> element owned; drives audioStore for presign->generate->play flow; shows inline controls when current chapter is active, 'Now playing / Load this chapter' banner when a different chapter is playing - Update +layout.svelte: single persistent <audio> outside {#key} block so it never unmounts on navigation; effects to load URL, sync speed, handle toggle/seek requests; fixed bottom mini-player bar with seek, skip 15s/30s, speed cycle, go-to-chapter link, dismiss; pb-24 padding when active - Pass chapterTitle and bookTitle from chapter page to AudioPlayerTwo bugs caused the start/stop loop: 1. The <audio> element was wrapped in {#if audioStore.audioUrl}, so whenever any reactive state changed (e.g. currentTime ticking), Svelte could destroy and recreate the element, firing onpause and then the URL effect restarting playback. 2. Comparing audioEl.src !== url is unreliable — browsers normalise the src property to a full absolute URL, causing false mismatches every tick. Fix: make <audio> always present in the DOM (display:none), and track the loaded URL in a plain local variable (loadedUrl) instead of reading audioEl.src.- Fix speed/voice bug: AudioPlayer no longer accepts speed/voice as props; startPlayback() reads audioStore.voice/speed directly instead of overwriting them - Add GET /api/voices endpoint (Go) proxying Kokoro, cached in-memory - Add POST /api/audio/voice-samples endpoint (Go) to pre-generate sample clips for all voices and store them in MinIO under _voice-samples/{voice}.mp3 - Add GET /api/presign/voice-sample/{voice} endpoint (Go) - Add SvelteKit proxy routes: /api/voices, /api/presign/voice-sample, /api/audio/voice-samples - Add presignVoiceSample() helper in minio.ts with proper host rewrite - Pass book.cover through +page.server.ts -> +page.svelte -> AudioPlayer - Set navigator.mediaSession.metadata on playback start so cover art, book title, and chapter title appear on phone lock screen / notificationAdd warmVoiceSamples() goroutine launched from ListenAndServe. It waits up to 30s for Kokoro to become reachable, then generates missing voice sample clips for all available voices and uploads them to MinIO (_voice-samples/{voice}.mp3). Already-existing samples are skipped, so the operation is idempotent on restarts.Adds GET /api/book-preview/{slug} and GET /api/chapter-text-preview/{slug}/{n} Go endpoints that scrape live from novelfire.net without persisting to PocketBase or MinIO. The UI book page falls back to the preview endpoint when a book is not found in PocketBase, showing a 'not in library' badge and the scraped chapter list. Chapter pages handle ?preview=1 to fetch and render chapter text live, skipping MinIO and suppressing the audio player.Replace blocking POST /api/audio with a non-blocking 202 flow: the Go handler immediately enqueues a job in a new `audio_jobs` PocketBase collection and returns {job_id, status}. A background goroutine runs the actual Kokoro TTS work and updates job status (pending → generating → done/failed). A new GET /api/audio/status/{slug}/{n} endpoint lets clients poll progress. The SvelteKit proxy and AudioPlayer.svelte are updated to POST, then poll the status route every 2s until done.- AudioPlayerService: replace loading/generating states with a single .generating state; presign fast path before triggering TTS; fix URL resolution for relative paths; cache cover artwork to avoid re-downloads; fix duration KVO race (durationObserver); use toleranceBefore/After:zero for accurate seeking; prefetch next chapter unconditionally (not just when autoNext is on); handle auto-next internally on playback finish - APIClient: fix URL construction (appendingPathComponent encodes slashes); add verbose debug logging for all requests/responses/decoding errors; fix sessions() to unwrap {sessions:[]} envelope; fix BrowseResponse CodingKey hasNext → camelCase - AudioGenerateResponse: update to synchronous {url, filename} shape - Models: remove redundant AudioStatus enum; remove CodingKeys from UserSettings (server now sends camelCase) - Views: fix alert bindings (.constant → proper two-way Binding); add error+retry UI to BrowseView; add pull-to-refresh to browse list; fix ChapterReaderView to show error state and use dynamic WKWebView height; fix HomeView HStack alignment; only treat audio as current if the player isActive; suppress CancellationError from error UI- Go scraper: add PresignAvatarUploadURL/PresignAvatarURL/DeleteAvatar to Store interface, implement on HybridStore+MinioClient, register GET /api/presign/avatar-upload/{userId} and /api/presign/avatar/{userId} - SvelteKit: replace direct AWS S3 SDK in minio.ts with presign calls to the Go scraper; rewrite avatar +server.ts (POST=presign, PATCH=record key) - iOS: rewrite uploadAvatar() as 3-step presigned PUT flow; refactor chip components into shared ChipButton in CommonViews.swift- Add AvatarCropModal.svelte using cropperjs v1: 1:1 crop, 400×400 output, JPEG/WebP output, dark glassmorphic UI - Rewrite profile page avatar upload to use presigned PUT flow (POST→PUT→PATCH) instead of sending raw FormData directly; crop modal opens on file select - Add GET /health → {status:ok} for Docker healthcheck - Simplify Dockerfile: drop runtime npm ci (adapter-node bundles all deps) - Fix docker-compose UI healthcheck: /health route, 127.0.0.1 to avoid IPv6 localhost resolution failure in alpine busybox wgetmarked({ async: true }) triggers a dynamic internal require inside marked that vite/rollup treats as external, causing ERR_MODULE_NOT_FOUND at runtime in the adapter-node Docker image which ships no node_modules. Switching to the synchronous marked() call makes rollup inline the full library into the server chunk.The /api/comments/[id] delete route was never created; the deleteComment helper in pocketbase.ts existed but was unreachable. Added DELETE /api/comment/[id] route handler alongside the existing vote route. Updated CommentsSection.svelte and iOS APIClient to use /api/comment/{id} for both delete and (already fixed) vote, keeping all comment-mutation endpoints under the singular /api/comment/ prefix to avoid SvelteKit route conflicts with /api/comments/[slug].View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.