From 5d3a1a09efa91c152d537bb023ca0ca9a22a64cb Mon Sep 17 00:00:00 2001 From: Admin Date: Fri, 6 Mar 2026 19:47:03 +0500 Subject: [PATCH] Fix browse filters having no effect due to filter-agnostic cache key The MinIO cache key only encoded page number, so all filter combinations hit the same cache entry and returned unfiltered results. Introduce BrowseFilteredHTMLKey() that encodes sort/genre/status into the key (e.g. novelfire.net/html/new-isekai-completed/page-1.html); falls back to the original page-only key for default filters to preserve existing cached pages. --- .../orchestrator/orchestrator_test.go | 5 ++-- scraper/internal/server/handlers_browse.go | 2 +- scraper/internal/storage/hybrid.go | 4 ++++ scraper/internal/storage/minio.go | 23 +++++++++++++++++++ scraper/internal/storage/store.go | 3 +++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/scraper/internal/orchestrator/orchestrator_test.go b/scraper/internal/orchestrator/orchestrator_test.go index 5e08378..d1366c6 100644 --- a/scraper/internal/orchestrator/orchestrator_test.go +++ b/scraper/internal/orchestrator/orchestrator_test.go @@ -155,8 +155,9 @@ func (s *mockStore) SaveBrowsePage(_ context.Context, _, _ string) error { retur func (s *mockStore) GetBrowsePage(_ context.Context, _ string) (string, bool, error) { return "", false, nil } -func (s *mockStore) BrowseHTMLKey(_ string, _ int) string { return "" } -func (s *mockStore) BrowseCoverKey(_, _ string) string { return "" } +func (s *mockStore) BrowseHTMLKey(_ string, _ int) string { return "" } +func (s *mockStore) BrowseFilteredHTMLKey(_ string, _ int, _, _, _ string) string { return "" } +func (s *mockStore) BrowseCoverKey(_, _ string) string { return "" } func (s *mockStore) SaveBrowseAsset(_ context.Context, _ string, _ []byte, _ string) error { return nil } diff --git a/scraper/internal/server/handlers_browse.go b/scraper/internal/server/handlers_browse.go index 85b879e..033e208 100644 --- a/scraper/internal/server/handlers_browse.go +++ b/scraper/internal/server/handlers_browse.go @@ -80,7 +80,7 @@ func (s *Server) handleBrowse(w http.ResponseWriter, r *http.Request) { defer cancel() // ── Cache-first: try MinIO snapshot (new key layout) ───────────────── - cacheKey := s.store.BrowseHTMLKey(novelFireDomain, pageNum) + cacheKey := s.store.BrowseFilteredHTMLKey(novelFireDomain, pageNum, sortBy, genre, status) if html, ok, err := s.store.GetBrowsePage(ctx, cacheKey); err == nil && ok && len(html) > 0 { novels, hasNext := parseBrowsePage(strings.NewReader(html)) s.log.Debug("browse: served from cache", "key", cacheKey) diff --git a/scraper/internal/storage/hybrid.go b/scraper/internal/storage/hybrid.go index cd89cfb..967f147 100644 --- a/scraper/internal/storage/hybrid.go +++ b/scraper/internal/storage/hybrid.go @@ -330,6 +330,10 @@ func (h *HybridStore) BrowseHTMLKey(domain string, page int) string { return BrowseHTMLKey(domain, page) } +func (h *HybridStore) BrowseFilteredHTMLKey(domain string, page int, sort, genre, status string) string { + return BrowseFilteredHTMLKey(domain, page, sort, genre, status) +} + func (h *HybridStore) BrowseCoverKey(domain, slug string) string { return BrowseCoverKey(domain, slug) } diff --git a/scraper/internal/storage/minio.go b/scraper/internal/storage/minio.go index c64f2a4..797cdf0 100644 --- a/scraper/internal/storage/minio.go +++ b/scraper/internal/storage/minio.go @@ -223,10 +223,33 @@ func (m *MinioClient) PresignAudio(ctx context.Context, key string, expires time // BrowseHTMLKey returns the MinIO object key for a SingleFile HTML snapshot. // Layout: {domain}/html/page-{n}.html +// This uses the default (popular/all/all) filter combination. func BrowseHTMLKey(domain string, page int) string { return fmt.Sprintf("%s/html/page-%d.html", domain, page) } +// BrowseFilteredHTMLKey returns the MinIO object key for a browse page snapshot +// that includes filter parameters (sort, genre, status) in the key so that +// different filter combinations are cached independently. +// Layout: {domain}/html/{sort}-{genre}-{status}/page-{n}.html +// Falls back to BrowseHTMLKey when all filters are at their default values +// (sort=popular, genre=all, status=all) for cache compatibility. +func BrowseFilteredHTMLKey(domain string, page int, sort, genre, status string) string { + if (sort == "" || sort == "popular") && (genre == "" || genre == "all") && (status == "" || status == "all") { + return BrowseHTMLKey(domain, page) + } + if sort == "" { + sort = "popular" + } + if genre == "" { + genre = "all" + } + if status == "" { + status = "all" + } + return fmt.Sprintf("%s/html/%s-%s-%s/page-%d.html", domain, sort, genre, status, page) +} + // BrowseCoverKey returns the MinIO object key for a cached book cover image. // Layout: {domain}/assets/book-covers/{slug}.jpg func BrowseCoverKey(domain, slug string) string { diff --git a/scraper/internal/storage/store.go b/scraper/internal/storage/store.go index 5b54bae..ae23918 100644 --- a/scraper/internal/storage/store.go +++ b/scraper/internal/storage/store.go @@ -156,6 +156,9 @@ type Store interface { // BrowseHTMLKey returns the MinIO object key for a SingleFile HTML snapshot. // Layout: {domain}/html/page-{n}.html BrowseHTMLKey(domain string, page int) string + // BrowseFilteredHTMLKey returns the MinIO object key for a browse page snapshot + // that incorporates sort/genre/status so different filter combos are cached separately. + BrowseFilteredHTMLKey(domain string, page int, sort, genre, status string) string // BrowseCoverKey returns the MinIO object key for a cached book cover image. // Layout: {domain}/assets/book-covers/{slug}.jpg BrowseCoverKey(domain, slug string) string