internal/handler/export_handler.go
Rama main · réplica de lectura del repositorio original.
package handler
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/johnzastrow/actalog/internal/service"
"github.com/johnzastrow/actalog/pkg/middleware"
)
// ExportHandler handles data export endpoints
type ExportHandler struct {
exportService *service.ExportService
}
// NewExportHandler creates a new export handler
func NewExportHandler(exportService *service.ExportService) *ExportHandler {
return &ExportHandler{
exportService: exportService,
}
}
// ExportWODs exports WODs to CSV or JSON format
// @Summary Export WODs
// @Description Export WODs to CSV or JSON format with optional filters
// @Tags import-export
// @Accept json
// @Produce text/csv,application/json
// @Security BearerAuth
// @Param include_standard query bool false "Include standard WODs (default true)"
// @Param include_custom query bool false "Include custom WODs (default true)"
// @Param format query string false "Export format: csv or json (default csv)"
// @Success 200 {file} file "Exported file (CSV or JSON)"
// @Failure 401 {object} ErrorResponse "Unauthorized"
// @Failure 500 {object} ErrorResponse "Internal server error"
// @Router /export/wods [get]
func (h *ExportHandler) ExportWODs(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 query parameters
includeStandard := parseBoolParam(r.URL.Query().Get("include_standard"), true)
includeCustom := parseBoolParam(r.URL.Query().Get("include_custom"), true)
format := r.URL.Query().Get("format")
if format == "" {
format = "csv" // default to CSV for backwards compatibility
}
var data []byte
var err error
var contentType, filename string
// Generate export based on format
if format == "json" {
data, err = h.exportService.ExportWODsToJSON(userID, isAdmin, includeStandard, includeCustom)
contentType = "application/json"
filename = "wods_export.json"
} else {
data, err = h.exportService.ExportWODsToCSV(userID, isAdmin, includeStandard, includeCustom)
contentType = "text/csv"
filename = "wods_export.csv"
}
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to export WODs: %v", err))
return
}
// Set response headers for download
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
// Write data
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
}
// ExportMovements exports movements to CSV or JSON format
// @Summary Export movements
// @Description Export movements to CSV or JSON format with optional filters
// @Tags import-export
// @Accept json
// @Produce text/csv,application/json
// @Security BearerAuth
// @Param include_standard query bool false "Include standard movements (default true)"
// @Param include_custom query bool false "Include custom movements (default true)"
// @Param format query string false "Export format: csv or json (default csv)"
// @Success 200 {file} file "Exported file (CSV or JSON)"
// @Failure 401 {object} ErrorResponse "Unauthorized"
// @Failure 500 {object} ErrorResponse "Internal server error"
// @Router /export/movements [get]
func (h *ExportHandler) ExportMovements(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 query parameters
includeStandard := parseBoolParam(r.URL.Query().Get("include_standard"), true)
includeCustom := parseBoolParam(r.URL.Query().Get("include_custom"), true)
format := r.URL.Query().Get("format")
if format == "" {
format = "csv" // default to CSV for backwards compatibility
}
var data []byte
var err error
var contentType, filename string
// Generate export based on format
if format == "json" {
data, err = h.exportService.ExportMovementsToJSON(userID, isAdmin, includeStandard, includeCustom)
contentType = "application/json"
filename = "movements_export.json"
} else {
data, err = h.exportService.ExportMovementsToCSV(userID, isAdmin, includeStandard, includeCustom)
contentType = "text/csv"
filename = "movements_export.csv"
}
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to export movements: %v", err))
return
}
// Set response headers for download
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
// Write data
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
}
// Helper function to parse boolean query parameters
func parseBoolParam(value string, defaultValue bool) bool {
if value == "" {
return defaultValue
}
b, err := strconv.ParseBool(value)
if err != nil {
return defaultValue
}
return b
}
// ExportUserWorkouts exports user workouts to CSV or JSON format
// @Summary Export user workouts
// @Description Export user's logged workouts to CSV or JSON format with optional date range
// @Tags import-export
// @Accept json
// @Produce text/csv,application/json
// @Security BearerAuth
// @Param start_date query string false "Start date (YYYY-MM-DD)"
// @Param end_date query string false "End date (YYYY-MM-DD)"
// @Param format query string false "Export format: csv or json (default json)"
// @Success 200 {file} file "Exported file (CSV or JSON)"
// @Failure 400 {object} ErrorResponse "Invalid date format"
// @Failure 401 {object} ErrorResponse "Unauthorized"
// @Failure 500 {object} ErrorResponse "Internal server error"
// @Router /export/user-workouts [get]
func (h *ExportHandler) ExportUserWorkouts(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 query parameters for date range
startDateStr := r.URL.Query().Get("start_date")
endDateStr := r.URL.Query().Get("end_date")
format := r.URL.Query().Get("format")
if format == "" {
format = "json" // default to JSON for backwards compatibility
}
var startDate, endDate *time.Time
if startDateStr != "" {
parsed, err := time.Parse("2006-01-02", startDateStr)
if err != nil {
respondError(w, http.StatusBadRequest, "Invalid start_date format. Use YYYY-MM-DD")
return
}
startDate = &parsed
}
if endDateStr != "" {
parsed, err := time.Parse("2006-01-02", endDateStr)
if err != nil {
respondError(w, http.StatusBadRequest, "Invalid end_date format. Use YYYY-MM-DD")
return
}
endDate = &parsed
}
// Both start and end date must be provided together, or neither
if (startDate != nil && endDate == nil) || (startDate == nil && endDate != nil) {
respondError(w, http.StatusBadRequest, "Both start_date and end_date must be provided together")
return
}
var data []byte
var err error
var contentType, filename, fileExtension string
// Generate export based on format
if format == "csv" {
data, err = h.exportService.ExportUserWorkoutsToCSV(userID, startDate, endDate)
contentType = "text/csv"
fileExtension = "csv"
} else {
data, err = h.exportService.ExportUserWorkoutsToJSON(userID, startDate, endDate)
contentType = "application/json"
fileExtension = "json"
}
if err != nil {
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to export user workouts: %v", err))
return
}
// Determine filename based on date range
if startDate != nil && endDate != nil {
filename = fmt.Sprintf("user_workouts_%s_to_%s.%s",
startDate.Format("2006-01-02"),
endDate.Format("2006-01-02"),
fileExtension)
} else {
filename = fmt.Sprintf("user_workouts_export.%s", fileExtension)
}
// Set response headers for download
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
// Write data
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
}