Files
MyClub/internal/controllers/export_helper.go
T
Tomas Dvorak dfc079288f hot fix #1
2026-01-26 08:13:18 +01:00

166 lines
4.0 KiB
Go

package controllers
import (
"encoding/csv"
"encoding/json"
"fmt"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/xuri/excelize/v2"
)
// ExportHelper provides export utilities for CSV, JSON, etc.
type ExportHelper struct{}
// NewExportHelper creates a new ExportHelper instance
func NewExportHelper() *ExportHelper {
return &ExportHelper{}
}
// ExportToCSV exports data to CSV format
func (eh *ExportHelper) ExportToCSV(c *gin.Context, data interface{}, filename string, headers []string) error {
c.Header("Content-Type", "text/csv")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
writer := csv.NewWriter(c.Writer)
defer writer.Flush()
// Write headers
if err := writer.Write(headers); err != nil {
return err
}
// Convert data to slice of records
records, err := eh.convertToCSVRecords(data)
if err != nil {
return err
}
// Write records
for _, record := range records {
if err := writer.Write(record); err != nil {
return err
}
}
return nil
}
// ExportToJSON exports data to JSON format
func (eh *ExportHelper) ExportToJSON(c *gin.Context, data interface{}, filename string) error {
c.Header("Content-Type", "application/json")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
return json.NewEncoder(c.Writer).Encode(data)
}
// convertToCSVRecords converts a slice of structs to CSV records
func (eh *ExportHelper) convertToCSVRecords(data interface{}) ([][]string, error) {
v := reflect.ValueOf(data)
if v.Kind() != reflect.Slice {
return nil, fmt.Errorf("data must be a slice")
}
records := make([][]string, 0, v.Len())
for i := 0; i < v.Len(); i++ {
item := v.Index(i)
if item.Kind() == reflect.Ptr {
item = item.Elem()
}
record := make([]string, 0, item.NumField())
for j := 0; j < item.NumField(); j++ {
field := item.Field(j)
record = append(record, eh.formatFieldValue(field))
}
records = append(records, record)
}
return records, nil
}
// formatFieldValue formats a field value for CSV export
func (eh *ExportHelper) formatFieldValue(v reflect.Value) string {
switch v.Kind() {
case reflect.String:
return v.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(v.Uint(), 10)
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(v.Float(), 'f', -1, 64)
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.Struct:
// Handle time.Time
if t, ok := v.Interface().(time.Time); ok {
return t.Format(time.RFC3339)
}
return fmt.Sprintf("%v", v.Interface())
case reflect.Ptr:
if v.IsNil() {
return ""
}
return eh.formatFieldValue(v.Elem())
default:
return fmt.Sprintf("%v", v.Interface())
}
}
// ImportFromCSV imports tabular data from an uploaded file.
// It supports traditional CSV files as well as Excel workbooks (.xlsx).
// The caller always receives a [][]string where the first row is treated
// as the header row by higher-level import functions.
func (eh *ExportHelper) ImportFromCSV(c *gin.Context, formFieldName string) ([][]string, error) {
file, err := c.FormFile(formFieldName)
if err != nil {
return nil, err
}
f, err := file.Open()
if err != nil {
return nil, err
}
defer f.Close()
ext := strings.ToLower(filepath.Ext(file.Filename))
if ext == ".xlsx" {
wb, err := excelize.OpenReader(f)
if err != nil {
return nil, err
}
defer func() {
_ = wb.Close()
}()
sheets := wb.GetSheetList()
if len(sheets) == 0 {
return nil, fmt.Errorf("xlsx file has no sheets")
}
rows, err := wb.GetRows(sheets[0])
if err != nil {
return nil, err
}
return rows, nil
}
reader := csv.NewReader(f)
records, err := reader.ReadAll()
if err != nil {
return nil, err
}
return records, nil
}
// Global ExportHelper instance
var Exporter = NewExportHelper()