diff --git a/internal/anime/search_api.go b/internal/anime/search_api.go new file mode 100644 index 0000000..a104eaa --- /dev/null +++ b/internal/anime/search_api.go @@ -0,0 +1,80 @@ +package anime + +import ( + "fmt" + "mal/internal/server" + "net/http" + "strconv" + "strings" + + "github.com/gin-gonic/gin" +) + +const searchAnimeLimit = 24 + +type searchItem struct { + ID string `json:"id"` + Type string `json:"type"` + Label string `json:"label"` + Subtitle string `json:"subtitle"` + Href string `json:"href"` + Image string `json:"image,omitempty"` + Icon string `json:"icon,omitempty"` + InWatchlist bool `json:"inWatchlist,omitempty"` +} + +type searchResponse struct { + Items []searchItem `json:"items"` + HasNextPage bool `json:"hasNextPage"` + NextPage int `json:"nextPage,omitempty"` +} + +func (h *AnimeHandler) HandleSearchAPI(c *gin.Context) { + user := server.CurrentUser(c) + if user == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + return + } + + query := strings.TrimSpace(c.Query("q")) + page, err := strconv.Atoi(c.DefaultQuery("page", "1")) + if err != nil || page < 1 { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid page"}) + return + } + + if query == "" || len(query) < 2 { + c.JSON(http.StatusOK, searchResponse{}) + return + } + + items, hasNextPage := h.searchAnimeResults(c, user.ID, query, page) + c.JSON(http.StatusOK, searchResponse{ + Items: items, + HasNextPage: hasNextPage, + NextPage: page + 1, + }) +} + +func (h *AnimeHandler) searchAnimeResults(c *gin.Context, userID string, query string, page int) ([]searchItem, bool) { + res, err := h.svc.SearchAdvanced(c.Request.Context(), query, "", "", "", "", nil, 0, true, page, searchAnimeLimit) + if err != nil { + return nil, false + } + + animes := wrapAnimes(res.Animes) + watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, animes) + items := make([]searchItem, 0, len(animes)) + for _, anime := range animes { + items = append(items, searchItem{ + ID: fmt.Sprintf("anime:%d", anime.MalID), + Type: "anime", + Label: anime.DisplayTitle(), + Subtitle: strings.TrimSpace("Anime " + anime.Type), + Href: fmt.Sprintf("/anime/%d", anime.MalID), + Image: anime.ImageURL(), + InWatchlist: watchlistMap[int64(anime.MalID)], + }) + } + return items, res.HasNextPage +}