first set of files

This commit is contained in:
2025-08-24 08:04:35 -07:00
commit c550f7d0df
9 changed files with 1371 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
// internal/database/models.go
package database
import (
"database/sql"
"time"
)
type Activity struct {
ID int `json:"id"`
ActivityID int `json:"activity_id"`
StartTime time.Time `json:"start_time"`
ActivityType string `json:"activity_type"`
Duration int `json:"duration"` // seconds
Distance float64 `json:"distance"` // meters
MaxHeartRate int `json:"max_heart_rate"`
AvgHeartRate int `json:"avg_heart_rate"`
AvgPower float64 `json:"avg_power"`
Calories int `json:"calories"`
Filename string `json:"filename"`
FileType string `json:"file_type"`
FileSize int64 `json:"file_size"`
Downloaded bool `json:"downloaded"`
CreatedAt time.Time `json:"created_at"`
LastSync time.Time `json:"last_sync"`
}
type Stats struct {
Total int `json:"total"`
Downloaded int `json:"downloaded"`
Missing int `json:"missing"`
}
type DaemonConfig struct {
ID int `json:"id"`
Enabled bool `json:"enabled"`
ScheduleCron string `json:"schedule_cron"`
LastRun string `json:"last_run"`
Status string `json:"status"`
}
// Database interface
type Database interface {
// Activities
GetActivities(limit, offset int) ([]Activity, error)
GetActivity(activityID int) (*Activity, error)
CreateActivity(activity *Activity) error
UpdateActivity(activity *Activity) error
DeleteActivity(activityID int) error
// Stats
GetStats() (*Stats, error)
// Search and filter
FilterActivities(filters ActivityFilters) ([]Activity, error)
// Close connection
Close() error
}
type ActivityFilters struct {
ActivityType string
DateFrom *time.Time
DateTo *time.Time
MinDistance float64
MaxDistance float64
MinDuration int
MaxDuration int
Downloaded *bool
Limit int
Offset int
SortBy string
SortOrder string
}

282
internal/database/sqlite.go Normal file
View File

