package session import ( "crypto/rand" "encoding/hex" "sync" "time" "kubeviz/internal/model" ) type sessionData struct { Dataset *model.Dataset LastAccess time.Time } type Store struct { mu sync.RWMutex sessions map[string]*sessionData ttl time.Duration stopCh chan struct{} } func NewStore(ttl time.Duration) *Store { s := &Store{ sessions: make(map[string]*sessionData), ttl: ttl, stopCh: make(chan struct{}), } go s.cleanupLoop() return s } func (s *Store) Stop() { close(s.stopCh) } func (s *Store) NewSessionID() (string, error) { buf := make([]byte, 16) if _, err := rand.Read(buf); err != nil { return "", err } return hex.EncodeToString(buf), nil } func (s *Store) GetDataset(sessionID string) *model.Dataset { s.mu.Lock() defer s.mu.Unlock() entry, ok := s.sessions[sessionID] if !ok { return nil } entry.LastAccess = time.Now() return entry.Dataset } func (s *Store) SetDataset(sessionID string, ds *model.Dataset) { s.mu.Lock() defer s.mu.Unlock() s.sessions[sessionID] = &sessionData{Dataset: ds, LastAccess: time.Now()} } func (s *Store) Clear(sessionID string) { s.mu.Lock() defer s.mu.Unlock() delete(s.sessions, sessionID) } func (s *Store) cleanupLoop() { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() for { select { case <-s.stopCh: return case <-ticker.C: s.cleanupExpired() } } } func (s *Store) cleanupExpired() { if s.ttl <= 0 { return } cutoff := time.Now().Add(-s.ttl) s.mu.Lock() defer s.mu.Unlock() for id, sess := range s.sessions { if sess.LastAccess.Before(cutoff) { delete(s.sessions, id) } } }