package presence import ( "errors" "sync" "time" "github.com/google/uuid" ) var ( ErrUserNotFound = errors.New("user not found") ErrSessionExpired = errors.New("session expired") ) const ( IdleTimeout = 5 * time.Minute ConnectionTimeout = 2 * time.Minute ) // Manager manages user presence and sessions type Manager struct { sessions map[string]*Session // userID -> session mu sync.RWMutex } // NewManager creates a new presence manager func NewManager() *Manager { return &Manager{ sessions: make(map[string]*Session), } } // CreateSession creates a new user session func (m *Manager) CreateSession(userID string) *Session { m.mu.Lock() defer m.mu.Unlock() sessionID := uuid.New().String() session := NewSession(userID, sessionID) m.sessions[userID] = session return session } // GetSession retrieves a user session func (m *Manager) GetSession(userID string) (*Session, error) { m.mu.RLock() defer m.mu.RUnlock() session, exists := m.sessions[userID] if !exists { return nil, ErrUserNotFound } return session, nil } // EndSession removes a user session func (m *Manager) EndSession(userID string) error { m.mu.Lock() defer m.mu.Unlock() _, exists := m.sessions[userID] if !exists { return ErrUserNotFound } delete(m.sessions, userID) return nil } // UpdatePresence updates a user's presence status func (m *Manager) UpdatePresence(userID string, status Status) error { m.mu.Lock() defer m.mu.Unlock() session, exists := m.sessions[userID] if !exists { return ErrUserNotFound } session.Status = status session.UpdateActivity() return nil } // SetChannelPresence updates which channel user is in func (m *Manager) SetChannelPresence(userID string, channelID string) error { m.mu.Lock() defer m.mu.Unlock() session, exists := m.sessions[userID] if !exists { return ErrUserNotFound } session.CurrentChannelID = channelID session.UpdateActivity() return nil } // SetMuteStatus updates mute status func (m *Manager) SetMuteStatus(userID string, micMuted bool, speakerMuted bool) error { m.mu.Lock() defer m.mu.Unlock() session, exists := m.sessions[userID] if !exists { return ErrUserNotFound } session.IsMicrophoneMuted = micMuted session.IsSpeakerMuted = speakerMuted session.UpdateActivity() return nil } // GetOnlineUsers returns list of online users func (m *Manager) GetOnlineUsers() []*Session { m.mu.RLock() defer m.mu.RUnlock() var users []*Session for _, session := range m.sessions { if session.Status != StatusOffline { users = append(users, session) } } return users } // GetChannelMembers returns sessions of users in a channel func (m *Manager) GetChannelMembers(channelID string) []*Session { m.mu.RLock() defer m.mu.RUnlock() var members []*Session for _, session := range m.sessions { if session.CurrentChannelID == channelID && session.Status != StatusOffline { members = append(members, session) } } return members } // DetectIdleUsers detects and marks idle users func (m *Manager) DetectIdleUsers() { m.mu.Lock() defer m.mu.Unlock() for _, session := range m.sessions { if session.Status == StatusOnline && session.IsIdle(IdleTimeout) { session.MarkIdle() } } } // ReportActivity updates user's activity timestamp func (m *Manager) ReportActivity(userID string) error { m.mu.Lock() defer m.mu.Unlock() session, exists := m.sessions[userID] if !exists { return ErrUserNotFound } session.UpdateActivity() return nil }