fix: implement proper 3-state boolean handling in backend
Updated the backend services and handlers to properly detect and pass
the has_cover parameter's validity state to the database layer.
Changes:
- services/search.go: Changed HasCover type from bool to pgtype.Bool
to support 3-state logic (NULL, TRUE, FALSE)
- handlers/media.go: Fixed 3-state detection by checking if has_cover
exists in query params before setting Valid flag
- router/search.go: Fixed 3-state detection to match media.go logic
- router/frontend.go: Use pgtype.Bool{Valid: false} for SSR initial
load to ensure no filtering occurs on first page load
The key fix is detecting whether the has_cover parameter was actually
sent in the request:
- Parameter not sent → pgtype.Bool{Bool: false, Valid: false}
- Parameter sent as "true" → pgtype.Bool{Bool: true, Valid: true}
- Parameter sent as "false" → pgtype.Bool{Bool: false, Valid: true}
Previously, media.go was hardcoding Valid: true, which meant it was
always filtering by has_cover=false (only books without covers) when
the parameter wasn't sent, causing searches to incorrectly return
0 results for queries like "1984".
This ensures consistency between the JSON API endpoint (media.go) and
the HTML endpoint (search.go), and fixes the critical bug where SSR
was returning 0 books on initial page load.
This commit is contained in:
@@ -1424,7 +1424,12 @@ func (mh *MediaHandler) SearchMediaItems(c *echo.Context) error {
|
|||||||
languageFilter := c.QueryParam("language_filter")
|
languageFilter := c.QueryParam("language_filter")
|
||||||
yearMin, _ := strconv.Atoi(c.QueryParam("year_min"))
|
yearMin, _ := strconv.Atoi(c.QueryParam("year_min"))
|
||||||
yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
|
yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
|
||||||
hasCover := c.QueryParam("has_cover") == "true"
|
hasCover := false
|
||||||
|
hasCoverValid := false
|
||||||
|
if _, exists := c.QueryParams()["has_cover"]; exists {
|
||||||
|
hasCover = c.QueryParam("has_cover") == "true"
|
||||||
|
hasCoverValid = true
|
||||||
|
}
|
||||||
|
|
||||||
// Extract sort parameter
|
// Extract sort parameter
|
||||||
sortParam := c.QueryParam("sort")
|
sortParam := c.QueryParam("sort")
|
||||||
@@ -1444,7 +1449,7 @@ func (mh *MediaHandler) SearchMediaItems(c *echo.Context) error {
|
|||||||
LanguageFilter: languageFilter,
|
LanguageFilter: languageFilter,
|
||||||
YearMin: yearMin,
|
YearMin: yearMin,
|
||||||
YearMax: yearMax,
|
YearMax: yearMax,
|
||||||
HasCover: hasCover,
|
HasCover: pgtype.Bool{Bool: hasCover, Valid: hasCoverValid},
|
||||||
Sort: sortParam,
|
Sort: sortParam,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
LanguageFilter: "",
|
LanguageFilter: "",
|
||||||
YearMin: 0,
|
YearMin: 0,
|
||||||
YearMax: 0,
|
YearMax: 0,
|
||||||
HasCover: false,
|
HasCover: pgtype.Bool{Valid: false},
|
||||||
Sort: "created_at DESC",
|
Sort: "created_at DESC",
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
|
|||||||
@@ -67,7 +67,12 @@ func handleSearchHTML(c *echo.Context, cfg *Config) error {
|
|||||||
languageFilter := c.QueryParam("language_filter")
|
languageFilter := c.QueryParam("language_filter")
|
||||||
yearMin, _ := strconv.Atoi(c.QueryParam("year_min"))
|
yearMin, _ := strconv.Atoi(c.QueryParam("year_min"))
|
||||||
yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
|
yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
|
||||||
hasCover := c.QueryParam("has_cover") == "true"
|
hasCover := false
|
||||||
|
hasCoverValid := false
|
||||||
|
if _, exists := c.QueryParams()["has_cover"]; exists {
|
||||||
|
hasCover = c.QueryParam("has_cover") == "true"
|
||||||
|
hasCoverValid = true
|
||||||
|
}
|
||||||
sortParam := c.QueryParam("sort")
|
sortParam := c.QueryParam("sort")
|
||||||
if sortParam == "" {
|
if sortParam == "" {
|
||||||
sortParam = "title ASC"
|
sortParam = "title ASC"
|
||||||
@@ -84,7 +89,7 @@ func handleSearchHTML(c *echo.Context, cfg *Config) error {
|
|||||||
LanguageFilter: languageFilter,
|
LanguageFilter: languageFilter,
|
||||||
YearMin: yearMin,
|
YearMin: yearMin,
|
||||||
YearMax: yearMax,
|
YearMax: yearMax,
|
||||||
HasCover: hasCover,
|
HasCover: pgtype.Bool{Bool: hasCover, Valid: hasCoverValid},
|
||||||
Sort: sortParam,
|
Sort: sortParam,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type SearchParams struct {
|
|||||||
LanguageFilter string
|
LanguageFilter string
|
||||||
YearMin int
|
YearMin int
|
||||||
YearMax int
|
YearMax int
|
||||||
HasCover bool
|
HasCover pgtype.Bool
|
||||||
SearchQuery string
|
SearchQuery string
|
||||||
Sort string
|
Sort string
|
||||||
Limit int
|
Limit int
|
||||||
@@ -76,7 +76,7 @@ func (s *SearchService) SearchMediaItemsUnified(ctx context.Context, params Sear
|
|||||||
LanguageFilter: pgtype.Text{String: params.LanguageFilter, Valid: true},
|
LanguageFilter: pgtype.Text{String: params.LanguageFilter, Valid: true},
|
||||||
YearMin: pgtype.Int4{Int32: int32(params.YearMin), Valid: true},
|
YearMin: pgtype.Int4{Int32: int32(params.YearMin), Valid: true},
|
||||||
YearMax: pgtype.Int4{Int32: int32(params.YearMax), Valid: true},
|
YearMax: pgtype.Int4{Int32: int32(params.YearMax), Valid: true},
|
||||||
HasCover: pgtype.Bool{Bool: params.HasCover, Valid: true},
|
HasCover: params.HasCover,
|
||||||
SearchQuery: pgtype.Text{String: searchQuery, Valid: true},
|
SearchQuery: pgtype.Text{String: searchQuery, Valid: true},
|
||||||
IsExactSearch: pgtype.Bool{Bool: isExact, Valid: true},
|
IsExactSearch: pgtype.Bool{Bool: isExact, Valid: true},
|
||||||
// SearchPattern: pgtype.Text{String: searchPattern, Valid: isExact},
|
// SearchPattern: pgtype.Text{String: searchPattern, Valid: isExact},
|
||||||
|
|||||||
Reference in New Issue
Block a user