fix(router): use service layer for SSR book loading

Replace direct database call with service layer to fix SSR
returning 0 books on initial page load.

Root Cause:
- SSR was calling cfg.Queries.SearchMediaItemsUnified directly
- API was using MediaHandler.ExecuteSearch via service layer
- Both code paths had different parameter structures

Solution:
- Use same MediaHandler.ExecuteSearch handler as API
- Build services.SearchParams struct (same as API path)
- Convert user.ID string to pgtype.UUID for service layer
- Remove unused books variable

Changes:
- Parse user.ID to UUID before building search params
- Build services.SearchParams with empty filters for SSR
- Call cfg.MediaHandler.ExecuteSearch instead of direct DB
- Use textToString helper (already exists in router package)
- Remove unused books variable declaration

Both SSR and API now use identical search logic, ensuring
consistent behavior. HTMX search continues working as before.

Fixes: Issue #1 - SSR returns 0 books on initial load
Related: Issue #2 - Search/filter returning JSON instead of HTML
This commit is contained in:
2026-03-27 15:52:14 -04:00
parent 85528396ad
commit 4dab581f33
+32 -26
View File
@@ -175,7 +175,6 @@ func registerFrontendRoutes(cfg *Config) {
}
// Fetch first page of books for SSR
var books []database.SearchMediaItemsUnifiedRow
var bookInfoList []handlers.BookInfo
totalCount := 0
limit := 50
@@ -195,42 +194,49 @@ func registerFrontendRoutes(cfg *Config) {
offset = o
}
}
books, err = cfg.Queries.SearchMediaItemsUnified(c.Request().Context(), database.SearchMediaItemsUnifiedParams{
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
AuthorFilter: pgtype.Text{String: "", Valid: true}, // Changed: Valid=true for empty string
SeriesFilter: pgtype.Text{String: "", Valid: true}, // Changed
GenreFilter: pgtype.Text{String: "", Valid: true}, // Changed
LanguageFilter: pgtype.Text{String: "", Valid: true}, // Changed
YearMin: pgtype.Int4{Int32: 0, Valid: true}, // Changed: use 0 as default
YearMax: pgtype.Int4{Int32: 0, Valid: true}, // Changed
HasCover: pgtype.Bool{Bool: false, Valid: true}, // Changed
SearchQuery: pgtype.Text{String: "", Valid: true}, // NEW: Required
IsExactSearch: pgtype.Bool{Bool: false, Valid: true}, // NEW: Required
// SearchPattern: pgtype.Text{Valid: false}, // NEW: Empty for no exact search
Sort: pgtype.Text{String: "created_at DESC", Valid: true},
Limit: pgtype.Int4{Int32: int32(limit), Valid: true},
Offset: pgtype.Int4{Int32: int32(offset), Valid: true},
})
// Convert user.ID (string) to pgtype.UUID for service layer
userUUID, err := uuid.Parse(user.ID)
if err != nil {
log.Printf("SearchMediaItemsUnified failed: %v", err)
log.Printf("Failed to parse user ID: %v", err)
return renderErrorPage(c, "Error loading user", "user_id_error")
}
// Build search params (same as search.go:76-91)
params := services.SearchParams{
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
SearchQuery: "", // Empty for initial SSR load
AuthorFilter: "",
SeriesFilter: "",
GenreFilter: "",
TagsFilter: "",
LanguageFilter: "",
YearMin: 0,
YearMax: 0,
HasCover: false,
Sort: "created_at DESC",
Limit: limit,
Offset: offset,
}
// Execute search using the same handler as API (search.go:93)
var results []database.SearchMediaItemsUnifiedRow
results, totalCount, err = cfg.MediaHandler.ExecuteSearch(c.Request().Context(), params)
if err != nil {
log.Printf("ExecuteSearch failed: %v", err)
// Continue without books - will show empty state
} else {
// Convert database rows to BookInfo structs (matching BuildSections pattern)
bookInfoList = make([]handlers.BookInfo, len(books))
for i, book := range books {
// Convert to BookInfo (same as search.go:99-109)
bookInfoList = make([]handlers.BookInfo, len(results))
for i, book := range results {
bookUUID, _ := uuid.FromBytes(book.ID.Bytes[0:16])
bookLibUUID, _ := uuid.FromBytes(book.LibraryID.Bytes[0:16])
bookInfoList[i] = handlers.BookInfo{
MediaItemID: bookUUID.String(),
Title: book.Title,
Author: getText(book.Author),
Author: textToString(book.Author),
CoverImagePath: utils.ResolveMediaURL(pgtype.UUID{Bytes: bookLibUUID, Valid: true}, book.CoverImagePath),
}
}
totalCount = len(bookInfoList)
log.Printf("SSR: fetched %d books for library %s", len(bookInfoList), libraryID)
}
}
}