package sessions import ( "sync" "time" "github.com/goccy/go-json" ) // Session stores data for a single user session type Session struct { ID string `json:"id"` Data map[string]any `json:"data"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LastUsed time.Time `json:"last_used"` Expiry time.Time `json:"expiry"` dirty bool // Tracks if session has changes, not serialized } // Session pool to reduce allocations var sessionPool = sync.Pool{ New: func() any { return &Session{ Data: make(map[string]any, 8), } }, } // GetFromPool retrieves a session from the pool func GetFromPool() *Session { return sessionPool.Get().(*Session) } // ReturnToPool returns a session to the pool after cleaning it func ReturnToPool(s *Session) { if s == nil { return } // Clean the session for reuse s.ID = "" for k := range s.Data { delete(s.Data, k) } s.CreatedAt = time.Time{} s.UpdatedAt = time.Time{} s.LastUsed = time.Time{} s.Expiry = time.Time{} s.dirty = false sessionPool.Put(s) } // NewSession creates a new session with the given ID func NewSession(id string, maxAge int) *Session { now := time.Now() // Get from pool or create new session := GetFromPool() // Initialize session.ID = id session.CreatedAt = now session.UpdatedAt = now session.LastUsed = now session.Expiry = now.Add(time.Duration(maxAge) * time.Second) session.dirty = false return session } // Get retrieves a value from the session func (s *Session) Get(key string) any { return s.Data[key] } // Set stores a value in the session func (s *Session) Set(key string, value any) { s.Data[key] = value s.UpdatedAt = time.Now() s.dirty = true } // Delete removes a value from the session func (s *Session) Delete(key string) { delete(s.Data, key) s.UpdatedAt = time.Now() s.dirty = true } // Clear removes all data from the session func (s *Session) Clear() { s.Data = make(map[string]any, 8) s.UpdatedAt = time.Now() s.dirty = true } // GetAll returns a copy of all session data func (s *Session) GetAll() map[string]any { copy := make(map[string]any, len(s.Data)) for k, v := range s.Data { copy[k] = v } return copy } // IsExpired checks if the session has expired func (s *Session) IsExpired() bool { return time.Now().After(s.Expiry) } // UpdateLastUsed updates the last used time // Only updates if at least 5 seconds have passed since last update func (s *Session) UpdateLastUsed() { now := time.Now() if now.Sub(s.LastUsed) > 5*time.Second { s.LastUsed = now // Not marking dirty for LastUsed updates to reduce writes } } // IsDirty returns if the session has unsaved changes func (s *Session) IsDirty() bool { return s.dirty } // ResetDirty marks the session as clean after saving func (s *Session) ResetDirty() { s.dirty = false } // Marshal serializes the session to JSON func (s *Session) Marshal() ([]byte, error) { return json.Marshal(s) } // Unmarshal deserializes a session from JSON func Unmarshal(data []byte) (*Session, error) { session := GetFromPool() err := json.Unmarshal(data, session) if err != nil { ReturnToPool(session) return nil, err } return session, nil }