2026-01-11 20:24:50 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"database/sql"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log"
|
2026-01-11 20:54:37 -07:00
|
|
|
"net/url"
|
2026-01-11 20:24:50 -07:00
|
|
|
"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
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 21:07:37 -07:00
|
|
|
// 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))
|
2026-01-11 20:54:37 -07:00
|
|
|
}
|
2026-01-11 21:07:37 -07:00
|
|
|
|
2026-01-11 20:24:50 -07:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 21:07:37 -07:00
|
|
|
log.Printf("[DB] Connected to MariaDB successfully (using loc=%s, tz=%s)", location, tz)
|
2026-01-11 20:50:08 -07:00
|
|
|
|
2026-01-11 20:24:50 -07:00
|
|
|
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,
|
2026-01-11 22:48:50 -07:00
|
|
|
description TEXT,
|
2026-01-11 20:24:50 -07:00
|
|
|
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,
|
2026-01-11 21:07:37 -07:00
|
|
|
last_sync_time TIMESTAMP NULL,
|
|
|
|
|
next_sync_time TIMESTAMP NULL,
|
2026-01-11 20:24:50 -07:00
|
|
|
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
|
|
|
|
|
)`,
|
2026-01-14 16:55:49 -07:00
|
|
|
`CREATE TABLE IF NOT EXISTS completed_trips (
|
|
|
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
user_id VARCHAR(255),
|
|
|
|
|
trip_type ENUM('address', 'kml') DEFAULT 'address',
|
|
|
|
|
route_name TEXT,
|
|
|
|
|
start_address TEXT,
|
|
|
|
|
end_address TEXT,
|
|
|
|
|
kml_id INT,
|
|
|
|
|
distance DOUBLE,
|
|
|
|
|
completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
FOREIGN KEY (user_id) REFERENCES users(fitbit_user_id) ON DELETE CASCADE,
|
|
|
|
|
FOREIGN KEY (kml_id) REFERENCES kml_metadata(id) ON DELETE SET NULL
|
|
|
|
|
)`,
|
2026-01-11 20:24:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, query := range queries {
|
|
|
|
|
if _, err := db.Exec(query); err != nil {
|
|
|
|
|
log.Fatalf("Error creating table: %v\nQuery: %s", err, query)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-11 21:07:37 -07:00
|
|
|
|
|
|
|
|
// 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")
|
2026-01-11 22:48:50 -07:00
|
|
|
// Migration for KML description
|
|
|
|
|
db.Exec("ALTER TABLE kml_metadata ADD COLUMN IF NOT EXISTS description TEXT")
|
2026-01-11 21:07:37 -07:00
|
|
|
|
2026-01-11 20:24:50 -07:00
|
|
|
log.Println("Database tables initialized")
|
|
|
|
|
}
|