Ctrl K
ComunidadGuías…

internal/handler/workout_handler.go.deprecated

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

import (
	"encoding/json"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"
	"github.com/johnzastrow/actalog/internal/domain"
	"github.com/johnzastrow/actalog/internal/service"
	"github.com/johnzastrow/actalog/pkg/middleware"
)

// WorkoutHandler handles workout template-related endpoints
type WorkoutHandler struct {
	workoutService *service.WorkoutService
}

// NewWorkoutHandler creates a new workout handler
func NewWorkoutHandler(workoutService *service.WorkoutService) *WorkoutHandler {
	return &WorkoutHandler{
		workoutService: workoutService,
	}
}

// CreateTemplateRequest represents a request to create a workout template
type CreateTemplateRequest struct {
	Name      string                         `json:"name"`
	Notes     *string                        `json:"notes,omitempty"`
	Movements []CreateWorkoutMovementRequest `json:"movements,omitempty"`
	WODs      []CreateWorkoutWODRequest      `json:"wods,omitempty"`
}

// CreateWorkoutMovementRequest represents a movement in a workout template
type CreateWorkoutMovementRequest struct {
	MovementID int64    `json:"movement_id"`
	Weight     *float64 `json:"weight,omitempty"`
	Sets       *int     `json:"sets,omitempty"`
	Reps       *int     `json:"reps,omitempty"`
	Time       *int     `json:"time,omitempty"`
	Distance   *float64 `json:"distance,omitempty"`
	IsRx       bool     `json:"is_rx"`
	Notes      *string  `json:"notes,omitempty"`
}

// CreateWorkoutWODRequest represents a WOD in a workout template
type CreateWorkoutWODRequest struct {
	WODID      int64   `json:"wod_id"`
	ScoreValue *string `json:"score_value,omitempty"`
	Division   *string `json:"division,omitempty"`
}

// UpdateTemplateRequest represents a request to update a workout template
type UpdateTemplateRequest struct {
	Name      string                         `json:"name"`
	Notes     *string                        `json:"notes,omitempty"`
	Movements []CreateWorkoutMovementRequest `json:"movements,omitempty"`
	WODs      []CreateWorkoutWODRequest      `json:"wods,omitempty"`
}

// TemplateResponse represents a workout template
type TemplateResponse struct {
	ID        int64                          `json:"id"`
	Name      string                         `json:"name"`
	Notes     *string                        `json:"notes,omitempty"`
	CreatedBy *int64                         `json:"created_by,omitempty"`
	CreatedAt string                         `json:"created_at"`
	UpdatedAt string                         `json:"updated_at"`
	Movements []*domain.WorkoutMovement      `json:"movements,omitempty"`
	WODs      []*domain.WorkoutWODWithDetails `json:"wods,omitempty"`
}

// CreateTemplate creates a new workout template
func (h *WorkoutHandler) CreateTemplate(w http.ResponseWriter, r *http.Request) {
	// Extract user ID from JWT token in context
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	var req CreateTemplateRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	// Validate required fields
	if req.Name == "" {
		respondError(w, http.StatusBadRequest, "Template name is required")
		return
	}

	// Build workout template
	workout := &domain.Workout{
		Name:  req.Name,
		Notes: req.Notes,
	}

	// Build movements
	if len(req.Movements) > 0 {
		for _, mv := range req.Movements {
			var notes string
			if mv.Notes != nil {
				notes = *mv.Notes
			}
			workoutMovement := &domain.WorkoutMovement{
				MovementID: mv.MovementID,
				Weight:     mv.Weight,
				Sets:       mv.Sets,
				Reps:       mv.Reps,
				Time:       mv.Time,
				Distance:   mv.Distance,
				IsRx:       mv.IsRx,
				Notes:      notes,
			}
			workout.Movements = append(workout.Movements, workoutMovement)
		}
	}

	// Build WODs
	if len(req.WODs) > 0 {
		for _, wod := range req.WODs {
			workoutWOD := &domain.WorkoutWODWithDetails{
				WorkoutWOD: domain.WorkoutWOD{
					WODID:      wod.WODID,
					ScoreValue: wod.ScoreValue,
					Division:   wod.Division,
				},
			}
			workout.WODs = append(workout.WODs, workoutWOD)
		}
	}

	// Create template
	if err := h.workoutService.CreateTemplate(userID, workout); err != nil {
		respondError(w, http.StatusInternalServerError, "Failed to create workout template: "+err.Error())
		return
	}

	// Retrieve created template with details
	template, err := h.workoutService.GetTemplate(workout.ID)
	if err != nil {
		respondError(w, http.StatusInternalServerError, "Failed to retrieve created template")
		return
	}

	response := TemplateResponse{
		ID:        template.ID,
		Name:      template.Name,
		Notes:     template.Notes,
		CreatedBy: template.CreatedBy,
		CreatedAt: template.CreatedAt.Format("2006-01-02T15:04:05Z"),
		UpdatedAt: template.UpdatedAt.Format("2006-01-02T15:04:05Z"),
		Movements: template.Movements,
		WODs:      template.WODs,
	}

	respondJSON(w, http.StatusCreated, response)
}

