Delete the kagane-specific cover path (#63)

The web proxy for kagane covers is dead: since #62 every Site's cover
bytes land in the content-addressed store at creation or on the poll,
and the one public route serves them all. Remove the second way to
reach a Cover:

- Bookmark.CoverURL() and the templates' use of it: templates render
  the wire value (.Cover) and nothing else.
- GET /img/kagane/{id}, web.CoverFetcher, coverIDRe: the route and its
  identifier validation are gone, and with them web/cover.go.
- store.KaganeImageID, GetKaganeCover, PutKaganeCover,
  kaganeCoverSourceURL: the proxy's persistence.
- Bookmark.CoverSource: dead once CoverURL is gone.

Acquisition keeps the browser where kagane genuinely needs it, but the
Site name leaves the routing: kaganeImageURLRe lives in browser.go with
the rest of the per-Site knowledge, browserCoverURL claims the URLs the
sidecar alone can fetch, and fetchCoverBytes routes by URL shape with
no Site argument. No plain-TLS fallback for a claimed URL — that would
only retrieve a challenge page.

Cover tests: kagane route tests removed, the general-route guarantees
they pinned kept and re-pinned — unstored and traversal-shaped
addresses serve nothing (TestPublicCoverRejectsUnknownAddress), non-image
content types are never echoed back (TestPublicCoverNeverEchoesNonImage
+ TestCoverStoreAcceptsAnySourceURL). Store content-addressing and
reopen-persistence tests rewritten against PutCover/GetCover.

No Site name remains in a cover code path outside the acquisition
module; go test ./... green.
This commit is contained in:
2026-08-10 11:24:42 +07:00
parent 78234f3c19
commit cce3d61799
16 changed files with 127 additions and 480 deletions
+4 -9
View File
@@ -57,10 +57,6 @@ type Config struct {
NovelUserscriptPath string
// LatestPoll configures the background latest-chapter fetcher.
LatestPoll LatestPoll
// Covers proxies kagane cover images for the web UI. Not from the
// environment: it is the shared headless browser, wired in main once it
// connects, and nil in every test router.
Covers web.CoverFetcher
}
// LatestPoll configures the background latest-chapter poller.
@@ -235,7 +231,7 @@ func newRouter(s *store.Store, cfg Config) http.Handler {
// The browser UI is always registered; signing in is Discord OAuth, so
// there is no password to forget and no gate to leave unset.
wh, err := web.New(s, cfg.Discord, []byte(cfg.TokenKey),
cfg.UserscriptPath, cfg.NovelUserscriptPath, cfg.Covers)
cfg.UserscriptPath, cfg.NovelUserscriptPath)
if err != nil {
log.Fatalf("web handler: %v", err)
}
@@ -307,9 +303,9 @@ func main() {
// chapters on its own.
//
// One headless browser serves both consumers that need a Cloudflare
// challenge cleared: the poller's kagane/novelfull fetches and the web
// UI's kagane cover proxy. Optional — unset leaves both degraded to what
// they were before the sidecar existed.
// challenge cleared: the poller's kagane/novelfull page fetches and
// kagane's cover bytes. Optional — unset leaves kagane unpolled and its
// Covers blank until the bytes exist.
var browser latest.Fetcher
pollCtx, stopPoll := context.WithCancel(context.Background())
defer stopPoll()
@@ -319,7 +315,6 @@ func main() {
log.Printf("browser fetcher disabled: %v", err)
} else {
browser = bf
cfg.Covers = bf
context.AfterFunc(pollCtx, bf.Close)
log.Printf("browser fetcher at %s", ws)
}