@@ -0,0 +1,282 @@
// internal/database/sqlite.go
package database
import (
"database/sql"
"fmt"
"strings"
"time"
)
type SQLiteDB struct {
db *sql.DB
}
func NewSQLiteDB(dbPath string) (*SQLiteDB, error) {
db, err := sql.Open("sqlite3", dbPath+"?_foreign_keys=on")
if err != nil {
return nil, err
}
sqlite := &SQLiteDB{db: db}
// Create tables
if err := sqlite.createTables(); err != nil {
return nil, err
}
return sqlite, nil
}
func (s *SQLiteDB) createTables() error {
schema := `
CREATE TABLE IF NOT EXISTS activities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
activity_id INTEGER UNIQUE NOT NULL,
start_time DATETIME NOT NULL,
activity_type TEXT,
duration INTEGER,
distance REAL,
max_heart_rate INTEGER,
avg_heart_rate INTEGER,
avg_power REAL,
calories INTEGER,
filename TEXT UNIQUE,
file_type TEXT,
file_size INTEGER,
downloaded BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_sync DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_activities_activity_id ON activities(activity_id);
CREATE INDEX IF NOT EXISTS idx_activities_start_time ON activities(start_time);
CREATE INDEX IF NOT EXISTS idx_activities_activity_type ON activities(activity_type);
CREATE INDEX IF NOT EXISTS idx_activities_downloaded ON activities(downloaded);
CREATE TABLE IF NOT EXISTS daemon_config (
id INTEGER PRIMARY KEY DEFAULT 1,
enabled BOOLEAN DEFAULT TRUE,
schedule_cron TEXT DEFAULT '0 * * * *',
last_run TEXT,
status TEXT DEFAULT 'stopped',
CONSTRAINT single_config CHECK (id = 1)
);
INSERT OR IGNORE INTO daemon_config (id) VALUES (1);
`
_, err := s.db.Exec(schema)
return err
}
func (s *SQLiteDB) GetActivities(limit, offset int) ([]Activity, error) {
query := `
SELECT id, activity_id, start_time, activity_type, duration, distance,
max_heart_rate, avg_heart_rate, avg_power, calories, filename,
file_type, file_size, downloaded, created_at, last_sync
FROM activities
ORDER BY start_time DESC
LIMIT ? OFFSET ?`
rows, err := s.db.Query(query, limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
var activities []Activity
for rows.Next() {
var a Activity
var startTime, createdAt, lastSync string
err := rows.Scan(
&a.ID, &a.ActivityID, &startTime, &a.ActivityType,
&a.Duration, &a.Distance, &a.MaxHeartRate, &a.AvgHeartRate,
&a.AvgPower, &a.Calories, &a.Filename, &a.FileType,
&a.FileSize, &a.Downloaded, &createdAt, &lastSync,
)
if err != nil {
return nil, err
}
// Parse time strings
if a.StartTime, err = time.Parse("2006-01-02 15:04:05", startTime); err != nil {
return nil, err
}
if a.CreatedAt, err = time.Parse("2006-01-02 15:04:05", createdAt); err != nil {
return nil, err
}
if a.LastSync, err = time.Parse("2006-01-02 15:04:05", lastSync); err != nil {
return nil, err
}
activities = append(activities, a)
}
return activities, nil
}
func (s *SQLiteDB) CreateActivity(activity *Activity) error {
query := `
INSERT INTO activities (
activity_id, start_time, activity_type, duration, distance,
max_heart_rate, avg_heart_rate, avg_power, calories,
filename, file_type, file_size, downloaded
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
_, err := s.db.Exec(query,
activity.ActivityID, activity.StartTime.Format("2006-01-02 15:04:05"),
activity.ActivityType, activity.Duration, activity.Distance,
activity.MaxHeartRate, activity.AvgHeartRate, activity.AvgPower,
activity.Calories, activity.Filename, activity.FileType,
activity.FileSize, activity.Downloaded,
)
return err
}
func (s *SQLiteDB) UpdateActivity(activity *Activity) error {
query := `
UPDATE activities SET
activity_type = ?, duration = ?, distance = ?,
max_heart_rate = ?, avg_heart_rate = ?, avg_power = ?,
calories = ?, filename = ?, file_type = ?, file_size = ?,
downloaded = ?, last_sync = CURRENT_TIMESTAMP
WHERE activity_id = ?`
_, err := s.db.Exec(query,
activity.ActivityType, activity.Duration, activity.Distance,
activity.MaxHeartRate, activity.AvgHeartRate, activity.AvgPower,
activity.Calories, activity.Filename, activity.FileType,
activity.FileSize, activity.Downloaded, activity.ActivityID,
)
return err
}
func (s *SQLiteDB) GetStats() (*Stats, error) {
stats := &Stats{}
// Get total count
err := s.db.QueryRow("SELECT COUNT(*) FROM activities").Scan(&stats.Total)
if err != nil {
return nil, err
}
// Get downloaded count
err = s.db.QueryRow("SELECT COUNT(*) FROM activities WHERE downloaded = TRUE").Scan(&stats.Downloaded)
if err != nil {
return nil, err
}
stats.Missing = stats.Total - stats.Downloaded
return stats, nil
}
func (s *SQLiteDB) FilterActivities(filters ActivityFilters) ([]Activity, error) {
query := `
SELECT id, activity_id, start_time, activity_type, duration, distance,
max_heart_rate, avg_heart_rate, avg_power, calories, filename,
file_type, file_size, downloaded, created_at, last_sync
FROM activities WHERE 1=1`
var args []interface{}
var conditions []string
// Build WHERE conditions
if filters.ActivityType != "" {
conditions = append(conditions, "activity_type = ?")
args = append(args, filters.ActivityType)
}
if filters.DateFrom != nil {
conditions = append(conditions, "start_time >= ?")
args = append(args, filters.DateFrom.Format("2006-01-02 15:04:05"))
}
if filters.DateTo != nil {
conditions = append(conditions, "start_time <= ?")
args = append(args, filters.DateTo.Format("2006-01-02 15:04:05"))
}
if filters.MinDistance > 0 {
conditions = append(conditions, "distance >= ?")
args = append(args, filters.MinDistance)
}
if filters.MaxDistance > 0 {
conditions = append(conditions, "distance <= ?")
args = append(args, filters.MaxDistance)
}
if filters.Downloaded != nil {
conditions = append(conditions, "downloaded = ?")
args = append(args, *filters.Downloaded)
}
// Add conditions to query
if len(conditions) > 0 {
query += " AND " + strings.Join(conditions, " AND ")
}
// Add sorting
orderBy := "start_time"
if filters.SortBy != "" {
orderBy = filters.SortBy
}
order := "DESC"
if filters.SortOrder == "asc" {
order = "ASC"
}
query += fmt.Sprintf(" ORDER BY %s %s", orderBy, order)
// Add pagination
if filters.Limit > 0 {
query += " LIMIT ?"
args = append(args, filters.Limit)
if filters.Offset > 0 {
query += " OFFSET ?"
args = append(args, filters.Offset)
}
}
rows, err := s.db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var activities []Activity
for rows.Next() {
var a Activity
var startTime, createdAt, lastSync string
err := rows.Scan(
&a.ID, &a.ActivityID, &startTime, &a.ActivityType,
&a.Duration, &a.Distance, &a.MaxHeartRate, &a.AvgHeartRate,
&a.AvgPower, &a.Calories, &a.Filename, &a.FileType,
&a.FileSize, &a.Downloaded, &createdAt, &lastSync,
)
if err != nil {
return nil, err
}
// Parse times
a.StartTime, _ = time.Parse("2006-01-02 15:04:05", startTime)
a.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
a.LastSync, _ = time.Parse("2006-01-02 15:04:05", lastSync)
activities = append(activities, a)
}
return activities, nil
}
func (s *SQLiteDB) Close() error {
return s.db.Close()
}