refactor: share episode cache decoding

This commit is contained in:
2026-06-21 17:18:36 +02:00
committed by Milas Holsting
parent a1aa5d2540
commit 66cd131756

View File

@@ -132,41 +132,7 @@ func (s *EpisodeService) markFailure(ctx context.Context, anime domain.Anime, ca
} }
func (s *EpisodeService) getCached(ctx context.Context, anime domain.Anime) (domain.CanonicalEpisodeList, bool) { func (s *EpisodeService) getCached(ctx context.Context, anime domain.Anime) (domain.CanonicalEpisodeList, bool) {
row, err := s.queries.GetEpisodeAvailabilityCache(ctx, int64(anime.MalID)) return s.getDecodedCached(ctx, anime, "episode_availability")
if err != nil {
s.metrics.ObserveCache("episode_availability", "miss")
return domain.CanonicalEpisodeList{}, false
}
var payload domain.CanonicalEpisodeList
if err := json.Unmarshal([]byte(row.Data), &payload); err != nil {
s.metrics.ObserveCache("episode_availability", "miss")
observability.Warn(
"episodes_cached_payload_invalid",
"episodes",
"",
map[string]any{
"anime_id": anime.MalID,
},
err,
)
return domain.CanonicalEpisodeList{}, false
}
if !isCanonicalEpisodePayloadValid(payload, anime.Episodes) {
s.metrics.ObserveCache("episode_availability", "miss")
observability.Info(
"episodes_cached_payload_rejected",
"episodes",
"",
map[string]any{
"anime_id": anime.MalID,
"expected_count": anime.Episodes,
"cached_episodes": len(payload.Episodes),
},
)
return domain.CanonicalEpisodeList{}, false
}
s.metrics.ObserveCache("episode_availability", "hit")
return payload, true
} }
func (s *EpisodeService) getFreshCached(ctx context.Context, anime domain.Anime) (domain.CanonicalEpisodeList, bool) { func (s *EpisodeService) getFreshCached(ctx context.Context, anime domain.Anime) (domain.CanonicalEpisodeList, bool) {
@@ -181,24 +147,10 @@ func (s *EpisodeService) getFreshCached(ctx context.Context, anime domain.Anime)
return domain.CanonicalEpisodeList{}, false return domain.CanonicalEpisodeList{}, false
} }
payload, ok := s.decodeFreshCachedPayload(anime, row.Data) payload, ok := s.decodeCachedPayload(anime, row.Data, "episode_availability_fresh")
if !ok { if !ok {
return domain.CanonicalEpisodeList{}, false return domain.CanonicalEpisodeList{}, false
} }
if !isCanonicalEpisodePayloadValid(payload, anime.Episodes) {
s.metrics.ObserveCache("episode_availability_fresh", "miss")
observability.Info(
"episodes_cached_payload_rejected",
"episodes",
"",
map[string]any{
"anime_id": anime.MalID,
"expected_count": anime.Episodes,
"cached_episodes": len(payload.Episodes),
},
)
return domain.CanonicalEpisodeList{}, false
}
s.metrics.ObserveCache("episode_availability_fresh", "hit") s.metrics.ObserveCache("episode_availability_fresh", "hit")
observability.Info( observability.Info(
"episodes_cache_served", "episodes_cache_served",
@@ -213,6 +165,20 @@ func (s *EpisodeService) getFreshCached(ctx context.Context, anime domain.Anime)
return payload, true return payload, true
} }
func (s *EpisodeService) getDecodedCached(ctx context.Context, anime domain.Anime, metric string) (domain.CanonicalEpisodeList, bool) {
row, err := s.queries.GetEpisodeAvailabilityCache(ctx, int64(anime.MalID))
if err != nil {
s.metrics.ObserveCache(metric, "miss")
return domain.CanonicalEpisodeList{}, false
}
payload, ok := s.decodeCachedPayload(anime, row.Data, metric)
if !ok {
return domain.CanonicalEpisodeList{}, false
}
s.metrics.ObserveCache(metric, "hit")
return payload, true
}
func (s *EpisodeService) isFreshEpisodeCache(anime domain.Anime, row db.EpisodeAvailabilityCache, now time.Time) bool { func (s *EpisodeService) isFreshEpisodeCache(anime domain.Anime, row db.EpisodeAvailabilityCache, now time.Time) bool {
if row.NextRefreshAt.Valid && !row.NextRefreshAt.Time.After(now) { if row.NextRefreshAt.Valid && !row.NextRefreshAt.Time.After(now) {
s.metrics.ObserveCache("episode_availability_fresh", "miss") s.metrics.ObserveCache("episode_availability_fresh", "miss")
@@ -243,22 +209,36 @@ func (s *EpisodeService) isFreshEpisodeCache(anime domain.Anime, row db.EpisodeA
return true return true
} }
func (s *EpisodeService) decodeFreshCachedPayload(anime domain.Anime, raw string) (domain.CanonicalEpisodeList, bool) { func (s *EpisodeService) decodeCachedPayload(anime domain.Anime, raw string, metric string) (domain.CanonicalEpisodeList, bool) {
var payload domain.CanonicalEpisodeList var payload domain.CanonicalEpisodeList
err := json.Unmarshal([]byte(raw), &payload) if err := json.Unmarshal([]byte(raw), &payload); err != nil {
if err == nil { s.metrics.ObserveCache(metric, "miss")
return payload, true observability.Warn(
"episodes_cached_payload_invalid",
"episodes",
"",
map[string]any{
"anime_id": anime.MalID,
},
err,
)
return domain.CanonicalEpisodeList{}, false
} }
s.metrics.ObserveCache("episode_availability_fresh", "miss") if !isCanonicalEpisodePayloadValid(payload, anime.Episodes) {
observability.Warn( s.metrics.ObserveCache(metric, "miss")
"episodes_cached_payload_invalid", observability.Info(
"episodes", "episodes_cached_payload_rejected",
"", "episodes",
map[string]any{ "",
"anime_id": anime.MalID, map[string]any{
}, "anime_id": anime.MalID,
err, "expected_count": anime.Episodes,
) "cached_episodes": len(payload.Episodes),
return domain.CanonicalEpisodeList{}, false },
)
return domain.CanonicalEpisodeList{}, false
}
return payload, true
} }