Files
pedestrian-simulator/server/main.go

154 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"
2026-01-11 17:16:59 -07:00
"sync"
2026-01-11 18:27:07 -07:00
"time"
2026-01-11 17:16:59 -07:00
)
var (
stepManagers = make(map[string]*StepManager) // userID -> StepManager
smMutex sync.RWMutex
)
// getOrCreateStepManager retrieves or creates a StepManager for the given user
func getOrCreateStepManager(userID string) *StepManager {
smMutex.RLock()
sm, exists := stepManagers[userID]
smMutex.RUnlock()
if exists {
return sm
}
// Create new StepManager for this user
smMutex.Lock()
defer smMutex.Unlock()
// Double-check it wasn't created while we were waiting for the lock
if sm, exists := stepManagers[userID]; exists {
return sm
}
sm = NewStepManager(userID)
stepManagers[userID] = sm
return sm
}
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()
2026-01-11 17:16:59 -07:00
InitFitbit()
InitUserRegistry()
InitVoteRegistry()
// 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 := getOrCreateStepManager(userID)
status := sm.GetStatus()
// Add user info to status
user, exists := userRegistry.GetUser(userID)
if exists && user != nil {
status["user"] = map[string]string{
"displayName": user.DisplayName,
"avatarUrl": user.AvatarURL,
}
} else {
fmt.Printf("[API Status] WARNING: User info not found for ID: %s (exists=%v)\n", userID, exists)
}
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 := getOrCreateStepManager(userID)
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 := getOrCreateStepManager(userID)
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 := getOrCreateStepManager(userID)
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/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, nil))
}