From 2ba1e0960bed63d95f5b18384a2eea511ddbbdf3 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 17 Oct 2026 17:15:14 -0400 Subject: [PATCH] refactor(hash-conflicts): one usage-counts reader for the API and SSR surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pending-hash-conflict walk existed twice (JSON admin API emitting numeric counts, SSR page emitting a formatted summary) — deliberately different shapes, but both duplicated the counts-or-zero lookup and the five-field total. MediaItemUsageCountsTotal is the shared read; the per-surface shaping stays local on purpose. --- internal/handlers/hash_conflicts.go | 20 ++++++++++++++++---- internal/router/frontend.go | 6 +----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/internal/handlers/hash_conflicts.go b/internal/handlers/hash_conflicts.go index bc50c883..fbc0063e 100644 --- a/internal/handlers/hash_conflicts.go +++ b/internal/handlers/hash_conflicts.go @@ -2,6 +2,7 @@ package handlers import ( "bookhoard/internal/database" + "context" "fmt" "net/http" "time" @@ -48,6 +49,20 @@ type HashConflictResponse struct { Items []HashConflictItem `json:"items"` } +// MediaItemUsageCountsTotal reads an item's usage tally for the +// hash-conflict surfaces, treating a lookup failure as "no usage" +// (these surfaces only use it to warn about data loss), and returns +// the counts with their precomputed total. +func MediaItemUsageCountsTotal(ctx context.Context, db *database.Queries, id pgtype.UUID) (database.GetMediaItemUsageCountsRow, int) { + counts, err := db.GetMediaItemUsageCounts(ctx, id) + if err != nil { + counts = database.GetMediaItemUsageCountsRow{} + } + total := counts.ProgressCount + counts.HighlightsCount + counts.BookmarksCount + + counts.NotesCount + counts.CollectionsCount + return counts, int(total) +} + // ListHashConflicts returns all pending hash conflict groups with their member // items and usage counts. // GET /api/admin/hash-conflicts @@ -80,10 +95,7 @@ func (h *HashConflictsHandler) ListHashConflicts(c *echo.Context) error { } for _, mi := range items { - counts, err := h.db.GetMediaItemUsageCounts(ctx, mi.ID) - if err != nil { - counts = database.GetMediaItemUsageCountsRow{} - } + counts, _ := MediaItemUsageCountsTotal(ctx, h.db, mi.ID) resp.Items = append(resp.Items, HashConflictItem{ ID: uuid.UUID(mi.ID.Bytes), Title: mi.Title, diff --git a/internal/router/frontend.go b/internal/router/frontend.go index 52d55f76..36571b60 100644 --- a/internal/router/frontend.go +++ b/internal/router/frontend.go @@ -1214,11 +1214,7 @@ func registerFrontendRoutes(cfg *Config) { } for _, mi := range items { - counts, err := cfg.Queries.GetMediaItemUsageCounts(c.Request().Context(), mi.ID) - if err != nil { - counts = database.GetMediaItemUsageCountsRow{} - } - totalData := counts.ProgressCount + counts.HighlightsCount + counts.BookmarksCount + counts.NotesCount + counts.CollectionsCount + counts, totalData := handlers.MediaItemUsageCountsTotal(c.Request().Context(), cfg.Queries, mi.ID) conflict.Items = append(conflict.Items, templates.HashConflictItemData{ ID: uuid.UUID(mi.ID.Bytes).String(), Title: mi.Title,