diff --git a/internal/handlers/ebook.go b/internal/handlers/ebook.go index bd4a44a..a129488 100644 --- a/internal/handlers/ebook.go +++ b/internal/handlers/ebook.go @@ -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) +}