// GetTemplate retrieves a workout template by ID
func (h *WorkoutHandler) GetTemplate(w http.ResponseWriter, r *http.Request) {
	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid template ID")
		return
	}

	template, err := h.workoutService.GetTemplate(id)
	if err != nil {
		if err == service.ErrWorkoutNotFound {
			respondError(w, http.StatusNotFound, "Template not found")
		} else {
			respondError(w, http.StatusInternalServerError, "Failed to retrieve template")
		}
		return
	}

	response := TemplateResponse{
		ID:        template.ID,
		Name:      template.Name,
		Notes:     template.Notes,
		CreatedBy: template.CreatedBy,
		CreatedAt: template.CreatedAt.Format("2006-01-02T15:04:05Z"),
		UpdatedAt: template.UpdatedAt.Format("2006-01-02T15:04:05Z"),
		Movements: template.Movements,
		WODs:      template.WODs,
	}

	respondJSON(w, http.StatusOK, response)
}

// ListTemplates retrieves all workout templates (standard + user's)
func (h *WorkoutHandler) ListTemplates(w http.ResponseWriter, r *http.Request) {
	// Extract user ID from JWT token in context (optional for browsing standard templates)
	userID, ok := middleware.GetUserID(r.Context())

	// Parse pagination parameters
	limitStr := r.URL.Query().Get("limit")
	offsetStr := r.URL.Query().Get("offset")

	limit := 50 // default
	offset := 0

	if limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
			limit = l
		}
	}

	if offsetStr != "" {
		if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
			offset = o
		}
	}

	var templates []*domain.Workout
	var err error

	if ok {
		templates, err = h.workoutService.ListTemplates(&userID, limit, offset)
	} else {
		templates, err = h.workoutService.ListTemplates(nil, limit, offset)
	}

	if err != nil {
		respondError(w, http.StatusInternalServerError, "Failed to retrieve templates")
		return
	}

	// Build response
	var responses []TemplateResponse
	for _, template := range templates {
		response := TemplateResponse{
			ID:        template.ID,
			Name:      template.Name,
			Notes:     template.Notes,
			CreatedBy: template.CreatedBy,
			CreatedAt: template.CreatedAt.Format("2006-01-02T15:04:05Z"),
			UpdatedAt: template.UpdatedAt.Format("2006-01-02T15:04:05Z"),
			Movements: template.Movements,
			WODs:      template.WODs,
		}
		responses = append(responses, response)
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"templates": responses,
		"limit":     limit,
		"offset":    offset,
	})
}

