From 8c47aa3a11d1b3e8e26ba2b1b007ec7fc0c836fa Mon Sep 17 00:00:00 2001 From: Admin Date: Fri, 17 Apr 2026 13:32:48 +0500 Subject: [PATCH] fix: cover proxy routing, session filtering, library tab deep-link, profile UX - Catalogue/cover: rewrite raw scraped cover URLs to /api/cover/{domain}/{slug} in handleCatalogue so all covers route through the backend proxy; fix broken cdn.novelfire.net fallback in handleGetCover to read stored URL from PocketBase - Catalogue/profile: add Svelte 5 onerror handlers on cover tags to show letter-initial placeholder when image fails to load - Library page: read ?status URL param to initialise activeShelf tab on load so /books?status=reading correctly pre-selects the Reading tab - Sessions: filter bot/tool user-agents (curl, python, wget, etc.) and debug-IP sessions from listUserSessions display; also purge them in pruneStaleUserSessions - Profile: show email under username, quick stats chips (streak/chapters/completed) in header, reading count on Library row, dedicated Sign out row, history covers routed through /api/cover proxy Co-Authored-By: Claude Sonnet 4.6 --- backend/internal/backend/handlers.go | 31 +++++++++++--- ui/src/lib/server/pocketbase.ts | 34 ++++++++++++++-- ui/src/routes/books/+page.svelte | 19 ++++++++- ui/src/routes/catalogue/+page.svelte | 20 +++++++++- ui/src/routes/profile/+page.svelte | 60 ++++++++++++++++++++++++---- 5 files changed, 145 insertions(+), 19 deletions(-) diff --git a/backend/internal/backend/handlers.go b/backend/internal/backend/handlers.go index 9da4483..8bfcd52 100644 --- a/backend/internal/backend/handlers.go +++ b/backend/internal/backend/handlers.go @@ -293,7 +293,8 @@ func (s *Server) handleGetRanking(w http.ResponseWriter, r *http.Request) { // handleGetCover handles GET /api/cover/{domain}/{slug}. // Serves the cover image directly from MinIO when available; falls back to a -// redirect to the novelfire CDN when the cover has not yet been downloaded. +// redirect to the stored cover URL from PocketBase when the cover has not yet +// been downloaded to MinIO. func (s *Server) handleGetCover(w http.ResponseWriter, r *http.Request) { slug := r.PathValue("slug") if slug == "" { @@ -318,10 +319,20 @@ func (s *Server) handleGetCover(w http.ResponseWriter, r *http.Request) { } } - // Fallback: redirect to the CDN. The caller sees a working image; the - // cover will be populated on the next catalogue refresh run. - coverURL := fmt.Sprintf("https://cdn.novelfire.net/covers/%s.jpg", slug) - http.Redirect(w, r, coverURL, http.StatusFound) + // Fallback: read the stored cover URL from PocketBase and redirect to it. + // This avoids the broken cdn.novelfire.net domain and uses the actual URL + // scraped from the source. If the book is not found, return 404. + meta, ok, err := s.deps.BookReader.ReadMetadata(r.Context(), slug) + if err != nil { + s.deps.Log.Warn("handleGetCover: ReadMetadata error", "slug", slug, "err", err) + http.Error(w, "cover not found", http.StatusNotFound) + return + } + if !ok || meta.Cover == "" || strings.HasPrefix(meta.Cover, "/api/cover/") { + http.Error(w, "cover not found", http.StatusNotFound) + return + } + http.Redirect(w, r, meta.Cover, http.StatusFound) } // ── Preview (live scrape, no store writes) ───────────────────────────────────── @@ -1891,6 +1902,16 @@ func (s *Server) handleCatalogue(w http.ResponseWriter, r *http.Request) { return } + // Rewrite raw scraped cover URLs to go through the backend cover proxy. + // /api/cover/{domain}/{slug} serves from MinIO when available, otherwise + // redirects to the CDN. This avoids ERR_BLOCKED_BY_ORB when the source + // site returns HTML error pages instead of images. + for i := range books { + if !strings.HasPrefix(books[i].Cover, "/api/cover/") { + books[i].Cover = fmt.Sprintf("/api/cover/novelfire.net/%s", books[i].Slug) + } + } + hasNext := int64(page*limit) < total w.Header().Set("Cache-Control", "public, max-age=60") diff --git a/ui/src/lib/server/pocketbase.ts b/ui/src/lib/server/pocketbase.ts index ac4b6c2..4c7159e 100644 --- a/ui/src/lib/server/pocketbase.ts +++ b/ui/src/lib/server/pocketbase.ts @@ -1431,10 +1431,34 @@ export async function isSessionRevoked(authSessionId: string): Promise } /** - * List all active sessions for a user. + * Returns true for user-agents that are clearly automated tools (curl, scrapers, + * debug logins, etc.) that should not appear in the user-facing sessions list. + * These sessions still exist in the DB so auth checks continue to work. + */ +function isBotUserAgent(ua: string): boolean { + if (!ua) return false; + const lower = ua.toLowerCase(); + return ( + lower.startsWith('curl/') || + lower.startsWith('python') || + lower.startsWith('wget/') || + lower.startsWith('go-http-client') || + lower.startsWith('axios/') || + lower.startsWith('node-fetch') || + lower.startsWith('undici') || + lower.startsWith('okhttp') || + lower.startsWith('java/') + ); +} + +/** + * List all active sessions for a user, excluding non-browser/tool sessions + * (curl, debug-login artifacts, scrapers, etc.) from the displayed list. + * The records still exist in the DB so auth validity checks are unaffected. */ export async function listUserSessions(userId: string): Promise { - return listAll('user_sessions', `user_id="${userId}"`, '-last_seen'); + const all = await listAll('user_sessions', `user_id="${userId}"`, '-last_seen'); + return all.filter((s) => !isBotUserAgent(s.user_agent) && s.ip !== 'debug'); } /** @@ -1453,9 +1477,11 @@ async function pruneStaleUserSessions( const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); const toDelete = new Set(); - // Mark stale sessions + // Mark stale sessions and debug/tool sessions for deletion for (const s of all) { - if (s.last_seen < cutoff) toDelete.add(s.id); + if (s.last_seen < cutoff || s.ip === 'debug' || isBotUserAgent(s.user_agent)) { + toDelete.add(s.id); + } } // Mark excess sessions beyond the cap (oldest first — list is sorted -last_seen) diff --git a/ui/src/routes/books/+page.svelte b/ui/src/routes/books/+page.svelte index 892d970..a1df75c 100644 --- a/ui/src/routes/books/+page.svelte +++ b/ui/src/routes/books/+page.svelte @@ -1,5 +1,7 @@