Files
pedestrian-simulator/server/main.go

145 lines
4.0 KiB
Go
Raw Normal View History

2026-01-11 17:16:59 -07:00
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
2026-01-11 18:27:07 -07:00
"os"
"time"
2026-01-11 17:16:59 -07:00
_ "github.com/go-sql-driver/mysql"
2026-01-11 17:16:59 -07:00
)
// RecoveryMiddleware catches panics and returns 500
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("[PANIC] %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// getStepManager creates a new StepManager for the given user, loading state from DB
func getStepManager(userID string) *StepManager {
return NewStepManager(userID)
2026-01-11 17:16:59 -07:00
}
2026-01-11 18:27:07 -07:00
func initTimezone() {
tz := os.Getenv("TZ")
if tz != "" {
loc, err := time.LoadLocation(tz)
if err != nil {
log.Printf("Error loading timezone %s: %v. Using UTC.", tz, err)
return
}
time.Local = loc
log.Printf("Timezone set to %s", tz)
} else {
log.Printf("TZ environment variable not set, using system default (usually UTC)")
}
log.Printf("Current system time: %s", time.Now().Format(time.RFC1123Z))
}
2026-01-11 17:16:59 -07:00
func main() {
// Initialize components
2026-01-11 18:27:07 -07:00
initTimezone()
InitDB()
2026-01-11 17:16:59 -07:00
InitFitbit()
// 1. Serve Static Files (Frontend)
fs := http.FileServer(http.Dir("frontend"))
http.Handle("/", fs)
// 2. API Endpoints (all require authentication)
http.HandleFunc("/api/status", RequireAuth(func(w http.ResponseWriter, r *http.Request) {
userID, _ := getUserID(r.Context())
sm := getStepManager(userID)
2026-01-11 17:16:59 -07:00
status := sm.GetStatus()
// Add user info to status
user, err := GetUser(userID)
if err == nil && user != nil {
2026-01-11 17:16:59 -07:00
status["user"] = map[string]string{
"id": user.FitbitUserID,
2026-01-11 17:16:59 -07:00
"displayName": user.DisplayName,
"avatarUrl": user.AvatarURL,
}
} else {
fmt.Printf("[API Status] WARNING: User info not found for ID: %s (err=%v)\n", userID, err)
2026-01-11 17:16:59 -07:00
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status)
}))
http.HandleFunc("/api/refresh", RequireAuth(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
userID, _ := getUserID(r.Context())
sm := getStepManager(userID)
2026-01-11 17:16:59 -07:00
sm.Sync()
w.WriteHeader(http.StatusOK)
}))
http.HandleFunc("/api/trip", RequireAuth(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
userID, _ := getUserID(r.Context())
sm := getStepManager(userID)
2026-01-11 17:16:59 -07:00
sm.StartNewTrip()
w.WriteHeader(http.StatusOK)
}))
http.HandleFunc("/api/drain", RequireAuth(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
userID, _ := getUserID(r.Context())
sm := getStepManager(userID)
2026-01-11 17:16:59 -07:00
go sm.Drain() // Async so we don't block
w.WriteHeader(http.StatusOK)
}))
// 3. KML Management Endpoints
http.HandleFunc("/api/kml/upload", RequireAuth(HandleKMLUpload))
http.HandleFunc("/api/kml/list", RequireAuth(HandleKMLList))
http.HandleFunc("/api/kml/edit", RequireAuth(HandleKMLEdit))
2026-01-11 17:16:59 -07:00
http.HandleFunc("/api/kml/privacy", RequireAuth(HandleKMLPrivacyToggle))
http.HandleFunc("/api/kml/vote", RequireAuth(HandleKMLVote))
http.HandleFunc("/api/kml/delete", RequireAuth(HandleKMLDelete))
http.HandleFunc("/api/kml/download", RequireAuth(HandleKMLDownload))
// 4. Fitbit OAuth Endpoints
http.HandleFunc("/auth/fitbit", HandleFitbitAuth)
http.HandleFunc("/auth/callback", HandleFitbitCallback)
// 5. Logout Endpoint
http.HandleFunc("/auth/logout", func(w http.ResponseWriter, r *http.Request) {
session, err := GetSessionFromRequest(r)
if err == nil {
DeleteSession(session.Token)
}
ClearSessionCookie(w)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
})
// 6. Start Server
binding := "0.0.0.0:8080"
fmt.Printf("Server starting on http://%s\n", binding)
log.Fatal(http.ListenAndServe(binding, RecoveryMiddleware(http.DefaultServeMux)))
2026-01-11 17:16:59 -07:00
}