package auth import ( "sort" "sync" "time" ) type AccessSnapshot struct { KeyID string LastPath string RemoteAddr string UserAgent string RequestCount int LastAccessedAt time.Time } type AccessTracker struct { mu sync.RWMutex entries map[string]AccessSnapshot } func NewAccessTracker() *AccessTracker { return &AccessTracker{entries: make(map[string]AccessSnapshot)} } func (t *AccessTracker) Record(keyID, path, remoteAddr, userAgent string, now time.Time) { if t == nil || keyID == "" { return } t.mu.Lock() defer t.mu.Unlock() entry := t.entries[keyID] entry.KeyID = keyID entry.LastPath = path entry.RemoteAddr = remoteAddr entry.UserAgent = userAgent entry.LastAccessedAt = now.UTC() entry.RequestCount++ t.entries[keyID] = entry } func (t *AccessTracker) Snapshot() []AccessSnapshot { if t == nil { return nil } t.mu.RLock() defer t.mu.RUnlock() items := make([]AccessSnapshot, 0, len(t.entries)) for _, entry := range t.entries { items = append(items, entry) } sort.Slice(items, func(i, j int) bool { return items[i].LastAccessedAt.After(items[j].LastAccessedAt) }) return items } func (t *AccessTracker) ConnectedCount(now time.Time, window time.Duration) int { if t == nil { return 0 } cutoff := now.UTC().Add(-window) t.mu.RLock() defer t.mu.RUnlock() count := 0 for _, entry := range t.entries { if !entry.LastAccessedAt.Before(cutoff) { count++ } } return count }