package store import ( "strconv" "time" ) type AuditEntry struct { TS string Account string Action string Target string Result string Reason string } func (s *Store) Audit(now time.Time, e AuditEntry) error { var reason any if e.Reason != "" { reason = e.Reason } _, err := s.db.Exec( "INSERT INTO audit_log(ts,account,action,target,result,reason) VALUES(?,?,?,?,?,?)", now.UTC().Format(time.RFC3339), e.Account, e.Action, e.Target, e.Result, reason) return err } func (s *Store) PurgeAudit(now time.Time) (int64, error) { v, err := s.GetSetting("audit_retention_days") if err != nil { // unset => no retention policy return 0, nil } days, err := strconv.Atoi(v) if err != nil || days <= 0 { return 0, nil } cutoff := now.UTC().AddDate(0, 0, -days).Format(time.RFC3339) res, err := s.db.Exec("DELETE FROM audit_log WHERE ts < ?", cutoff) if err != nil { return 0, err } return res.RowsAffected() } func (s *Store) RecentAudit(limit int) ([]AuditEntry, error) { return s.RecentAuditFor("", limit) } // RecentAuditFor returns recent audit entries, newest first. An empty account // returns entries for all accounts; otherwise only that account's entries. func (s *Store) RecentAuditFor(account string, limit int) ([]AuditEntry, error) { q := "SELECT ts,account,action,target,result,COALESCE(reason,'') FROM audit_log" var args []any if account != "" { q += " WHERE account=?" args = append(args, account) } q += " ORDER BY id DESC LIMIT ?" args = append(args, limit) rows, err := s.db.Query(q, args...) if err != nil { return nil, err } defer rows.Close() var out []AuditEntry for rows.Next() { var e AuditEntry if err := rows.Scan(&e.TS, &e.Account, &e.Action, &e.Target, &e.Result, &e.Reason); err != nil { return nil, err } out = append(out, e) } return out, rows.Err() }