package handler import ( "fmt" "io" "net/http" "github.com/johnzastrow/actalog/internal/service" "github.com/johnzastrow/actalog/pkg/middleware" ) // ImportHandler handles data import endpoints type ImportHandler struct { importService *service.ImportService } // NewImportHandler creates a new import handler func NewImportHandler(importService *service.ImportService) *ImportHandler { return &ImportHandler{ importService: importService, } } // ImportWODsPreviewRequest represents the request body for confirming WOD import type ImportWODsConfirmRequest struct { SkipDuplicates bool `json:"skip_duplicates"` UpdateDuplicates bool `json:"update_duplicates"` CSVData string `json:"csv_data"` // Base64 encoded or raw CSV string } // ImportMovementsConfirmRequest represents the request body for confirming movement import type ImportMovementsConfirmRequest struct { SkipDuplicates bool `json:"skip_duplicates"` UpdateDuplicates bool `json:"update_duplicates"` CSVData string `json:"csv_data"` // Base64 encoded or raw CSV string } const maxUploadSize = 10 * 1024 * 1024 // 10MB // PreviewWODImport validates and previews WOD CSV import // @Summary Preview WOD import // @Description Validate and preview WOD CSV import before confirming // @Tags import-export // @Accept multipart/form-data // @Produce json // @Security BearerAuth // @Param file formData file true "CSV file to import" // @Success 200 {object} map[string]interface{} "Import preview with validation results" // @Failure 400 {object} ErrorResponse "Invalid file or validation failed" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Router /import/wods/preview [post] func (h *ImportHandler) PreviewWODImport(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 } // Check if user is admin role, ok := middleware.GetUserRole(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } isAdmin := role == "admin" // Parse multipart form if err := r.ParseMultipartForm(maxUploadSize); err != nil { respondError(w, http.StatusBadRequest, "Failed to parse multipart form") return } // Get file from form file, _, err := r.FormFile("file") if err != nil { respondError(w, http.StatusBadRequest, "No file uploaded") return } defer file.Close() // Preview import result, err := h.importService.PreviewWODImport(file, userID, isAdmin) if err != nil { respondError(w, http.StatusBadRequest, fmt.Sprintf("Import preview failed: %v", err)) return } respondJSON(w, http.StatusOK, result) } // ConfirmWODImport confirms and executes WOD CSV import // @Summary Confirm WOD import // @Description Execute WOD CSV import after preview // @Tags import-export // @Accept multipart/form-data // @Produce json // @Security BearerAuth // @Param file formData file true "CSV file to import" // @Param skip_duplicates formData bool false "Skip duplicate entries" // @Param update_duplicates formData bool false "Update existing duplicates" // @Success 200 {object} map[string]interface{} "Import results with counts" // @Failure 400 {object} ErrorResponse "Invalid file" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 500 {object} ErrorResponse "Import failed" // @Router /import/wods/confirm [post] func (h *ImportHandler) ConfirmWODImport(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 } // Check if user is admin role, ok := middleware.GetUserRole(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } isAdmin := role == "admin" // Parse multipart form if err := r.ParseMultipartForm(maxUploadSize); err != nil { respondError(w, http.StatusBadRequest, "Failed to parse multipart form") return } // Get file from form file, _, err := r.FormFile("file") if err != nil { respondError(w, http.StatusBadRequest, "No file uploaded") return } defer file.Close() // Parse options from form data skipDuplicates := r.FormValue("skip_duplicates") == "true" updateDuplicates := r.FormValue("update_duplicates") == "true" // Confirm import result, err := h.importService.ConfirmWODImport(file, userID, isAdmin, skipDuplicates, updateDuplicates) if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Import failed: %v", err)) return } respondJSON(w, http.StatusOK, result) } // PreviewMovementImport validates and previews movement CSV import // @Summary Preview movement import // @Description Validate and preview movement CSV import before confirming // @Tags import-export // @Accept multipart/form-data // @Produce json // @Security BearerAuth // @Param file formData file true "CSV file to import" // @Success 200 {object} map[string]interface{} "Import preview with validation results" // @Failure 400 {object} ErrorResponse "Invalid file or validation failed" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Router /import/movements/preview [post] func (h *ImportHandler) PreviewMovementImport(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 } // Check if user is admin role, ok := middleware.GetUserRole(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } isAdmin := role == "admin" // Parse multipart form if err := r.ParseMultipartForm(maxUploadSize); err != nil { respondError(w, http.StatusBadRequest, "Failed to parse multipart form") return } // Get file from form file, _, err := r.FormFile("file") if err != nil { respondError(w, http.StatusBadRequest, "No file uploaded") return } defer file.Close() // Preview import result, err := h.importService.PreviewMovementImport(file, userID, isAdmin) if err != nil { respondError(w, http.StatusBadRequest, fmt.Sprintf("Import preview failed: %v", err)) return } respondJSON(w, http.StatusOK, result) } // ConfirmMovementImport confirms and executes movement CSV import // @Summary Confirm movement import // @Description Execute movement CSV import after preview // @Tags import-export // @Accept multipart/form-data // @Produce json // @Security BearerAuth // @Param file formData file true "CSV file to import" // @Param skip_duplicates formData bool false "Skip duplicate entries" // @Param update_duplicates formData bool false "Update existing duplicates" // @Success 200 {object} map[string]interface{} "Import results with counts" // @Failure 400 {object} ErrorResponse "Invalid file" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 500 {object} ErrorResponse "Import failed" // @Router /import/movements/confirm [post] func (h *ImportHandler) ConfirmMovementImport(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 } // Check if user is admin role, ok := middleware.GetUserRole(r.Context()) if !ok { respondError(w, http.StatusUnauthorized, "Unauthorized") return } isAdmin := role == "admin" // Parse multipart form if err := r.ParseMultipartForm(maxUploadSize); err != nil { respondError(w, http.StatusBadRequest, "Failed to parse multipart form") return } // Get file from form file, _, err := r.FormFile("file") if err != nil { respondError(w, http.StatusBadRequest, "No file uploaded") return } defer file.Close() // Parse options from form data skipDuplicates := r.FormValue("skip_duplicates") == "true" updateDuplicates := r.FormValue("update_duplicates") == "true" // Confirm import result, err := h.importService.ConfirmMovementImport(file, userID, isAdmin, skipDuplicates, updateDuplicates) if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Import failed: %v", err)) return } respondJSON(w, http.StatusOK, result) } // PreviewUserWorkoutImport validates and previews user workout JSON import // @Summary Preview user workout import // @Description Validate and preview user workout JSON import before confirming // @Tags import-export // @Accept multipart/form-data // @Produce json // @Security BearerAuth // @Param file formData file true "JSON file to import" // @Success 200 {object} map[string]interface{} "Import preview with validation results" // @Failure 400 {object} ErrorResponse "Invalid file or validation failed" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Router /import/user-workouts/preview [post] func (h *ImportHandler) PreviewUserWorkoutImport(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 multipart form if err := r.ParseMultipartForm(maxUploadSize); err != nil { respondError(w, http.StatusBadRequest, "Failed to parse multipart form") return } // Get file from form file, _, err := r.FormFile("file") if err != nil { respondError(w, http.StatusBadRequest, "No file uploaded") return } defer file.Close() // Read file content jsonData, err := io.ReadAll(file) if err != nil { respondError(w, http.StatusBadRequest, "Failed to read file") return } // Preview import result, err := h.importService.PreviewUserWorkoutImport(jsonData, userID) if err != nil { respondError(w, http.StatusBadRequest, fmt.Sprintf("Import preview failed: %v", err)) return } respondJSON(w, http.StatusOK, result) } // ConfirmUserWorkoutImport confirms and executes user workout JSON import // @Summary Confirm user workout import // @Description Execute user workout JSON import after preview // @Tags import-export // @Accept multipart/form-data // @Produce json // @Security BearerAuth // @Param file formData file true "JSON file to import" // @Param skip_duplicates formData bool false "Skip duplicate entries" // @Param update_duplicates formData bool false "Update existing duplicates" // @Success 200 {object} map[string]interface{} "Import results with counts" // @Failure 400 {object} ErrorResponse "Invalid file" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 500 {object} ErrorResponse "Import failed" // @Router /import/user-workouts/confirm [post] func (h *ImportHandler) ConfirmUserWorkoutImport(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 multipart form if err := r.ParseMultipartForm(maxUploadSize); err != nil { respondError(w, http.StatusBadRequest, "Failed to parse multipart form") return } // Get file from form file, _, err := r.FormFile("file") if err != nil { respondError(w, http.StatusBadRequest, "No file uploaded") return } defer file.Close() // Read file content jsonData, err := io.ReadAll(file) if err != nil { respondError(w, http.StatusBadRequest, "Failed to read file") return } // Parse options from form data skipDuplicates := r.FormValue("skip_duplicates") == "true" updateDuplicates := r.FormValue("update_duplicates") == "true" // Confirm import result, err := h.importService.ConfirmUserWorkoutImport(jsonData, userID, skipDuplicates, updateDuplicates) if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Import failed: %v", err)) return } respondJSON(w, http.StatusOK, result) }