203 lines
5.9 KiB
Go
203 lines
5.9 KiB
Go
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"))
|
|
} |