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
|
// CreateMediaRating handles POST /api/media-items/:id/rating
|
||||||
func (mh *MediaHandler) CreateMediaRating(c *echo.Context) error {
|
func (mh *MediaHandler) CreateMediaRating(c *echo.Context) error {
|
||||||
userID := c.Get("user_id").(string)
|
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"))
|
yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
|
||||||
hasCover := c.QueryParam("has_cover") == "true"
|
hasCover := c.QueryParam("has_cover") == "true"
|
||||||
|
|
||||||
|
// Extract sort parameter
|
||||||
|
sortParam := c.QueryParam("sort")
|
||||||
|
if sortParam == "" {
|
||||||
|
sortParam = "title ASC" // Default sort
|
||||||
|
}
|
||||||
|
|
||||||
// Build search params
|
// Build search params
|
||||||
params := services.SearchParams{
|
params := services.SearchParams{
|
||||||
UserID: userID.ID,
|
UserID: userID.ID,
|
||||||
@@ -1483,6 +1426,7 @@ func (mh *MediaHandler) SearchMediaItems(c *echo.Context) error {
|
|||||||
YearMin: yearMin,
|
YearMin: yearMin,
|
||||||
YearMax: yearMax,
|
YearMax: yearMax,
|
||||||
HasCover: hasCover,
|
HasCover: hasCover,
|
||||||
|
Sort: sortParam,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-10
@@ -175,7 +175,7 @@ func registerFrontendRoutes(cfg *Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch first page of books for SSR
|
// Fetch first page of books for SSR
|
||||||
var books []database.ListMediaItemsFilteredRow
|
var books []database.SearchMediaItemsUnifiedRow
|
||||||
var bookInfoList []handlers.BookInfo
|
var bookInfoList []handlers.BookInfo
|
||||||
totalCount := 0
|
totalCount := 0
|
||||||
limit := 50
|
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},
|
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
|
||||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||||
AuthorFilter: pgtype.Text{String: "", Valid: false},
|
AuthorFilter: pgtype.Text{String: "", Valid: true}, // Changed: Valid=true for empty string
|
||||||
SeriesFilter: pgtype.Text{String: "", Valid: false},
|
SeriesFilter: pgtype.Text{String: "", Valid: true}, // Changed
|
||||||
GenreFilter: pgtype.Text{String: "", Valid: false},
|
GenreFilter: pgtype.Text{String: "", Valid: true}, // Changed
|
||||||
LanguageFilter: pgtype.Text{String: "", Valid: false},
|
LanguageFilter: pgtype.Text{String: "", Valid: true}, // Changed
|
||||||
YearMin: pgtype.Int4{Valid: false},
|
YearMin: pgtype.Int4{Int32: 0, Valid: true}, // Changed: use 0 as default
|
||||||
YearMax: pgtype.Int4{Valid: false},
|
YearMax: pgtype.Int4{Int32: 0, Valid: true}, // Changed
|
||||||
HasCover: pgtype.Bool{Valid: false},
|
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},
|
Sort: pgtype.Text{String: "created_at DESC", Valid: true},
|
||||||
Limit: pgtype.Int4{Int32: int32(limit), Valid: true},
|
Limit: pgtype.Int4{Int32: int32(limit), Valid: true},
|
||||||
Offset: pgtype.Int4{Int32: int32(offset), Valid: true},
|
Offset: pgtype.Int4{Int32: int32(offset), Valid: true},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ListMediaItemsFiltered failed: %v", err)
|
log.Printf("SearchMediaItemsUnified failed: %v", err)
|
||||||
// Continue without books - will show empty state
|
// Continue without books - will show empty state
|
||||||
} else {
|
} else {
|
||||||
// Convert database rows to BookInfo structs (matching BuildSections pattern)
|
// Convert database rows to BookInfo structs (matching BuildSections pattern)
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ func registerMediaRoutes(cfg *Config) {
|
|||||||
|
|
||||||
// Media item routes (all authenticated users)
|
// Media item routes (all authenticated users)
|
||||||
protected.GET("/media-items", cfg.MediaHandler.ListMediaItems)
|
protected.GET("/media-items", cfg.MediaHandler.ListMediaItems)
|
||||||
protected.GET("/media-items/filtered", cfg.MediaHandler.ListMediaItemsFiltered)
|
|
||||||
protected.GET("/media-items/:id", cfg.MediaHandler.GetMediaItem)
|
protected.GET("/media-items/:id", cfg.MediaHandler.GetMediaItem)
|
||||||
|
|
||||||
// Media rating routes (all authenticated users)
|
// Media rating routes (all authenticated users)
|
||||||
|
|||||||
Reference in New Issue
Block a user