//go:build embed package web import ( "crypto/sha256" "encoding/hex" "sync" ) // HTMLCache manages the cached index.html with injected settings type HTMLCache struct { mu sync.RWMutex cachedHTML []byte etag string baseHTMLHash string // Hash of the original index.html (immutable after build) settingsVersion uint64 // Incremented when settings change } // CachedHTML represents the cache state type CachedHTML struct { Content []byte ETag string } // NewHTMLCache creates a new HTML cache instance func NewHTMLCache() *HTMLCache { return &HTMLCache{} } // SetBaseHTML initializes the cache with the base HTML template func (c *HTMLCache) SetBaseHTML(baseHTML []byte) { c.mu.Lock() defer c.mu.Unlock() hash := sha256.Sum256(baseHTML) c.baseHTMLHash = hex.EncodeToString(hash[:8]) // First 8 bytes for brevity } // Invalidate marks the cache as stale func (c *HTMLCache) Invalidate() { c.mu.Lock() defer c.mu.Unlock() c.settingsVersion++ c.cachedHTML = nil c.etag = "" } // Get returns the cached HTML or nil if cache is stale func (c *HTMLCache) Get() *CachedHTML { c.mu.RLock() defer c.mu.RUnlock() if c.cachedHTML == nil { return nil } return &CachedHTML{ Content: c.cachedHTML, ETag: c.etag, } } // Set updates the cache with new rendered HTML func (c *HTMLCache) Set(html []byte, settingsJSON []byte) { c.mu.Lock() defer c.mu.Unlock() c.cachedHTML = html c.etag = c.generateETag(settingsJSON) } // generateETag creates an ETag from base HTML hash + settings hash func (c *HTMLCache) generateETag(settingsJSON []byte) string { settingsHash := sha256.Sum256(settingsJSON) return `"` + c.baseHTMLHash + "-" + hex.EncodeToString(settingsHash[:8]) + `"` }