package main import ( "log" "net/http" "os" "github.com/go-chi/chi/v5" chiMiddleware "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "github.com/joho/godotenv" "rideaware/internal/activity" "rideaware/internal/auth" "rideaware/internal/config" "rideaware/internal/equipment" "rideaware/internal/export" "rideaware/internal/integration" "rideaware/internal/middleware" "rideaware/internal/stats" "rideaware/internal/templates" "rideaware/internal/user" "rideaware/internal/workout" "rideaware/pkg/database" ) func main() { godotenv.Load() // Initialize database database.Init() defer database.Close() // Run migrations if err := database.Migrate( &user.User{}, &user.Profile{}, &user.PasswordReset{}, &user.Session{}, &equipment.Equipment{}, &workout.Workout{}, &integration.OAuthConnection{}, &integration.OAuthState{}, ); err != nil { log.Fatalf("Failed to migrate database: %v", err) } // Initialize JWT config config.InitJWT() // Initialize OAuth config config.InitOAuth() r := chi.NewRouter() // Logging middleware r.Use(chiMiddleware.RequestID) r.Use(chiMiddleware.RealIP) r.Use(loggingMiddleware) r.Use(chiMiddleware.Recoverer) // CORS middleware r.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{ "GET", "POST", "PUT", "DELETE", "OPTIONS", }, AllowedHeaders: []string{ "Accept", "Authorization", "Content-Type", }, ExposedHeaders: []string{"Link"}, MaxAge: 300, })) // Routes setupRoutes(r) port := os.Getenv("PORT") if port == "" { port = "5000" } log.Printf("🚀 Server running on port %s", port) log.Fatal(http.ListenAndServe(":"+port, r)) } // Logging middleware func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf( "[%s] %s %s", r.Method, r.RequestURI, r.RemoteAddr, ) next.ServeHTTP(w, r) }) } func setupRoutes(r *chi.Mux) { r.Options("/*", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type") w.WriteHeader(http.StatusOK) }) // Public routes r.Get("/health", healthCheck) // Auth routes (public - no /api prefix needed for these) authHandler := auth.NewHandler() r.Post("/api/signup", authHandler.Signup) r.Post("/api/login", authHandler.Login) r.Post("/api/logout", authHandler.Logout) r.Post("/api/password-reset/request", authHandler.RequestPasswordReset) r.Post("/api/password-reset/confirm", authHandler.ConfirmPasswordReset) r.Post("/api/refresh-token", authHandler.RefreshToken) // OAuth callbacks (public - called by provider redirects) garminHandler := integration.NewGarminHandler() wahooHandler := integration.NewWahooHandler() r.Get("/api/garmin/callback", garminHandler.Callback) r.Get("/api/wahoo/callback", wahooHandler.Callback) // Protected routes authMiddleware := middleware.NewAuthMiddleware() r.Route("/api/protected", func(r chi.Router) { r.Use(authMiddleware.ProtectedRoute) // User routes userHandler := user.NewHandler() r.Get("/profile", userHandler.GetProfile) r.Put("/profile", userHandler.UpdateProfile) // Equipment routes equipmentHandler := equipment.NewHandler() r.Post("/equipment", equipmentHandler.CreateEquipment) r.Get("/equipment", equipmentHandler.GetEquipment) r.Put("/equipment", equipmentHandler.UpdateEquipment) r.Delete("/equipment", equipmentHandler.DeleteEquipment) // Equipment service tracking r.Post("/equipment/service", equipmentHandler.RecordService) r.Get("/equipment/service-status", equipmentHandler.GetServiceStatus) // Training zones r.Get("/zones", equipmentHandler.GetTrainingZones) // Workout routes workoutHandler := workout.NewHandler() r.Post("/workouts", workoutHandler.CreateWorkout) r.Get("/workouts", workoutHandler.GetWorkouts) r.Get("/workouts/month", workoutHandler.GetWorkoutsByMonth) r.Get("/workouts/equipment-stats", workoutHandler.GetEquipmentStats) r.Put("/workouts", workoutHandler.UpdateWorkout) r.Delete("/workouts", workoutHandler.DeleteWorkout) r.Get("/workout-types", workoutHandler.GetWorkoutTypes) r.Post("/workouts/upload", workoutHandler.UploadWorkoutFile) // Activity import (FIT/TCX/GPX) activityHandler := activity.NewHandler() r.Post("/workouts/import", activityHandler.ImportActivity) // Workout export routes exportHandler := export.NewHandler() r.Get("/workouts/export/fit", exportHandler.ExportFIT) r.Get("/workouts/export/zwo", exportHandler.ExportZWO) // Garmin integration routes r.Get("/garmin/auth", garminHandler.StartAuth) r.Post("/workouts/push/garmin", garminHandler.PushWorkout) r.Get("/garmin/status", garminHandler.ConnectionStatus) r.Delete("/garmin/disconnect", garminHandler.Disconnect) // Wahoo integration routes r.Get("/wahoo/auth", wahooHandler.StartAuth) r.Post("/workouts/push/wahoo", wahooHandler.PushWorkout) r.Get("/wahoo/status", wahooHandler.ConnectionStatus) r.Delete("/wahoo/disconnect", wahooHandler.Disconnect) // Stats routes statsHandler := stats.NewHandler() r.Get("/stats/summary", statsHandler.GetSummary) r.Get("/stats/weekly", statsHandler.GetWeeklyStats) r.Get("/stats/monthly", statsHandler.GetMonthlyStats) r.Get("/stats/personal-bests", statsHandler.GetPersonalBests) // Workout template routes templateHandler := templates.NewHandler() r.Get("/workout-templates", templateHandler.ListTemplates) r.Get("/workout-templates/detail", templateHandler.GetTemplate) r.Post("/workouts/from-template", templateHandler.CreateFromTemplate) }) log.Println("✅ Routes registered successfully") } func healthCheck(w http.ResponseWriter, r *http.Request) { log.Println("📊 Health check called") w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) }