feat: add genres filter to browse page

This commit is contained in:
2026-05-02 16:08:46 +02:00
committed by Mikkel Elvers
parent 4869bc055d
commit 056b5ad93e
4 changed files with 57 additions and 5 deletions

View File

@@ -119,11 +119,24 @@ func (h *Handler) HandleBrowse(w http.ResponseWriter, r *http.Request) {
orderBy := r.URL.Query().Get("order_by") orderBy := r.URL.Query().Get("order_by")
sort := r.URL.Query().Get("sort") sort := r.URL.Query().Get("sort")
res, err := h.jikanClient.SearchAdvanced(r.Context(), q, animeType, status, orderBy, sort, 1, 24) var genres []int
for _, g := range r.URL.Query()["genres"] {
id, err := strconv.Atoi(g)
if err == nil {
genres = append(genres, id)
}
}
res, err := h.jikanClient.SearchAdvanced(r.Context(), q, animeType, status, orderBy, sort, genres, 1, 24)
if err != nil { if err != nil {
log.Printf("browse error: %v", err) log.Printf("browse error: %v", err)
} }
genresList, err := h.jikanClient.GetAnimeGenres(r.Context())
if err != nil {
log.Printf("genres error: %v", err)
}
watchlistMap := make(map[int64]bool) watchlistMap := make(map[int64]bool)
var watchlistIDs []int64 var watchlistIDs []int64
if user != nil { if user != nil {
@@ -137,12 +150,14 @@ func (h *Handler) HandleBrowse(w http.ResponseWriter, r *http.Request) {
if err := templates.GetRenderer().ExecuteTemplate(w, "browse.gohtml", map[string]any{ if err := templates.GetRenderer().ExecuteTemplate(w, "browse.gohtml", map[string]any{
"User": user, "User": user,
"CurrentPath": r.URL.Path, "CurrentPath": r.URL.Path,
"Query": q, "Query": q,
"Type": animeType, "Type": animeType,
"Status": status, "Status": status,
"OrderBy": orderBy, "OrderBy": orderBy,
"Sort": sort, "Sort": sort,
"Genres": genres,
"GenresList": genresList,
"Animes": res.Animes, "Animes": res.Animes,
"WatchlistMap": watchlistMap, "WatchlistMap": watchlistMap,
"WatchlistIDs": watchlistIDs, "WatchlistIDs": watchlistIDs,
@@ -255,7 +270,7 @@ func (h *Handler) HandleQuickSearch(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode([]quickSearchResult{}) json.NewEncoder(w).Encode([]quickSearchResult{})
return return
} }
res, err := h.jikanClient.SearchAdvanced(r.Context(), query, "", "", "", "", 1, 5) res, err := h.jikanClient.SearchAdvanced(r.Context(), query, "", "", "", "", nil, 1, 5)
if err != nil { if err != nil {
log.Printf("quick search error: %v", err) log.Printf("quick search error: %v", err)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)

View File

@@ -3,3 +3,4 @@ package jikan
import "time" import "time"
const shortCacheTTL = time.Hour const shortCacheTTL = time.Hour
const longCacheTTL = time.Hour * 24

View File

@@ -4,13 +4,15 @@ import (
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"strconv"
"strings"
) )
func (c *Client) Search(ctx context.Context, query string, page int) (SearchResult, error) { func (c *Client) Search(ctx context.Context, query string, page int) (SearchResult, error) {
return c.search(ctx, query, page, 0) return c.search(ctx, query, page, 0)
} }
func (c *Client) SearchAdvanced(ctx context.Context, query, animeType, status, orderBy, sort string, page, limit int) (SearchResult, error) { func (c *Client) SearchAdvanced(ctx context.Context, query, animeType, status, orderBy, sort string, genres []int, page, limit int) (SearchResult, error) {
if page < 1 { if page < 1 {
page = 1 page = 1
} }
@@ -18,7 +20,16 @@ func (c *Client) SearchAdvanced(ctx context.Context, query, animeType, status, o
limit = 0 limit = 0
} }
cacheKey := fmt.Sprintf("search:%s:%s:%s:%s:%s:%d:%d", query, animeType, status, orderBy, sort, page, limit) genresParam := ""
if len(genres) > 0 {
ids := make([]string, len(genres))
for i, g := range genres {
ids[i] = strconv.Itoa(g)
}
genresParam = strings.Join(ids, ",")
}
cacheKey := fmt.Sprintf("search:%s:%s:%s:%s:%s:%s:%d:%d", query, animeType, status, orderBy, sort, genresParam, page, limit)
var result SearchResponse var result SearchResponse
reqURL := fmt.Sprintf("%s/anime?page=%d", c.baseURL, page) reqURL := fmt.Sprintf("%s/anime?page=%d", c.baseURL, page)
@@ -37,6 +48,9 @@ func (c *Client) SearchAdvanced(ctx context.Context, query, animeType, status, o
if sort != "" { if sort != "" {
reqURL += "&sort=" + url.QueryEscape(sort) reqURL += "&sort=" + url.QueryEscape(sort)
} }
if genresParam != "" {
reqURL += "&genres=" + genresParam
}
if limit > 0 { if limit > 0 {
reqURL += fmt.Sprintf("&limit=%d", limit) reqURL += fmt.Sprintf("&limit=%d", limit)
} }
@@ -127,3 +141,16 @@ func (c *Client) GetTopAnime(ctx context.Context, page int) (TopAnimeResult, err
HasNextPage: result.Pagination.HasNextPage, HasNextPage: result.Pagination.HasNextPage,
}, nil }, nil
} }
func (c *Client) GetAnimeGenres(ctx context.Context) ([]Genre, error) {
const cacheKey = "anime_genres"
var result GenresResponse
reqURL := fmt.Sprintf("%s/genres/anime", c.baseURL)
if err := c.getWithCache(ctx, cacheKey, longCacheTTL, reqURL, &result); err != nil {
return nil, err
}
return result.Data, nil
}

View File

@@ -143,6 +143,15 @@ type AnimeResponse struct {
Data Anime `json:"data"` Data Anime `json:"data"`
} }
type Genre struct {
MalID int `json:"mal_id"`
Name string `json:"name"`
}
type GenresResponse struct {
Data []Genre `json:"data"`
}
type SearchResponse struct { type SearchResponse struct {
Data []Anime `json:"data"` Data []Anime `json:"data"`
Pagination Pagination `json:"pagination"` Pagination Pagination `json:"pagination"`