package esi import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "os" "time" "go-eve-pi/options" "go-eve-pi/types" logger "git.site.quack-lab.dev/dave/cylogger" ) // CacheEntry is now defined in the types package // CachedESI implements ESIInterface with caching type CachedESI struct { direct ESIInterface db interface { GetCacheEntry(urlHash string) (*types.CacheEntry, error) SaveCacheEntry(entry *types.CacheEntry) error } cacheValidity time.Duration } // NewCachedESI creates a new CachedESI instance func NewCachedESI(direct ESIInterface, db interface { GetCacheEntry(urlHash string) (*types.CacheEntry, error) SaveCacheEntry(entry *types.CacheEntry) error }) *CachedESI { // Parse cache validity ONCE at initialization cacheValidity, err := time.ParseDuration(options.GlobalOptions.CacheValidity) if err != nil { logger.Error("Invalid cache validity duration %s: %v", options.GlobalOptions.CacheValidity, err) os.Exit(1) } return &CachedESI{ direct: direct, db: db, cacheValidity: cacheValidity, } } // GetCharacterPlanets retrieves a list of planets for a character with caching func (c *CachedESI) GetCharacterPlanets(ctx context.Context, characterID int, accessToken string) ([]Planet, error) { url := fmt.Sprintf("/v1/characters/%d/planets/", characterID) fetchFunc := func() ([]Planet, error) { return c.direct.GetCharacterPlanets(ctx, characterID, accessToken) } return getCachedResponse(c, url, fetchFunc) } // GetPlanetDetails retrieves detailed information about a specific planet with caching func (c *CachedESI) GetPlanetDetails(ctx context.Context, characterID, planetID int, accessToken string) (*PlanetDetail, error) { url := fmt.Sprintf("/v3/characters/%d/planets/%d/", characterID, planetID) fetchFunc := func() (*PlanetDetail, error) { return c.direct.GetPlanetDetails(ctx, characterID, planetID, accessToken) } return getCachedResponse(c, url, fetchFunc) } // getCachedResponse handles caching logic with generics func getCachedResponse[T any](c *CachedESI, url string, fetchFunc func() (T, error)) (T, error) { // Generate cache key hash := sha256.Sum256([]byte(url)) urlHash := hex.EncodeToString(hash[:]) // Check cache using the interface cacheEntry, err := c.db.GetCacheEntry(urlHash) if err == nil { // Check if cache is still valid if time.Since(cacheEntry.CachedAt) < c.cacheValidity { logger.Debug("Cache hit for URL: %s", url) // Parse cached response var result T if err := json.Unmarshal([]byte(cacheEntry.Response), &result); err == nil { return result, nil } } } // Cache miss or invalid, fetch from API logger.Debug("Cache miss for URL: %s", url) result, err := fetchFunc() if err != nil { var zero T return zero, err } // Store in cache responseBytes, err := json.Marshal(result) if err != nil { logger.Warning("Failed to marshal response for caching: %v", err) return result, nil } cacheEntry = &types.CacheEntry{ URLHash: urlHash, Response: string(responseBytes), CachedAt: time.Now(), } if err := c.db.SaveCacheEntry(cacheEntry); err != nil { logger.Warning("Failed to cache response: %v", err) } logger.Debug("Cached response for URL: %s", url) return result, nil }