134 lines
3.8 KiB
Go
134 lines
3.8 KiB
Go
package ai
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
|
|
"rideaware/internal/config"
|
|
"rideaware/internal/middleware"
|
|
)
|
|
|
|
type Handler struct {
|
|
service *Service
|
|
}
|
|
|
|
func NewHandler() *Handler {
|
|
return &Handler{
|
|
service: NewService(),
|
|
}
|
|
}
|
|
|
|
// GenerateRecommendations POST /api/protected/ai/generate
|
|
func (h *Handler) GenerateRecommendations(w http.ResponseWriter, r *http.Request) {
|
|
claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims)
|
|
if claims == nil {
|
|
respondError(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
|
|
var req GenerateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
|
|
// Validate request
|
|
if err := validateGenerateRequest(req); err != nil {
|
|
respondError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
log.Printf("[AI] User %d requesting %d-day plan (focus: %v, intensity: %s)",
|
|
claims.UserID, req.PlanDuration, req.FocusAreas, req.IntensityLevel)
|
|
|
|
// Generate workouts
|
|
response, err := h.service.GenerateWorkouts(claims.UserID, req)
|
|
if err != nil {
|
|
log.Printf("[AI] Generation error for user %d: %v", claims.UserID, err)
|
|
respondError(w, http.StatusInternalServerError, "failed to generate workouts: "+err.Error())
|
|
return
|
|
}
|
|
|
|
log.Printf("[AI] Successfully generated %d workouts for user %d (total TSS: %.1f)",
|
|
len(response.Workouts), claims.UserID, response.TotalTSS)
|
|
|
|
respondJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// ScheduleRecommendations POST /api/protected/ai/schedule
|
|
func (h *Handler) ScheduleRecommendations(w http.ResponseWriter, r *http.Request) {
|
|
claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims)
|
|
if claims == nil {
|
|
respondError(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
RecommendationID uint `json:"recommendation_id"`
|
|
WorkoutIndices []int `json:"workout_indices"` // Which workouts to schedule
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
respondError(w, http.StatusBadRequest, "invalid request")
|
|
return
|
|
}
|
|
|
|
if req.RecommendationID == 0 {
|
|
respondError(w, http.StatusBadRequest, "recommendation_id is required")
|
|
return
|
|
}
|
|
|
|
log.Printf("[AI] User %d scheduling %d workouts from recommendation %d",
|
|
claims.UserID, len(req.WorkoutIndices), req.RecommendationID)
|
|
|
|
// Schedule selected workouts
|
|
workouts, err := h.service.ScheduleWorkouts(claims.UserID, req.RecommendationID, req.WorkoutIndices)
|
|
if err != nil {
|
|
log.Printf("[AI] Schedule error for user %d: %v", claims.UserID, err)
|
|
respondError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
log.Printf("[AI] Successfully scheduled %d workouts for user %d", len(workouts), claims.UserID)
|
|
|
|
respondJSON(w, http.StatusCreated, map[string]interface{}{
|
|
"scheduled_count": len(workouts),
|
|
"workouts": workouts,
|
|
})
|
|
}
|
|
|
|
// GetRecommendationHistory GET /api/protected/ai/history
|
|
func (h *Handler) GetRecommendationHistory(w http.ResponseWriter, r *http.Request) {
|
|
claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims)
|
|
if claims == nil {
|
|
respondError(w, http.StatusUnauthorized, "unauthorized")
|
|
return
|
|
}
|
|
|
|
history, err := h.service.GetUserRecommendations(claims.UserID, 10)
|
|
if err != nil {
|
|
log.Printf("[AI] Failed to fetch history for user %d: %v", claims.UserID, err)
|
|
respondError(w, http.StatusInternalServerError, "failed to fetch history")
|
|
return
|
|
}
|
|
|
|
// Return empty array instead of null
|
|
if history == nil {
|
|
history = []AIRecommendation{}
|
|
}
|
|
|
|
respondJSON(w, http.StatusOK, history)
|
|
}
|
|
|
|
// Helper functions
|
|
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
func respondError(w http.ResponseWriter, status int, message string) {
|
|
respondJSON(w, status, map[string]string{"error": message})
|
|
}
|