package esi import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "time" logger "git.site.quack-lab.dev/dave/cylogger" ) // CacheEntry represents a cached API response type CacheEntry struct { ID uint `gorm:"primaryKey"` URLHash string `gorm:"uniqueIndex"` Response string `gorm:"type:text"` CachedAt time.Time `gorm:"index"` } // CachedESI implements ESIInterface with caching type CachedESI struct { direct ESIInterface db interface { GetCacheEntry(urlHash string) (*CacheEntry, error) SaveCacheEntry(entry *CacheEntry) error } } // NewCachedESI creates a new CachedESI instance func NewCachedESI(direct ESIInterface, db interface { GetCacheEntry(urlHash string) (*CacheEntry, error) SaveCacheEntry(entry *CacheEntry) error }) *CachedESI { return &CachedESI{ direct: direct, db: db, } } // 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) result, err := func() (interface{}, error) { var fetchFunc func() (interface{}, error) = func() (interface{}, error) { return c.direct.GetCharacterPlanets(ctx, characterID, accessToken) } return c.getCachedResponse(url, fetchFunc) }() if err != nil { return nil, err } return result.([]Planet), nil } // 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) result, err := func() (interface{}, error) { var fetchFunc func() (interface{}, error) = func() (interface{}, error) { return c.direct.GetPlanetDetails(ctx, characterID, planetID, accessToken) } return c.getCachedResponse(url, fetchFunc) }() if err != nil { return nil, err } return result.(*PlanetDetail), nil } // getCachedResponse handles caching logic func (c *CachedESI) getCachedResponse(url string, fetchFunc func() (interface{}, error)) (interface{}, 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 cacheValidity, _ := time.ParseDuration("10m") // Default 10 minutes if time.Since(cacheEntry.CachedAt) < cacheValidity { logger.Debug("Cache hit for URL: %s", url) // Parse cached response based on URL pattern if url[len(url)-1:] == "/" { // Planets endpoint var planets []Planet if err := json.Unmarshal([]byte(cacheEntry.Response), &planets); err == nil { return planets, nil } } else { // Planet details endpoint var planetDetail PlanetDetail if err := json.Unmarshal([]byte(cacheEntry.Response), &planetDetail); err == nil { return &planetDetail, nil } } } } // Cache miss or invalid, fetch from API logger.Debug("Cache miss for URL: %s", url) result, err := fetchFunc() if err != nil { return nil, 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 = 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 }