feat: implement search endpoint with partial match and fuzzy fallback
- Add GET /api/media-items/search endpoint - Try partial matching first (ILIKE with wildcards) - Fallback to fuzzy search if no results found - Return 404 with 'no results found' when no matches - Limit results to 50 items by default - Supports search across title, author, series, tags, contributors - Respects library visibility settings per user
This commit is contained in:
@@ -96,6 +96,7 @@ func SetupRoutes(g *echo.Group, db *database.Queries) *Handler {
|
||||
|
||||
// Media item routes (new library system)
|
||||
g.GET("/media-items", h.ListMediaItems)
|
||||
g.GET("/media-items/search", h.SearchMediaItems)
|
||||
g.GET("/media-items/:id", h.GetMediaItem)
|
||||
g.POST("/media-items/:id/rating", h.CreateMediaRating)
|
||||
g.GET("/media-items/:id/rating", h.GetMediaRating)
|
||||
@@ -1668,3 +1669,60 @@ func (h *Handler) DeleteEbookHighlight(c echo.Context) error {
|
||||
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// SearchMediaItems handles GET /api/media-items/search
|
||||
// Performs partial matching search with fuzzy fallback if no results found
|
||||
func (h *Handler) SearchMediaItems(c echo.Context) error {
|
||||
query := c.QueryParam("q")
|
||||
userID := c.Get("user_id").(string)
|
||||
|
||||
if query == "" {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "query parameter 'q' is required"})
|
||||
}
|
||||
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
|
||||
}
|
||||
|
||||
limit := int32(50)
|
||||
offset := int32(0)
|
||||
|
||||
searchPattern := "%" + query + "%"
|
||||
|
||||
partialResults, err := h.db.SearchMediaItems(c.Request().Context(), database.SearchMediaItemsParams{
|
||||
SearchPattern: pgtype.Text{String: searchPattern, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
Limit: pgtype.Int4{Int32: limit, Valid: true},
|
||||
Offset: pgtype.Int4{Int32: offset, Valid: true},
|
||||
})
|
||||
|
||||
if err != nil && err != pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
if len(partialResults) > 0 {
|
||||
return c.JSON(http.StatusOK, partialResults)
|
||||
}
|
||||
|
||||
fuzzyResults, err := h.db.SearchMediaItemsFuzzy(c.Request().Context(), database.SearchMediaItemsFuzzyParams{
|
||||
SearchQuery: pgtype.Text{String: query, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
Limit: pgtype.Int4{Int32: limit, Valid: true},
|
||||
Offset: pgtype.Int4{Int32: offset, Valid: true},
|
||||
})
|
||||
|
||||
if err != nil && err != pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
if len(fuzzyResults) == 0 {
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{
|
||||
"error": "no results found",
|
||||
"query": query,
|
||||
"results": []interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, fuzzyResults)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user