This commit is contained in:
106
internal/audit/store.go
Normal file
106
internal/audit/store.go
Normal file
@@ -0,0 +1,106 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user