refactor: update handlers to use unified search endpoint
- Update SearchMediaItems handler to use SearchService - Add autocomplete detection for field value queries (author=value, genre=value, etc.) - Add handleFieldValuesSearch method for dropdown population - Add sort parameter extraction with default "title ASC" - Remove deprecated ListMediaItemsFiltered handler - Remove deprecated /api/media-items/filtered route registration - Update frontend.go to use SearchMediaItemsUnified instead of ListMediaItemsFiltered - Fix parameter passing (empty filters use Valid:true with empty values, not Valid:false) - Add SearchQuery, IsExactSearch, SearchPattern parameters for query parsing Handler is now a thin wrapper that extracts params and delegates to service layer.
This commit is contained in:
@@ -704,69 +704,6 @@ func (mh *MediaHandler) GetMediaItem(c *echo.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
// ListMediaItemsFiltered handles GET /api/media-items/filtered
|
||||
func (mh *MediaHandler) ListMediaItemsFiltered(c *echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
libraryID := c.QueryParam("library_id")
|
||||
sort := c.QueryParam("sort")
|
||||
limit, _ := strconv.Atoi(c.QueryParam("limit"))
|
||||
offset, _ := strconv.Atoi(c.QueryParam("offset"))
|
||||
|
||||
authorFilter := c.QueryParam("author_filter")
|
||||
seriesFilter := c.QueryParam("series_filter")
|
||||
genreFilter := c.QueryParam("genre_filter")
|
||||
languageFilter := c.QueryParam("language_filter")
|
||||
yearMin, _ := strconv.Atoi(c.QueryParam("year_min"))
|
||||
yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
|
||||
hasCover, _ := strconv.ParseBool(c.QueryParam("has_cover"))
|
||||
|
||||
if limit == 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
if sort == "" {
|
||||
sort = "created_at DESC"
|
||||
}
|
||||
|
||||
if limit > maxPaginationLimit {
|
||||
limit = maxPaginationLimit
|
||||
}
|
||||
|
||||
libUUID, err := uuid.Parse(libraryID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
||||
}
|
||||
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
|
||||
}
|
||||
|
||||
items, err := mh.db.ListMediaItemsFiltered(c.Request().Context(), database.ListMediaItemsFilteredParams{
|
||||
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
AuthorFilter: pgtype.Text{String: authorFilter, Valid: true},
|
||||
SeriesFilter: pgtype.Text{String: seriesFilter, Valid: true},
|
||||
GenreFilter: pgtype.Text{String: genreFilter, Valid: true},
|
||||
LanguageFilter: pgtype.Text{String: languageFilter, Valid: true},
|
||||
YearMin: pgtype.Int4{Int32: int32(yearMin), Valid: true},
|
||||
YearMax: pgtype.Int4{Int32: int32(yearMax), Valid: true},
|
||||
HasCover: pgtype.Bool{Bool: hasCover, Valid: true},
|
||||
Sort: pgtype.Text{String: sort, Valid: true},
|
||||
Limit: pgtype.Int4{Int32: int32(limit), Valid: true},
|
||||
Offset: pgtype.Int4{Int32: int32(offset), Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": items})
|
||||
}
|
||||
|
||||
// CreateMediaRating handles POST /api/media-items/:id/rating
|
||||
func (mh *MediaHandler) CreateMediaRating(c *echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
@@ -1471,6 +1408,12 @@ func (mh *MediaHandler) SearchMediaItems(c *echo.Context) error {
|
||||
yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
|
||||
hasCover := c.QueryParam("has_cover") == "true"
|
||||
|
||||
// Extract sort parameter
|
||||
sortParam := c.QueryParam("sort")
|
||||
if sortParam == "" {
|
||||
sortParam = "title ASC" // Default sort
|
||||
}
|
||||
|
||||
// Build search params
|
||||
params := services.SearchParams{
|
||||
UserID: userID.ID,
|
||||
@@ -1483,6 +1426,7 @@ func (mh *MediaHandler) SearchMediaItems(c *echo.Context) error {
|
||||
YearMin: yearMin,
|
||||
YearMax: yearMax,
|
||||
HasCover: hasCover,
|
||||
Sort: sortParam,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}
|
||||
|
||||
+13
-10
@@ -175,7 +175,7 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
}
|
||||
|
||||
// Fetch first page of books for SSR
|
||||
var books []database.ListMediaItemsFilteredRow
|
||||
var books []database.SearchMediaItemsUnifiedRow
|
||||
var bookInfoList []handlers.BookInfo
|
||||
totalCount := 0
|
||||
limit := 50
|
||||
@@ -196,23 +196,26 @@ func registerFrontendRoutes(cfg *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
books, err = cfg.Queries.ListMediaItemsFiltered(c.Request().Context(), database.ListMediaItemsFilteredParams{
|
||||
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: false},
|
||||
SeriesFilter: pgtype.Text{String: "", Valid: false},
|
||||
GenreFilter: pgtype.Text{String: "", Valid: false},
|
||||
LanguageFilter: pgtype.Text{String: "", Valid: false},
|
||||
YearMin: pgtype.Int4{Valid: false},
|
||||
YearMax: pgtype.Int4{Valid: false},
|
||||
HasCover: pgtype.Bool{Valid: false},
|
||||
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},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("ListMediaItemsFiltered failed: %v", err)
|
||||
log.Printf("SearchMediaItemsUnified failed: %v", err)
|
||||
// Continue without books - will show empty state
|
||||
} else {
|
||||
// Convert database rows to BookInfo structs (matching BuildSections pattern)
|
||||
|
||||
@@ -14,7 +14,6 @@ func registerMediaRoutes(cfg *Config) {
|
||||
|
||||
// Media item routes (all authenticated users)
|
||||
protected.GET("/media-items", cfg.MediaHandler.ListMediaItems)
|
||||
protected.GET("/media-items/filtered", cfg.MediaHandler.ListMediaItemsFiltered)
|
||||
protected.GET("/media-items/:id", cfg.MediaHandler.GetMediaItem)
|
||||
|
||||
// Media rating routes (all authenticated users)
|
||||
|
||||
Reference in New Issue
Block a user