Files
pedestrian-simulator/server/db.go

139 lines
3.9 KiB
Go
Raw Normal View History

package main
import (
"database/sql"
"fmt"
"log"
2026-01-11 20:54:37 -07:00
"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))
2026-01-11 20:54:37 -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)
}
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
)`,
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
)`,
}
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")
}