internal/handler/import_handler.go
Rama main · réplica de lectura del repositorio original.
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)
}