172 lines
3.8 KiB
Go
172 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type ImportResult struct {
|
|
Inserted int `json:"inserted"`
|
|
Skipped int `json:"skipped"`
|
|
Errors []string `json:"errors"`
|
|
}
|
|
|
|
func (app *App) handleImport(w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
|
writeError(w, "invalid form", http.StatusBadRequest)
|
|
return
|
|
}
|
|
file, _, err := r.FormFile("csv")
|
|
if err != nil {
|
|
writeError(w, "csv file required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
result, err := app.importCSV(file)
|
|
if err != nil {
|
|
writeError(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if result.Errors == nil {
|
|
result.Errors = []string{}
|
|
}
|
|
writeJSON(w, result)
|
|
}
|
|
|
|
func (app *App) importCSV(r io.Reader) (ImportResult, error) {
|
|
reader := csv.NewReader(r)
|
|
reader.TrimLeadingSpace = true
|
|
reader.LazyQuotes = true
|
|
|
|
header, err := reader.Read()
|
|
if err != nil {
|
|
return ImportResult{}, fmt.Errorf("reading header: %w", err)
|
|
}
|
|
if len(header) > 0 {
|
|
header[0] = strings.TrimPrefix(header[0], "\xef\xbb\xbf") // strip BOM
|
|
}
|
|
|
|
colIndex := make(map[string]int)
|
|
for i, h := range header {
|
|
colIndex[strings.ToLower(strings.TrimSpace(h))] = i
|
|
}
|
|
|
|
var (
|
|
nameIdx, emailIdx, ticketIDIdx, ticketTypeIdx int
|
|
hasEmail, hasTicketID, hasTicketType bool
|
|
isCrowdWork bool
|
|
)
|
|
|
|
if idx, ok := colIndex["patron name"]; ok {
|
|
// CrowdWork / ticketing platform format
|
|
isCrowdWork = true
|
|
nameIdx = idx
|
|
if idx, ok := colIndex["patron email"]; ok {
|
|
emailIdx, hasEmail = idx, true
|
|
}
|
|
if idx, ok := colIndex["tier name"]; ok {
|
|
ticketTypeIdx, hasTicketType = idx, true
|
|
}
|
|
if idx, ok := colIndex["order number"]; ok {
|
|
ticketIDIdx, hasTicketID = idx, true
|
|
}
|
|
} else if idx, ok := colIndex["name"]; ok {
|
|
// Generic format
|
|
nameIdx = idx
|
|
if idx, ok := colIndex["email"]; ok {
|
|
emailIdx, hasEmail = idx, true
|
|
}
|
|
if idx, ok := colIndex["ticket_id"]; ok {
|
|
ticketIDIdx, hasTicketID = idx, true
|
|
}
|
|
if idx, ok := colIndex["ticket_type"]; ok {
|
|
ticketTypeIdx, hasTicketType = idx, true
|
|
}
|
|
} else {
|
|
return ImportResult{}, fmt.Errorf("CSV must have a 'name' or 'patron name' column")
|
|
}
|
|
|
|
var result ImportResult
|
|
lineNum := 1
|
|
|
|
for {
|
|
record, err := reader.Read()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
lineNum++
|
|
if err != nil {
|
|
result.Errors = append(result.Errors, fmt.Sprintf("line %d: %v", lineNum, err))
|
|
continue
|
|
}
|
|
|
|
name := strings.TrimSpace(csvGet(record, nameIdx))
|
|
if name == "" {
|
|
continue
|
|
}
|
|
|
|
email := ""
|
|
if hasEmail {
|
|
email = strings.TrimSpace(csvGet(record, emailIdx))
|
|
}
|
|
externalID := ""
|
|
if hasTicketID {
|
|
externalID = strings.TrimSpace(csvGet(record, ticketIDIdx))
|
|
}
|
|
ticketType := ""
|
|
if hasTicketType {
|
|
ticketType = strings.TrimSpace(csvGet(record, ticketTypeIdx))
|
|
}
|
|
|
|
source := "manual"
|
|
orderID := ""
|
|
if isCrowdWork {
|
|
source = "crowdwork"
|
|
orderID = externalID
|
|
}
|
|
|
|
// Find or create participant when email is present.
|
|
var participantID *int
|
|
if email != "" {
|
|
p, _, err := app.upsertParticipant(email, name)
|
|
if err != nil {
|
|
result.Errors = append(result.Errors, fmt.Sprintf("line %d (%s): participant: %v", lineNum, name, err))
|
|
continue
|
|
}
|
|
if p != nil {
|
|
participantID = &p.ID
|
|
}
|
|
}
|
|
|
|
_, err = app.createTicket(Ticket{
|
|
ParticipantID: participantID,
|
|
Name: name,
|
|
TicketType: ticketType,
|
|
Source: source,
|
|
ExternalID: externalID,
|
|
OrderID: orderID,
|
|
})
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
|
|
result.Skipped++
|
|
} else {
|
|
result.Errors = append(result.Errors, fmt.Sprintf("line %d (%s): %v", lineNum, name, err))
|
|
}
|
|
continue
|
|
}
|
|
result.Inserted++
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func csvGet(record []string, idx int) string {
|
|
if idx < len(record) {
|
|
return record[idx]
|
|
}
|
|
return ""
|
|
}
|