diff --git a/internal/router/search.go b/internal/router/search.go index 80489cb..2b3eb28 100644 --- a/internal/router/search.go +++ b/internal/router/search.go @@ -1,13 +1,126 @@ package router +import ( + "bookhoard/internal/database" + "bookhoard/internal/handlers" + "bookhoard/internal/services" + "bookhoard/internal/utils" + "bookhoard/templates" + "bytes" + "log" + "net/http" + "strconv" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/labstack/echo/v5" +) + func registerSearchRoutes(cfg *Config) { e := cfg.Echo - // JWT middleware for protected routes jwtMiddleware := createJWTMiddleware(cfg) protected := e.Group("/api", jwtMiddleware) + // Search endpoint - returns JSON or HTML based on HX-Request header + protected.GET("/media-items/search", func(c *echo.Context) error { + // Check if HTMX request (expects HTML) + if c.Request().Header.Get("HX-Request") == "true" { + return handleSearchHTML(c, cfg) + } - // Search and query endpoints (all authenticated users) - protected.GET("/media-items/search", cfg.MediaHandler.SearchMediaItems) + // Otherwise return JSON (API clients) + return cfg.MediaHandler.SearchMediaItems(c) + }) protected.POST("/sync/books/query", cfg.MatchingHandler.QueryBooks) } + +// handleSearchHTML renders HTML response for HTMX requests +func handleSearchHTML(c *echo.Context, cfg *Config) error { + user, ok := c.Get("user").(database.Users) + if !ok { + return c.String(http.StatusUnauthorized, "Unauthorized") + } + // Extract query parameters + query := c.QueryParam("q") + libraryID := c.QueryParam("library_id") + limit, _ := strconv.Atoi(c.QueryParam("limit")) + if limit == 0 { + limit = 50 + } + offset, _ := strconv.Atoi(c.QueryParam("offset")) + // Validate library_id + var libUUID pgtype.UUID + if libraryID != "" { + lib, err := uuid.Parse(libraryID) + if err != nil { + return c.String(http.StatusBadRequest, "invalid library_id") + } + libUUID = pgtype.UUID{Bytes: lib, Valid: true} + } else { + libUUID = pgtype.UUID{Valid: false} + } + // Extract filters + authorFilter := c.QueryParam("author_filter") + seriesFilter := c.QueryParam("series_filter") + genreFilter := c.QueryParam("genre_filter") + tagsFilter := c.QueryParam("tags_filter") + languageFilter := c.QueryParam("language_filter") + yearMin, _ := strconv.Atoi(c.QueryParam("year_min")) + yearMax, _ := strconv.Atoi(c.QueryParam("year_max")) + hasCover := c.QueryParam("has_cover") == "true" + sortParam := c.QueryParam("sort") + if sortParam == "" { + sortParam = "title ASC" + } + // Build search params + params := services.SearchParams{ + UserID: user.ID, + LibraryID: libUUID, + SearchQuery: query, + AuthorFilter: authorFilter, + SeriesFilter: seriesFilter, + GenreFilter: genreFilter, + TagsFilter: tagsFilter, + LanguageFilter: languageFilter, + YearMin: yearMin, + YearMax: yearMax, + HasCover: hasCover, + Sort: sortParam, + Limit: limit, + Offset: offset, + } + // Execute search using handler method + results, totalCount, err := cfg.MediaHandler.ExecuteSearch(c.Request().Context(), params) + if err != nil { + log.Printf("Search error: %v", err) + return c.HTML(http.StatusInternalServerError, `