// UpdateTemplate updates a workout template
func (h *WorkoutHandler) UpdateTemplate(w http.ResponseWriter, r *http.Request) {
	// Extract user ID from JWT token in context
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid template ID")
		return
	}

	var req UpdateTemplateRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	// Build update
	workout := &domain.Workout{
		Name:  req.Name,
		Notes: req.Notes,
	}

	// Build movements if provided
	if req.Movements != nil {
		for _, mv := range req.Movements {
			var notes string
			if mv.Notes != nil {
				notes = *mv.Notes
			}
			workoutMovement := &domain.WorkoutMovement{
				MovementID: mv.MovementID,
				Weight:     mv.Weight,
				Sets:       mv.Sets,
				Reps:       mv.Reps,
				Time:       mv.Time,
				Distance:   mv.Distance,
				IsRx:       mv.IsRx,
				Notes:      notes,
			}
			workout.Movements = append(workout.Movements, workoutMovement)
		}
	}

	// Build WODs if provided
	if req.WODs != nil {
		for _, wod := range req.WODs {
			workoutWOD := &domain.WorkoutWODWithDetails{
				WorkoutWOD: domain.WorkoutWOD{
					WODID:      wod.WODID,
					ScoreValue: wod.ScoreValue,
					Division:   wod.Division,
				},
			}
			workout.WODs = append(workout.WODs, workoutWOD)
		}
	}

	// Update template
	if err := h.workoutService.UpdateTemplate(id, userID, workout); err != nil {
		if err == service.ErrUnauthorized {
			respondError(w, http.StatusForbidden, "You don't have permission to update this template")
		} else if err == service.ErrWorkoutNotFound {
			respondError(w, http.StatusNotFound, "Template not found")
		} else {
			respondError(w, http.StatusInternalServerError, "Failed to update template: "+err.Error())
		}
		return
	}

	// Retrieve updated template
	template, err := h.workoutService.GetTemplate(id)
	if err != nil {
		respondError(w, http.StatusInternalServerError, "Failed to retrieve updated template")
		return
	}

	response := TemplateResponse{
		ID:        template.ID,
		Name:      template.Name,
		Notes:     template.Notes,
		CreatedBy: template.CreatedBy,
		CreatedAt: template.CreatedAt.Format("2006-01-02T15:04:05Z"),
		UpdatedAt: template.UpdatedAt.Format("2006-01-02T15:04:05Z"),
		Movements: template.Movements,
		WODs:      template.WODs,
	}

	respondJSON(w, http.StatusOK, response)
}

// DeleteTemplate deletes a workout template
func (h *WorkoutHandler) DeleteTemplate(w http.ResponseWriter, r *http.Request) {
	// Extract user ID from JWT token in context
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid template ID")
		return
	}

	if err := h.workoutService.DeleteTemplate(id, userID); err != nil {
		if err == service.ErrUnauthorized {
			respondError(w, http.StatusForbidden, "You don't have permission to delete this template")
		} else if err == service.ErrWorkoutNotFound {
			respondError(w, http.StatusNotFound, "Template not found")
		} else {
			respondError(w, http.StatusInternalServerError, "Failed to delete template")
		}
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{"message": "Template deleted successfully"})
}

// GetTemplateStats retrieves usage statistics for a template
func (h *WorkoutHandler) GetTemplateStats(w http.ResponseWriter, r *http.Request) {
	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid template ID")
		return
	}

	stats, err := h.workoutService.GetTemplateUsageStats(id)
	if err != nil {
		if err == service.ErrWorkoutNotFound {
			respondError(w, http.StatusNotFound, "Template not found")
		} else {
			respondError(w, http.StatusInternalServerError, "Failed to retrieve template stats")
		}
		return
	}

	respondJSON(w, http.StatusOK, stats)
}

// GetPersonalRecords retrieves all personal records for a user
func (h *WorkoutHandler) GetPersonalRecords(w http.ResponseWriter, r *http.Request) {
	// Extract user ID from JWT token in context
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	records, err := h.workoutService.GetPersonalRecords(userID)
	if err != nil {
		respondError(w, http.StatusInternalServerError, "Failed to retrieve personal records")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"records": records,
	})
}

// GetPRMovements retrieves recent PR-flagged movements for a user
func (h *WorkoutHandler) GetPRMovements(w http.ResponseWriter, r *http.Request) {
	// Extract user ID from JWT token in context
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	// Parse limit
	limitStr := r.URL.Query().Get("limit")
	limit := 10 // default
	if limitStr != "" {
		if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
			limit = l
		}
	}

	movements, err := h.workoutService.GetPRMovements(userID, limit)
	if err != nil {
		respondError(w, http.StatusInternalServerError, "Failed to retrieve PR movements")
		return
	}

	respondJSON(w, http.StatusOK, map[string]interface{}{
		"pr_movements": movements,
	})
}

// TogglePRFlag manually toggles the PR flag on a workout movement
func (h *WorkoutHandler) TogglePRFlag(w http.ResponseWriter, r *http.Request) {
	// Extract user ID from JWT token in context
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		respondError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	movementIDStr := chi.URLParam(r, "id")
	movementID, err := strconv.ParseInt(movementIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid movement ID")
		return
	}

	err = h.workoutService.TogglePRFlag(movementID, userID)
	if err != nil {
		if err == service.ErrUnauthorized {
			respondError(w, http.StatusForbidden, "Unauthorized")
		} else {
			respondError(w, http.StatusInternalServerError, "Failed to toggle PR flag")
		}
		return
	}

	respondJSON(w, http.StatusOK, map[string]string{"message": "PR flag toggled successfully"})
}