Files
AetherForge/server/internal/db/recon_scans.go
AetherForge 5317135a7d
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add recon UX backend: scan history, export, and streaming tests.
Persist recon scans in SQLite with history diff and export routes; wire WebSocket streaming test coverage and relay path_tracer_hints in fleet reports.
2026-06-07 12:25:35 -07:00

112 lines
2.9 KiB
Go

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
}