package main import ( "database/sql" "fmt" "log" "net/url" "os" _ "github.com/go-sql-driver/mysql" ) var db *sql.DB func InitDB() { host := os.Getenv("DB_HOST") port := os.Getenv("DB_PORT") name := os.Getenv("DB_DATABASENAME") user := os.Getenv("DB_USERNAME") pass := os.Getenv("DB_PASSWORD") if host == "" { log.Println("DB_HOST not set, skipping database initialization") return } // Use TZ env var if set, otherwise fallback to Local location := "Local" tz := os.Getenv("TZ") dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=%s", user, pass, host, port, name, location) if tz != "" { dsn += fmt.Sprintf("&time_zone='%s'", url.QueryEscape(tz)) } var err error db, err = sql.Open("mysql", dsn) if err != nil { log.Fatalf("Error opening database: %v", err) } if err := db.Ping(); err != nil { log.Fatalf("Error connecting to database: %v", err) } log.Printf("[DB] Connected to MariaDB successfully (using loc=%s, tz=%s)", location, tz) createTables() } func createTables() { queries := []string{ `CREATE TABLE IF NOT EXISTS users ( fitbit_user_id VARCHAR(255) PRIMARY KEY, display_name VARCHAR(255), avatar_url TEXT, created_at DATETIME )`, `CREATE TABLE IF NOT EXISTS sessions ( token VARCHAR(255) PRIMARY KEY, fitbit_user_id VARCHAR(255), created_at DATETIME, expires_at DATETIME, FOREIGN KEY (fitbit_user_id) REFERENCES users(fitbit_user_id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS fitbit_tokens ( user_id VARCHAR(255) PRIMARY KEY, access_token TEXT, refresh_token TEXT, expires_at DATETIME, FOREIGN KEY (user_id) REFERENCES users(fitbit_user_id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS kml_metadata ( id INT AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(255), user_id VARCHAR(255), distance DOUBLE, description TEXT, is_public BOOLEAN DEFAULT FALSE, uploaded_at DATETIME, UNIQUE KEY (user_id, filename), FOREIGN KEY (user_id) REFERENCES users(fitbit_user_id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS kml_votes ( kml_id INT, user_id VARCHAR(255), vote_value INT, PRIMARY KEY (kml_id, user_id), FOREIGN KEY (kml_id) REFERENCES kml_metadata(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(fitbit_user_id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS trips ( user_id VARCHAR(255) PRIMARY KEY, start_date VARCHAR(10), start_time DATETIME, start_day_initial_steps INT, previous_total_steps INT, target_total_steps INT, last_sync_time TIMESTAMP NULL, next_sync_time TIMESTAMP NULL, FOREIGN KEY (user_id) REFERENCES users(fitbit_user_id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS daily_steps ( user_id VARCHAR(255), date VARCHAR(10), steps INT, PRIMARY KEY (user_id, date), FOREIGN KEY (user_id) REFERENCES users(fitbit_user_id) ON DELETE CASCADE )`, } for _, query := range queries { if _, err := db.Exec(query); err != nil { log.Fatalf("Error creating table: %v\nQuery: %s", err, query) } } // Migrations: Ensure trips table uses TIMESTAMP for sync times (breaking change for DATETIME -> TIMESTAMP) db.Exec("ALTER TABLE trips MODIFY last_sync_time TIMESTAMP NULL") db.Exec("ALTER TABLE trips MODIFY next_sync_time TIMESTAMP NULL") // Migration for KML description db.Exec("ALTER TABLE kml_metadata ADD COLUMN IF NOT EXISTS description TEXT") log.Println("Database tables initialized") }