package indexer import ( "context" "time" "github.com/jackc/pgx/v5" "github.com/riverqueue/river" "github.com/rs/zerolog/log" "homelab.lan/music-agregator/internal/config" ) type CachedIndexer struct { inner Indexer cache *IndexerCache riverClient *river.Client[pgx.Tx] cfg config.CacheConfig } func NewCachedIndexer(inner Indexer, cache *IndexerCache, riverClient *river.Client[pgx.Tx], cfg config.CacheConfig) *CachedIndexer { return &CachedIndexer{ inner: inner, cache: cache, riverClient: riverClient, cfg: cfg, } } func (c *CachedIndexer) Search(query string, limit int32, tracker string) (SearchResult, error) { key := query + "|" + tracker log.Trace().Str("key", key).Str("query", query).Str("tracker", tracker).Msg("cached indexer search") if entry, ok := c.cache.Get(key); ok { log.Debug().Str("key", key).Int("items", len(entry.Result.Items)).Msg("returning cached result") return entry.Result, nil } log.Trace().Str("key", key).Msg("cache miss, fetching from indexer") result, err := c.inner.Search(query, limit, tracker) if err != nil { log.Error().Err(err).Str("key", key).Msg("cached indexer fetch failed") return SearchResult{}, err } url := c.inner.BuildSearchURL(query, limit, tracker) log.Trace().Str("key", key).Str("url", url).Int("items", len(result.Items)).Msg("caching result") c.cache.Add(CacheEntry{ Key: key, URL: url, Result: result, CreatedAt: time.Now(), TTL: c.cfg.TTL, RefreshInterval: c.cfg.RefreshInterval, }) scheduleAt := time.Now().Add(c.cfg.RefreshInterval) _, err = c.riverClient.Insert(context.Background(), CacheRefreshArgs{ Key: key, URL: url, TTLExpires: time.Now().Add(c.cfg.TTL), RefreshInterval: c.cfg.RefreshInterval, }, &river.InsertOpts{ ScheduledAt: scheduleAt, }) if err != nil { log.Error().Err(err).Str("key", key).Msg("failed to schedule cache refresh job") } else { log.Debug().Str("key", key).Time("scheduled_at", scheduleAt).Msg("cache refresh job scheduled") } log.Debug().Str("key", key).Dur("ttl", c.cfg.TTL).Dur("refresh", c.cfg.RefreshInterval).Int("items", len(result.Items)).Msg("cached indexer search complete") return result, nil } func (c *CachedIndexer) FetchURL(url string) (SearchResult, error) { log.Trace().Str("url", url).Msg("cached indexer fetch URL passthrough") return c.inner.FetchURL(url) } func (c *CachedIndexer) BuildSearchURL(query string, limit int32, tracker string) string { return c.inner.BuildSearchURL(query, limit, tracker) } func (c *CachedIndexer) Capabilities(indexerName string) (IndexerCapabilities, error) { log.Trace().Str("indexer", indexerName).Msg("cached indexer capabilities passthrough") return c.inner.Capabilities(indexerName) }