package sqlite import ( "database/sql" "fmt" "github.com/librespeed/speedtest-go/database/schema" _ "modernc.org/sqlite" log "github.com/sirupsen/logrus" ) type SQLite struct { db *sql.DB } func Open(databaseFile string) *SQLite { conn, err := sql.Open("sqlite", databaseFile) if err != nil { log.Fatalf("Cannot open SQLite database: %s", err) } // Enable WAL mode for better concurrent performance if _, err := conn.Exec("PRAGMA journal_mode=WAL"); err != nil { log.Warnf("Failed to set SQLite journal mode to WAL: %s", err) } // Create table if not exists (matching the PHP SQLite auto-creation behavior) stmt := `CREATE TABLE IF NOT EXISTS speedtest_users ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, ip TEXT NOT NULL, ispinfo TEXT, extra TEXT, ua TEXT NOT NULL, lang TEXT NOT NULL, dl TEXT, ul TEXT, ping TEXT, jitter TEXT, log TEXT, uuid TEXT );` if _, err := conn.Exec(stmt); err != nil { log.Fatalf("Failed to create speedtest_users table: %s", err) } return &SQLite{db: conn} } func (p *SQLite) Insert(data *schema.TelemetryData) error { var existingID int // Check for duplicate UUID first err := p.db.QueryRow(`SELECT id FROM speedtest_users WHERE uuid = ?`, data.UUID).Scan(&existingID) if err == nil { // Record with this UUID already exists - skip insert return nil } stmt := `INSERT INTO speedtest_users (ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);` _, err = p.db.Exec(stmt, data.IPAddress, data.ISPInfo, data.Extra, data.UserAgent, data.Language, data.Download, data.Upload, data.Ping, data.Jitter, data.Log, data.UUID) return err } func (p *SQLite) FetchByUUID(uuid string) (*schema.TelemetryData, error) { var record schema.TelemetryData row := p.db.QueryRow(`SELECT * FROM speedtest_users WHERE uuid = ?`, uuid) if row != nil { var id int if err := row.Scan(&id, &record.Timestamp, &record.IPAddress, &record.ISPInfo, &record.Extra, &record.UserAgent, &record.Language, &record.Download, &record.Upload, &record.Ping, &record.Jitter, &record.Log, &record.UUID); err != nil { return nil, fmt.Errorf("sqlite fetch by uuid: %w", err) } } return &record, nil } func (p *SQLite) FetchLast100() ([]schema.TelemetryData, error) { var records []schema.TelemetryData rows, err := p.db.Query(`SELECT * FROM speedtest_users ORDER BY timestamp DESC LIMIT 100;`) if err != nil { return nil, fmt.Errorf("sqlite fetch last 100: %w", err) } if rows != nil { defer rows.Close() for rows.Next() { var record schema.TelemetryData var id int if err := rows.Scan(&id, &record.Timestamp, &record.IPAddress, &record.ISPInfo, &record.Extra, &record.UserAgent, &record.Language, &record.Download, &record.Upload, &record.Ping, &record.Jitter, &record.Log, &record.UUID); err != nil { return nil, fmt.Errorf("sqlite scan row: %w", err) } records = append(records, record) } } return records, nil }