package store import ( "context" "database/sql" "encoding/json" "fmt" "time" ) type AuditEntry struct { ID int64 `json:"id"` Timestamp time.Time `json:"timestamp"` Actor string `json:"actor"` Action string `json:"action"` Resource string `json:"resource"` Result string `json:"result"` Error string `json:"error,omitempty"` Metadata map[string]any `json:"metadata,omitempty"` } type AuditRepo struct { db *sql.DB } func NewAuditRepo(db *sql.DB) *AuditRepo { return &AuditRepo{db: db} } func (r *AuditRepo) Append(ctx context.Context, e *AuditEntry) error { if e.Timestamp.IsZero() { e.Timestamp = time.Now().UTC() } if e.Actor == "" { e.Actor = "system" } metaJSON, _ := json.Marshal(e.Metadata) if e.Error == "" { _, err := r.db.ExecContext(ctx, `INSERT INTO audit_log (timestamp, actor, action, resource, result, metadata) VALUES (?, ?, ?, ?, ?, ?)`, e.Timestamp, e.Actor, e.Action, e.Resource, e.Result, string(metaJSON)) if err != nil { return fmt.Errorf("insert audit: %w", err) } return nil } _, err := r.db.ExecContext(ctx, `INSERT INTO audit_log (timestamp, actor, action, resource, result, error, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)`, e.Timestamp, e.Actor, e.Action, e.Resource, e.Result, e.Error, string(metaJSON)) if err != nil { return fmt.Errorf("insert audit (with error): %w", err) } return nil } func (r *AuditRepo) List(ctx context.Context, limit int) ([]*AuditEntry, error) { if limit <= 0 { limit = 100 } rows, err := r.db.QueryContext(ctx, `SELECT id, timestamp, actor, action, resource, result, COALESCE(error, ''), COALESCE(metadata, '') FROM audit_log ORDER BY id DESC LIMIT ?`, limit) if err != nil { return nil, fmt.Errorf("list audit: %w", err) } defer rows.Close() var entries []*AuditEntry for rows.Next() { var ( e AuditEntry metaJSON string ) if err := rows.Scan(&e.ID, &e.Timestamp, &e.Actor, &e.Action, &e.Resource, &e.Result, &e.Error, &metaJSON); err != nil { return nil, fmt.Errorf("scan audit: %w", err) } if metaJSON != "" { _ = json.Unmarshal([]byte(metaJSON), &e.Metadata) } entries = append(entries, &e) } return entries, rows.Err() }