package integration import ( "encoding/json" "log" "net/http" "strconv" "rideaware/internal/config" "rideaware/internal/middleware" ) type IntervalsHandler struct { client *IntervalsClient oauthService *OAuthService } func NewIntervalsHandler() *IntervalsHandler { return &IntervalsHandler{ client: NewIntervalsClient(), oauthService: NewOAuthService(), } } // SaveApiKey POST /api/protected/intervals/connect func (h *IntervalsHandler) SaveApiKey(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"}) return } var req struct { APIKey string `json:"api_key"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.APIKey == "" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "api_key is required"}) return } if err := h.client.SaveApiKey(claims.UserID, req.APIKey); err != nil { log.Printf("Intervals.icu save key error: %v", err) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "failed to save API key"}) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Intervals.icu connected"}) } // ConnectionStatus GET /api/protected/intervals/status func (h *IntervalsHandler) ConnectionStatus(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"}) return } status, err := h.oauthService.GetConnectionStatus(claims.UserID, intervalsProvider) if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(status) } // Disconnect DELETE /api/protected/intervals/disconnect func (h *IntervalsHandler) Disconnect(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"}) return } if err := h.oauthService.Disconnect(claims.UserID, intervalsProvider); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"message": "Intervals.icu disconnected"}) } // PushWorkout POST /api/protected/workouts/push/intervals?id=X func (h *IntervalsHandler) PushWorkout(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"}) return } idStr := r.URL.Query().Get("id") if idStr == "" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "workout id is required"}) return } id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "invalid workout id"}) return } if err := h.client.PushWorkout(uint(id), claims.UserID); err != nil { log.Printf("Intervals.icu push error: %v", err) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"message": "workout pushed to Intervals.icu"}) } // SyncActivities POST /api/protected/intervals/sync?days=30 func (h *IntervalsHandler) SyncActivities(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"}) return } days := 30 if dStr := r.URL.Query().Get("days"); dStr != "" { if d, err := strconv.Atoi(dStr); err == nil && d > 0 { days = d } } imported, err := h.client.SyncActivities(claims.UserID, days) if err != nil { log.Printf("Intervals.icu sync error: %v", err) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]int{"imported": imported}) }