Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type Config struct {
EnableTLS bool `mapstructure:"enable_tls"`
TLSCertFile string `mapstructure:"tls_cert_file"`
TLSKeyFile string `mapstructure:"tls_key_file"`

RedirectPort string `mapstructure:"redirect_from"`
}

var (
Expand Down
56 changes: 55 additions & 1 deletion database/bolt/bolt.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ func (p *Bolt) FetchByUUID(uuid string) (*schema.TelemetryData, error) {
func (p *Bolt) FetchLast100() ([]schema.TelemetryData, error) {
var records []schema.TelemetryData
err := p.db.View(func(tx *bbolt.Tx) error {
var record schema.TelemetryData
bucket := tx.Bucket([]byte(bucketName))
if bucket == nil {
return errors.New("data bucket doesn't exist yet")
Expand All @@ -65,6 +64,11 @@ func (p *Bolt) FetchLast100() ([]schema.TelemetryData, error) {
_, b := cursor.Last()

for len(records) < 100 {
// IMPORTANT: declare a fresh record on every iteration.
// json.Unmarshal does NOT reset fields that are absent from the JSON,
// so reusing one struct would let values (e.g. ClientID, GradeData)
// leak from an earlier row into a later one.
var record schema.TelemetryData
if err := json.Unmarshal(b, &record); err != nil {
return err
}
Expand All @@ -80,3 +84,53 @@ func (p *Bolt) FetchLast100() ([]schema.TelemetryData, error) {
})
return records, err
}

// FetchAll returns one page of records (most recent first), skipping `offset`
// records. ULID keys are lexicographically ordered by time, so descending key
// order == newest first.
func (p *Bolt) FetchAll(offset, limit int) ([]schema.TelemetryData, error) {
if limit <= 0 {
limit = 50
}
var records []schema.TelemetryData
err := p.db.View(func(tx *bbolt.Tx) error {
bucket := tx.Bucket([]byte(bucketName))
if bucket == nil {
return errors.New("data bucket doesn't exist yet")
}

skipped, taken := 0, 0
cursor := bucket.Cursor()
for k, b := cursor.Last(); k != nil; k, b = cursor.Prev() {
if skipped < offset {
skipped++
continue
}
if taken >= limit {
break
}
var record schema.TelemetryData // fresh each iteration (see FetchLast100)
if err := json.Unmarshal(b, &record); err != nil {
return err
}
records = append(records, record)
taken++
}
return nil
})
return records, err
}

// Count returns the total number of stored records.
func (p *Bolt) Count() (int, error) {
var count int
err := p.db.View(func(tx *bbolt.Tx) error {
bucket := tx.Bucket([]byte(bucketName))
if bucket == nil {
return nil // no bucket yet → 0 records
}
count = bucket.Stats().KeyN
return nil
})
return count, err
}
2 changes: 2 additions & 0 deletions database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type DataAccess interface {
Insert(*schema.TelemetryData) error
FetchByUUID(string) (*schema.TelemetryData, error)
FetchLast100() ([]schema.TelemetryData, error)
FetchAll(offset, limit int) ([]schema.TelemetryData, error)
Count() (int, error)
}

func SetDBInfo(conf *config.Config) {
Expand Down
30 changes: 30 additions & 0 deletions database/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,33 @@ func (mem *Memory) FetchLast100() ([]schema.TelemetryData, error) {
defer mem.lock.RUnlock()
return mem.records, nil
}

func (mem *Memory) FetchAll(offset, limit int) ([]schema.TelemetryData, error) {
if limit <= 0 {
limit = 50
}
mem.lock.RLock()
defer mem.lock.RUnlock()

n := len(mem.records)
// records are stored oldest→first; we want newest→first
if offset >= n {
return nil, nil
}
end := n - offset
start := end - limit
if start < 0 {
start = 0
}
out := make([]schema.TelemetryData, 0, end-start)
for i := end - 1; i >= start; i-- {
out = append(out, mem.records[i])
}
return out, nil
}

func (mem *Memory) Count() (int, error) {
mem.lock.RLock()
defer mem.lock.RUnlock()
return len(mem.records), nil
}
90 changes: 69 additions & 21 deletions database/mssql/mssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,43 +39,91 @@ func Open(hostname, username, password, database, port string) *MSSQL {
return &MSSQL{db: conn}
}

func msScan(row interface{ Scan(...any) error }, record *schema.TelemetryData) error {
var id int64
return 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,
&record.GradeData, &record.ChartData, &record.LatencyUnderload,
&record.PingDuringTest, &record.ClientID)
}

func (p *MSSQL) Insert(data *schema.TelemetryData) error {
stmt := `INSERT INTO speedtest_users (ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid)
VALUES (@p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11);`
_, err := p.db.Exec(stmt,
_, err := p.db.Exec(
`INSERT INTO speedtest_users
(ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid,
grade_data, chart_data, latency_underload, ping_during_test, client_id)
VALUES (@p1,@p2,@p3,@p4,@p5,@p6,@p7,@p8,@p9,@p10,@p11,@p12,@p13,@p14,@p15,@p16)`,
data.IPAddress, data.ISPInfo, data.Extra, data.UserAgent, data.Language,
data.Download, data.Upload, data.Ping, data.Jitter, data.Log, data.UUID)
data.Download, data.Upload, data.Ping, data.Jitter, data.Log, data.UUID,
data.GradeData, data.ChartData, data.LatencyUnderload, data.PingDuringTest, data.ClientID)
return err
}

func (p *MSSQL) FetchByUUID(uuid string) (*schema.TelemetryData, error) {
var record schema.TelemetryData
row := p.db.QueryRow(`SELECT * FROM speedtest_users WHERE uuid = @p1`, uuid)
if row != nil {
var id int64
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("mssql fetch by uuid: %w", err)
}
row := p.db.QueryRow(
`SELECT id, timestamp, ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid,
COALESCE(grade_data,''), COALESCE(chart_data,''), COALESCE(latency_underload,''),
COALESCE(ping_during_test,''), COALESCE(client_id,'')
FROM speedtest_users WHERE uuid = @p1`, uuid)
if err := msScan(row, &record); err != nil {
return nil, fmt.Errorf("mssql fetch by uuid: %w", err)
}
return &record, nil
}

func (p *MSSQL) FetchLast100() ([]schema.TelemetryData, error) {
var records []schema.TelemetryData
rows, err := p.db.Query(`SELECT TOP 100 * FROM speedtest_users ORDER BY timestamp DESC;`)
rows, err := p.db.Query(
`SELECT TOP 100 id, timestamp, ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid,
COALESCE(grade_data,''), COALESCE(chart_data,''), COALESCE(latency_underload,''),
COALESCE(ping_during_test,''), COALESCE(client_id,'')
FROM speedtest_users ORDER BY timestamp DESC`)
if err != nil {
return nil, fmt.Errorf("mssql fetch last 100: %w", err)
}
if rows != nil {
defer rows.Close()
for rows.Next() {
var record schema.TelemetryData
var id int64
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("mssql scan row: %w", err)
}
records = append(records, record)
defer rows.Close()
var records []schema.TelemetryData
for rows.Next() {
var record schema.TelemetryData
if err := msScan(rows, &record); err != nil {
return nil, fmt.Errorf("mssql scan row: %w", err)
}
records = append(records, record)
}
return records, nil
}

func (p *MSSQL) FetchAll(offset, limit int) ([]schema.TelemetryData, error) {
if limit <= 0 {
limit = 50
}
rows, err := p.db.Query(
`SELECT id, timestamp, ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid,
COALESCE(grade_data,''), COALESCE(chart_data,''), COALESCE(latency_underload,''),
COALESCE(ping_during_test,''), COALESCE(client_id,'')
FROM speedtest_users ORDER BY timestamp DESC OFFSET @p1 ROWS FETCH NEXT @p2 ROWS ONLY`,
offset, limit)
if err != nil {
return nil, fmt.Errorf("mssql fetch all: %w", err)
}
defer rows.Close()
var records []schema.TelemetryData
for rows.Next() {
var record schema.TelemetryData
if err := msScan(rows, &record); err != nil {
return nil, fmt.Errorf("mssql scan row: %w", err)
}
records = append(records, record)
}
return records, nil
}

func (p *MSSQL) Count() (int, error) {
var count int
err := p.db.QueryRow(`SELECT COUNT(*) FROM speedtest_users;`).Scan(&count)
if err != nil {
return 0, fmt.Errorf("mssql count: %w", err)
}
return count, nil
}
84 changes: 66 additions & 18 deletions database/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,88 @@ func Open(hostname, username, password, database string) *MySQL {
return &MySQL{db: conn}
}

func myScan(row interface{ Scan(...any) error }, record *schema.TelemetryData) error {
var id string
return 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,
&record.GradeData, &record.ChartData, &record.LatencyUnderload,
&record.PingDuringTest, &record.ClientID)
}

func (p *MySQL) Insert(data *schema.TelemetryData) error {
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)
_, err := p.db.Exec(
`INSERT INTO speedtest_users
(ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid,
grade_data, chart_data, latency_underload, ping_during_test, client_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
data.IPAddress, data.ISPInfo, data.Extra, data.UserAgent, data.Language,
data.Download, data.Upload, data.Ping, data.Jitter, data.Log, data.UUID,
data.GradeData, data.ChartData, data.LatencyUnderload, data.PingDuringTest, data.ClientID)
return err
}

func (p *MySQL) 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 string
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, err
}
row := p.db.QueryRow(
`SELECT id, timestamp, ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid,
COALESCE(grade_data,''), COALESCE(chart_data,''), COALESCE(latency_underload,''),
COALESCE(ping_during_test,''), COALESCE(client_id,'')
FROM speedtest_users WHERE uuid = ?`, uuid)
if err := myScan(row, &record); err != nil {
return nil, err
}
return &record, nil
}

func (p *MySQL) FetchLast100() ([]schema.TelemetryData, error) {
var records []schema.TelemetryData
rows, err := p.db.Query(`SELECT * FROM speedtest_users ORDER BY "timestamp" DESC LIMIT 100;`)
rows, err := p.db.Query(
`SELECT id, timestamp, ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid,
COALESCE(grade_data,''), COALESCE(chart_data,''), COALESCE(latency_underload,''),
COALESCE(ping_during_test,''), COALESCE(client_id,'')
FROM speedtest_users ORDER BY timestamp DESC LIMIT 100`)
if err != nil {
return nil, err
}
if rows != nil {
var id string
defer rows.Close()
var records []schema.TelemetryData
for rows.Next() {
var record schema.TelemetryData
if err := myScan(rows, &record); err != nil {
return nil, err
}
records = append(records, record)
}
return records, nil
}

for rows.Next() {
var record schema.TelemetryData
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, err
}
records = append(records, record)
func (p *MySQL) FetchAll(offset, limit int) ([]schema.TelemetryData, error) {
if limit <= 0 {
limit = 50
}
rows, err := p.db.Query(
`SELECT id, timestamp, ip, ispinfo, extra, ua, lang, dl, ul, ping, jitter, log, uuid,
COALESCE(grade_data,''), COALESCE(chart_data,''), COALESCE(latency_underload,''),
COALESCE(ping_during_test,''), COALESCE(client_id,'')
FROM speedtest_users ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
var records []schema.TelemetryData
for rows.Next() {
var record schema.TelemetryData
if err := myScan(rows, &record); err != nil {
return nil, err
}
records = append(records, record)
}
return records, nil
}

func (p *MySQL) Count() (int, error) {
var count int
err := p.db.QueryRow(`SELECT COUNT(*) FROM speedtest_users;`).Scan(&count)
return count, err
}
8 changes: 8 additions & 0 deletions database/none/none.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ func (n *None) FetchByUUID(_ string) (*schema.TelemetryData, error) {
func (n *None) FetchLast100() ([]schema.TelemetryData, error) {
return []schema.TelemetryData{}, nil
}

func (n *None) FetchAll(_, _ int) ([]schema.TelemetryData, error) {
return []schema.TelemetryData{}, nil
}

func (n *None) Count() (int, error) {
return 0, nil
}
Loading