diff --git a/internal/jikan/anime.go b/internal/jikan/anime.go index f794496..cb6f5fd 100644 --- a/internal/jikan/anime.go +++ b/internal/jikan/anime.go @@ -8,21 +8,20 @@ import ( func (c *Client) GetAnimeByID(ctx context.Context, id int) (Anime, error) { cacheKey := fmt.Sprintf("anime:%d", id) + var cached Anime if c.getCache(ctx, cacheKey, &cached) { return cached, nil } - var stale Anime - hasStale := c.getStaleCache(ctx, cacheKey, &stale) - var result AnimeResponse reqURL := fmt.Sprintf("%s/anime/%d/full", c.baseURL, id) + if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { - if hasStale { + var stale Anime + if c.getStaleCache(ctx, cacheKey, &stale) { return stale, nil } - return Anime{}, err } @@ -33,4 +32,4 @@ func (c *Client) GetAnimeByID(ctx context.Context, id int) (Anime, error) { c.setCache(ctx, cacheKey, result.Data, ttl) return result.Data, nil -} +} \ No newline at end of file diff --git a/internal/jikan/client.go b/internal/jikan/client.go index e1773fa..863ef0e 100644 --- a/internal/jikan/client.go +++ b/internal/jikan/client.go @@ -226,6 +226,32 @@ func (c *Client) setCache(parentCtx context.Context, key string, data any, ttl t }) } +type cacheResult struct { + data any + hasStale bool +} + +func (c *Client) getWithCache(ctx context.Context, cacheKey string, ttl time.Duration, url string, out any) error { + if c.getCache(ctx, cacheKey, out) { + return nil + } + + var stale any + hasStale := c.getStaleCache(ctx, cacheKey, &stale) + + if err := c.fetchWithRetry(ctx, url, out); err != nil { + if hasStale { + staleBytes, _ := json.Marshal(stale) + json.Unmarshal(staleBytes, out) + return nil + } + return err + } + + c.setCache(ctx, cacheKey, out, ttl) + return nil +} + func (c *Client) fetchWithRetry(ctx context.Context, urlStr string, out any) error { maxRetries := 5 for attempt := range maxRetries { diff --git a/internal/jikan/episodes.go b/internal/jikan/episodes.go index 5ee7b85..9b48579 100644 --- a/internal/jikan/episodes.go +++ b/internal/jikan/episodes.go @@ -12,24 +12,9 @@ func (c *Client) GetEpisodes(ctx context.Context, animeID int, page int) (Episod } cacheKey := fmt.Sprintf("anime:%d:episodes:%d", animeID, page) - var cached EpisodesResponse - if c.getCache(ctx, cacheKey, &cached) { - return cached, nil - } - - var stale EpisodesResponse - hasStale := c.getStaleCache(ctx, cacheKey, &stale) - var result EpisodesResponse reqURL := fmt.Sprintf("%s/anime/%d/episodes?page=%d", c.baseURL, animeID, page) - if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { - if hasStale { - return stale, nil - } - return EpisodesResponse{}, err - } - - c.setCache(ctx, cacheKey, result, 12*time.Hour) - return result, nil -} + err := c.getWithCache(ctx, cacheKey, 12*time.Hour, reqURL, &result) + return result, err +} \ No newline at end of file diff --git a/internal/jikan/recommendations.go b/internal/jikan/recommendations.go index d17f709..c320c81 100644 --- a/internal/jikan/recommendations.go +++ b/internal/jikan/recommendations.go @@ -26,6 +26,7 @@ type RecommendationsResponse struct { func (c *Client) GetRecommendations(ctx context.Context, animeID int, limit int) ([]Anime, error) { cacheKey := fmt.Sprintf("recs:%d", animeID) + var cached []Anime if c.getCache(ctx, cacheKey, &cached) { if limit > 0 && len(cached) > limit { @@ -34,19 +35,17 @@ func (c *Client) GetRecommendations(ctx context.Context, animeID int, limit int) return cached, nil } - var stale []Anime - hasStale := c.getStaleCache(ctx, cacheKey, &stale) - var result RecommendationsResponse reqURL := fmt.Sprintf("%s/anime/%d/recommendations", c.baseURL, animeID) + if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { - if hasStale { + var stale []Anime + if c.getStaleCache(ctx, cacheKey, &stale) { if limit > 0 && len(stale) > limit { return stale[:limit], nil } return stale, nil } - return nil, err } @@ -59,15 +58,12 @@ func (c *Client) GetRecommendations(ctx context.Context, animeID int, limit int) for i := 0; i < max; i++ { rec := result.Data[i] - // Try to see if we already have the full anime details in our local cache. - // If we do, we can use it to get the English title without making an API call! var fullAnime Anime animeCacheKey := fmt.Sprintf("anime:%d", rec.Entry.MalID) if c.getCache(ctx, animeCacheKey, &fullAnime) { animes = append(animes, fullAnime) } else { - // Otherwise, map the basic recommendation data directly into an Anime struct. anime := Anime{ MalID: rec.Entry.MalID, Title: rec.Entry.Title, @@ -92,4 +88,4 @@ func (c *Client) GetRecommendations(ctx context.Context, animeID int, limit int) c.setCache(ctx, cacheKey, animes, time.Hour*24) return animes, nil -} +} \ No newline at end of file diff --git a/internal/jikan/search.go b/internal/jikan/search.go index ce01070..f56411f 100644 --- a/internal/jikan/search.go +++ b/internal/jikan/search.go @@ -26,13 +26,6 @@ func (c *Client) search(ctx context.Context, query string, page int, limit int) } cacheKey := fmt.Sprintf("search:%s:%d:%d", query, page, limit) - var cached SearchResult - if c.getCache(ctx, cacheKey, &cached) { - return cached, nil - } - - var stale SearchResult - hasStale := c.getStaleCache(ctx, cacheKey, &stale) var result SearchResponse reqURL := fmt.Sprintf("%s/anime?q=%s&page=%d", c.baseURL, url.QueryEscape(query), page) @@ -40,7 +33,7 @@ func (c *Client) search(ctx context.Context, query string, page int, limit int) reqURL = fmt.Sprintf("%s&limit=%d", reqURL, limit) } - if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { + if err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result); err != nil { if limit > 0 && IsRetryableError(err) { fallbackURL := fmt.Sprintf("%s/anime?q=%s&page=%d", c.baseURL, url.QueryEscape(query), page) if fallbackErr := c.fetchWithRetry(ctx, fallbackURL, &result); fallbackErr == nil { @@ -53,20 +46,18 @@ func (c *Client) search(ctx context.Context, query string, page int, limit int) } } - if hasStale { + var stale SearchResult + if c.getStaleCache(ctx, cacheKey, &stale) { return stale, nil } return SearchResult{}, err } - res := SearchResult{ + return SearchResult{ Animes: result.Data, HasNextPage: result.Pagination.HasNextPage, - } - - c.setCache(ctx, cacheKey, res, shortCacheTTL) - return res, nil + }, nil } func (c *Client) GetTopAnime(ctx context.Context, page int) (TopAnimeResult, error) { @@ -74,30 +65,20 @@ func (c *Client) GetTopAnime(ctx context.Context, page int) (TopAnimeResult, err page = 1 } cacheKey := fmt.Sprintf("top:%d", page) - var cached TopAnimeResult - if c.getCache(ctx, cacheKey, &cached) { - return cached, nil - } - - var stale TopAnimeResult - hasStale := c.getStaleCache(ctx, cacheKey, &stale) var result TopAnimeResponse reqURL := fmt.Sprintf("%s/top/anime?page=%d", c.baseURL, page) - if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { - if hasStale { + if err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result); err != nil { + var stale TopAnimeResult + if c.getStaleCache(ctx, cacheKey, &stale) { return stale, nil } - return TopAnimeResult{}, err } - res := TopAnimeResult{ + return TopAnimeResult{ Animes: result.Data, HasNextPage: result.Pagination.HasNextPage, - } - - c.setCache(ctx, cacheKey, res, shortCacheTTL) - return res, nil -} + }, nil +} \ No newline at end of file diff --git a/internal/jikan/seasons.go b/internal/jikan/seasons.go index aaf89c2..3ed6991 100644 --- a/internal/jikan/seasons.go +++ b/internal/jikan/seasons.go @@ -15,31 +15,18 @@ func (c *Client) GetSchedule(ctx context.Context, day string) (ScheduleResult, e day = strings.ToLower(day) cacheKey := fmt.Sprintf("schedule_%s", day) - var cached ScheduleResult - if c.getCache(ctx, cacheKey, &cached) { - return cached, nil - } - - var stale ScheduleResult - hasStale := c.getStaleCache(ctx, cacheKey, &stale) - var result TopAnimeResponse reqURL := fmt.Sprintf("%s/schedules?filter=%s&sfw=true", c.baseURL, day) - if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { - if hasStale { - return stale, nil - } + err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result) + if err != nil { return ScheduleResult{}, err } - res := ScheduleResult{ + return ScheduleResult{ Animes: result.Data, HasNextPage: result.Pagination.HasNextPage, - } - - c.setCache(ctx, cacheKey, res, shortCacheTTL) - return res, nil + }, nil } func (c *Client) GetFullSchedule(ctx context.Context) (map[string][]Anime, error) { @@ -62,31 +49,19 @@ func (c *Client) GetSeasonsNow(ctx context.Context, page int) (TopAnimeResult, e page = 1 } cacheKey := fmt.Sprintf("seasons_now:%d", page) - var cached TopAnimeResult - if c.getCache(ctx, cacheKey, &cached) { - return cached, nil - } - - var stale TopAnimeResult - hasStale := c.getStaleCache(ctx, cacheKey, &stale) var result TopAnimeResponse reqURL := fmt.Sprintf("%s/seasons/now?page=%d", c.baseURL, page) - if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { - if hasStale { - return stale, nil - } + err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result) + if err != nil { return TopAnimeResult{}, err } - res := TopAnimeResult{ + return TopAnimeResult{ Animes: result.Data, HasNextPage: result.Pagination.HasNextPage, - } - - c.setCache(ctx, cacheKey, res, shortCacheTTL) - return res, nil + }, nil } func (c *Client) GetSeasonsUpcoming(ctx context.Context, page int) (TopAnimeResult, error) { @@ -94,29 +69,17 @@ func (c *Client) GetSeasonsUpcoming(ctx context.Context, page int) (TopAnimeResu page = 1 } cacheKey := fmt.Sprintf("seasons_upcoming:%d", page) - var cached TopAnimeResult - if c.getCache(ctx, cacheKey, &cached) { - return cached, nil - } - - var stale TopAnimeResult - hasStale := c.getStaleCache(ctx, cacheKey, &stale) var result TopAnimeResponse reqURL := fmt.Sprintf("%s/seasons/upcoming?page=%d", c.baseURL, page) - if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { - if hasStale { - return stale, nil - } + err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result) + if err != nil { return TopAnimeResult{}, err } - res := TopAnimeResult{ + return TopAnimeResult{ Animes: result.Data, HasNextPage: result.Pagination.HasNextPage, - } - - c.setCache(ctx, cacheKey, res, shortCacheTTL) - return res, nil -} + }, nil +} \ No newline at end of file diff --git a/internal/jikan/studio.go b/internal/jikan/studio.go index f27ff53..bca7673 100644 --- a/internal/jikan/studio.go +++ b/internal/jikan/studio.go @@ -34,6 +34,7 @@ func (c *Client) GetAnimeByProducer(ctx context.Context, producerID int, page in } cacheKey := fmt.Sprintf("producer:%d:%d", producerID, page) + var cached StudioAnimeResult if c.getCache(ctx, cacheKey, &cached) { return cached, nil @@ -49,11 +50,9 @@ func (c *Client) GetAnimeByProducer(ctx context.Context, producerID int, page in if hasStale { return stale, nil } - return StudioAnimeResult{}, err } - // Get producer info for the name producerName := "" var producerRes ProducerResponse producerURL := fmt.Sprintf("%s/producers/%d", c.baseURL, producerID) @@ -78,25 +77,10 @@ func (c *Client) GetAnimeByProducer(ctx context.Context, producerID int, page in func (c *Client) GetProducerByID(ctx context.Context, producerID int) (ProducerResponse, error) { cacheKey := fmt.Sprintf("producer:info:%d", producerID) - var cached ProducerResponse - if c.getCache(ctx, cacheKey, &cached) { - return cached, nil - } - - var stale ProducerResponse - hasStale := c.getStaleCache(ctx, cacheKey, &stale) var result ProducerResponse reqURL := fmt.Sprintf("%s/producers/%d/full", c.baseURL, producerID) - if err := c.fetchWithRetry(ctx, reqURL, &result); err != nil { - if hasStale { - return stale, nil - } - - return ProducerResponse{}, err - } - - c.setCache(ctx, cacheKey, result, shortCacheTTL) - return result, nil -} + err := c.getWithCache(ctx, cacheKey, shortCacheTTL, reqURL, &result) + return result, err +} \ No newline at end of file