package audit import ( "fmt" "sync" "time" "gitea.avt.data-center.id/othman.suseno/atlas/internal/models" ) // Store manages audit logs type Store struct { mu sync.RWMutex logs []models.AuditLog nextID int64 maxLogs int // Maximum number of logs to keep (0 = unlimited) } // NewStore creates a new audit log store func NewStore(maxLogs int) *Store { return &Store{ logs: make([]models.AuditLog, 0), nextID: 1, maxLogs: maxLogs, } } // Log records an audit log entry func (s *Store) Log(actor, action, resource, result, message, ip, userAgent string) *models.AuditLog { s.mu.Lock() defer s.mu.Unlock() id := fmt.Sprintf("audit-%d", s.nextID) s.nextID++ entry := models.AuditLog{ ID: id, Actor: actor, Action: action, Resource: resource, Result: result, Message: message, IP: ip, UserAgent: userAgent, Timestamp: time.Now(), } s.logs = append(s.logs, entry) // Enforce max logs limit if s.maxLogs > 0 && len(s.logs) > s.maxLogs { // Remove oldest logs excess := len(s.logs) - s.maxLogs s.logs = s.logs[excess:] } return &entry } // List returns audit logs, optionally filtered func (s *Store) List(actor, action, resource string, limit int) []models.AuditLog { s.mu.RLock() defer s.mu.RUnlock() var filtered []models.AuditLog for i := len(s.logs) - 1; i >= 0; i-- { // Reverse iteration (newest first) log := s.logs[i] if actor != "" && log.Actor != actor { continue } if action != "" && log.Action != action { continue } if resource != "" && !containsResource(log.Resource, resource) { continue } filtered = append(filtered, log) if limit > 0 && len(filtered) >= limit { break } } return filtered } // Get returns a specific audit log by ID func (s *Store) Get(id string) (*models.AuditLog, error) { s.mu.RLock() defer s.mu.RUnlock() for _, log := range s.logs { if log.ID == id { return &log, nil } } return nil, fmt.Errorf("audit log %s not found", id) } // containsResource checks if resource string contains the search term func containsResource(resource, search string) bool { return resource == search || (len(resource) > len(search) && resource[:len(search)] == search) }