mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Handle legacy audit timestamp rows
This commit is contained in:
parent
6212eecf12
commit
0f1211d510
2 changed files with 107 additions and 3 deletions
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -406,16 +407,20 @@ func (l *SQLiteLogger) queryLocked(filter QueryFilter) ([]Event, error) {
|
|||
var events []Event
|
||||
for rows.Next() {
|
||||
var e Event
|
||||
var timestamp int64
|
||||
var timestampValue any
|
||||
var success int
|
||||
var user, ip, path, details, signature sql.NullString
|
||||
|
||||
err := rows.Scan(&e.ID, ×tamp, &e.EventType, &user, &ip, &path, &success, &details, &signature)
|
||||
err := rows.Scan(&e.ID, ×tampValue, &e.EventType, &user, &ip, &path, &success, &details, &signature)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan audit event: %w", err)
|
||||
}
|
||||
timestamp, err := parseAuditTimestamp(timestampValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan audit event: %w", err)
|
||||
}
|
||||
|
||||
e.Timestamp = time.Unix(timestamp, 0)
|
||||
e.Timestamp = timestamp
|
||||
e.Success = success == 1
|
||||
e.User = user.String
|
||||
e.IP = ip.String
|
||||
|
|
@ -429,6 +434,62 @@ func (l *SQLiteLogger) queryLocked(filter QueryFilter) ([]Event, error) {
|
|||
return events, rows.Err()
|
||||
}
|
||||
|
||||
func parseAuditTimestamp(value any) (time.Time, error) {
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
return v, nil
|
||||
case int64:
|
||||
return time.Unix(v, 0), nil
|
||||
case int:
|
||||
return time.Unix(int64(v), 0), nil
|
||||
case int32:
|
||||
return time.Unix(int64(v), 0), nil
|
||||
case float64:
|
||||
return time.Unix(int64(v), 0), nil
|
||||
case []byte:
|
||||
return parseAuditTimestampString(string(v))
|
||||
case string:
|
||||
return parseAuditTimestampString(v)
|
||||
case nil:
|
||||
return time.Time{}, errors.New("timestamp is null")
|
||||
default:
|
||||
return time.Time{}, fmt.Errorf("unsupported timestamp type %T", value)
|
||||
}
|
||||
}
|
||||
|
||||
func parseAuditTimestampString(raw string) (time.Time, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return time.Time{}, errors.New("timestamp is empty")
|
||||
}
|
||||
if unix, err := strconv.ParseInt(raw, 10, 64); err == nil {
|
||||
return time.Unix(unix, 0), nil
|
||||
}
|
||||
|
||||
layouts := []string{
|
||||
time.RFC3339Nano,
|
||||
time.RFC3339,
|
||||
"2006-01-02 15:04:05.999999999Z07:00",
|
||||
"2006-01-02 15:04:05.999999Z07:00",
|
||||
"2006-01-02 15:04:05Z07:00",
|
||||
"2006-01-02 15:04:05.999999999-07:00",
|
||||
"2006-01-02 15:04:05.999999-07:00",
|
||||
"2006-01-02 15:04:05-07:00",
|
||||
"2006-01-02 15:04:05.999999999 -0700 MST",
|
||||
"2006-01-02 15:04:05.999999 -0700 MST",
|
||||
"2006-01-02 15:04:05 -0700 MST",
|
||||
"2006-01-02 15:04:05.999999999",
|
||||
"2006-01-02 15:04:05.999999",
|
||||
"2006-01-02 15:04:05",
|
||||
}
|
||||
for _, layout := range layouts {
|
||||
if timestamp, err := time.Parse(layout, raw); err == nil {
|
||||
return timestamp, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("unsupported timestamp value %q", raw)
|
||||
}
|
||||
|
||||
// Count returns the number of events matching the filter.
|
||||
func (l *SQLiteLogger) Count(filter QueryFilter) (int, error) {
|
||||
var count int
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package audit
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
|
@ -267,6 +268,48 @@ func TestSQLiteLoggerQuery(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSQLiteLoggerQueryHandlesLegacyDatetimeTimestampRows(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
logger, err := NewSQLiteLogger(SQLiteLoggerConfig{
|
||||
DataDir: tempDir,
|
||||
CryptoMgr: newMockCryptoManager(),
|
||||
RetentionDays: 30,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewSQLiteLogger failed: %v", err)
|
||||
}
|
||||
defer logger.Close()
|
||||
|
||||
legacyTime := time.Date(2026, 7, 5, 16, 4, 13, 0, time.UTC)
|
||||
if _, err := logger.db.Exec(`
|
||||
INSERT INTO audit_events (id, timestamp, event_type, user, ip, path, success, details, signature)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
"legacy-datetime",
|
||||
legacyTime,
|
||||
"startup",
|
||||
sql.NullString{},
|
||||
sql.NullString{},
|
||||
"/api/audit",
|
||||
1,
|
||||
"legacy Pro runtime timestamp",
|
||||
"legacy-signature",
|
||||
); err != nil {
|
||||
t.Fatalf("insert legacy datetime row: %v", err)
|
||||
}
|
||||
|
||||
events, err := logger.Query(QueryFilter{ID: "legacy-datetime", Limit: 1})
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed for legacy datetime timestamp row: %v", err)
|
||||
}
|
||||
if len(events) != 1 {
|
||||
t.Fatalf("Expected 1 event, got %d", len(events))
|
||||
}
|
||||
if !events[0].Timestamp.Equal(legacyTime) {
|
||||
t.Fatalf("timestamp = %s, want %s", events[0].Timestamp, legacyTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLiteLoggerCount(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue