package db import ( "encoding/json" "fmt" "time" "crypto-miner-server/internal/recon" ) func (d *Database) ensureReconScansTable() error { _, err := d.Exec(`CREATE TABLE IF NOT EXISTS recon_scans ( scan_id TEXT PRIMARY KEY, host TEXT NOT NULL, profile TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'complete', scanned_at DATETIME NOT NULL, report_json TEXT NOT NULL )`) if err != nil { return err } _, err = d.Exec(`CREATE INDEX IF NOT EXISTS idx_recon_scans_host ON recon_scans(host, scanned_at DESC)`) return err } func (d *Database) InsertReconScan(report *recon.ScanReport) error { if d == nil || report == nil || report.ScanID == "" { return fmt.Errorf("invalid recon scan row") } if err := d.ensureReconScansTable(); err != nil { return err } raw, err := json.Marshal(report) if err != nil { return err } status := report.Status if status == "" { status = "complete" } _, err = d.Exec(`INSERT INTO recon_scans (scan_id, host, profile, status, scanned_at, report_json) VALUES (?, ?, ?, ?, ?, ?)`, report.ScanID, report.Host, report.Profile, status, report.ScannedAt.UTC().Format(time.RFC3339Nano), string(raw)) if err != nil { return err } return d.PruneReconScansForHost(report.Host, 10) } func (d *Database) GetReconScan(scanID string) (*recon.ScanReport, error) { if err := d.ensureReconScansTable(); err != nil { return nil, err } var host, profile, status, scannedAt, raw string err := d.QueryRow(`SELECT host, profile, status, scanned_at, report_json FROM recon_scans WHERE scan_id = ?`, scanID).Scan(&host, &profile, &status, &scannedAt, &raw) if err != nil { return nil, err } var report recon.ScanReport if err := json.Unmarshal([]byte(raw), &report); err != nil { return nil, err } if report.ScanID == "" { report.ScanID = scanID } if report.Host == "" { report.Host = host } if report.Profile == "" { report.Profile = profile } if report.Status == "" { report.Status = status } return &report, nil } func (d *Database) ListReconScansByHost(host string, limit int) ([]*recon.ScanReport, error) { if err := d.ensureReconScansTable(); err != nil { return nil, err } if limit <= 0 { limit = 10 } rows, err := d.Query(`SELECT report_json FROM recon_scans WHERE host = ? ORDER BY scanned_at ASC LIMIT ?`, host, limit) if err != nil { return nil, err } defer rows.Close() var out []*recon.ScanReport for rows.Next() { var raw string if err := rows.Scan(&raw); err != nil { return nil, err } var report recon.ScanReport if err := json.Unmarshal([]byte(raw), &report); err != nil { return nil, err } out = append(out, &report) } return out, rows.Err() } func (d *Database) PruneReconScansForHost(host string, keep int) error { if keep <= 0 { keep = 10 } _, err := d.Exec(`DELETE FROM recon_scans WHERE host = ? AND scan_id NOT IN (SELECT scan_id FROM recon_scans WHERE host = ? ORDER BY scanned_at DESC LIMIT ?)`, host, host, keep) return err }