Files
vibetree/internal/statuses/csv.go
T
Owen f3a959334f Rename project from inventree to vibetree
- go.mod module path (and every internal/vibetree import) inventree -> vibetree
- cmd/inventree/ -> cmd/vibetree/, deploy/inventree.service -> vibetree.service
- Default site name (seed data + the siteName template func's fallback)
  is now "vibetree", shown in the navbar, <title>, and login page
- Session cookie renamed inventree_session -> vibetree_session
- Default DB path data/inventree.db -> data/vibetree.db, CLI/log text,
  Dockerfile, docker-compose.yml, README, .gitignore/.dockerignore all
  updated to match

Existing installs: this changes the session cookie name (users will be
signed out once) and the default DB filename (point -db at your existing
file, or rename it, to keep your data).
2026-08-17 17:14:52 -05:00

100 lines
2.2 KiB
Go

package statuses
import (
"context"
"encoding/csv"
"fmt"
"io"
"strings"
"vibetree/internal/models"
)
type ImportResult struct {
Created int
Updated int
Errors []string
}
// Import creates or updates statuses from CSV data with "Name" and "Color"
// columns (case-insensitive header names, Color optional). Existing
// statuses are matched and updated by name (case-insensitive); everything
// else is created.
func (s *Store) Import(ctx context.Context, r io.Reader) (*ImportResult, error) {
existing, err := s.List(ctx)
if err != nil {
return nil, err
}
byName := map[string]int64{}
for _, st := range existing {
byName[strings.ToLower(st.Name)] = st.ID
}
cr := csv.NewReader(r)
cr.FieldsPerRecord = -1
header, err := cr.Read()
if err != nil {
if err == io.EOF {
return &ImportResult{}, nil
}
return nil, fmt.Errorf("read CSV header: %w", err)
}
colIdx := map[string]int{}
for i, h := range header {
colIdx[strings.ToLower(strings.TrimSpace(h))] = i
}
get := func(row []string, key string) string {
idx, ok := colIdx[key]
if !ok || idx >= len(row) {
return ""
}
return strings.TrimSpace(row[idx])
}
result := &ImportResult{}
rowNum := 1
for {
row, err := cr.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("read CSV row %d: %w", rowNum, err)
}
rowNum++
name := get(row, "name")
if name == "" {
result.Errors = append(result.Errors, fmt.Sprintf("row %d: name is required, skipped", rowNum))
continue
}
color := get(row, "color")
if id, ok := byName[strings.ToLower(name)]; ok {
st, err := s.Get(ctx, id)
if err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("row %d: %v", rowNum, err))
continue
}
if color != "" {
st.Color = color
}
if err := s.Update(ctx, st); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("row %d: update failed: %v", rowNum, err))
continue
}
result.Updated++
continue
}
st := &models.Status{Name: name, Color: color}
if err := s.Create(ctx, st); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("row %d: create failed: %v", rowNum, err))
continue
}
byName[strings.ToLower(name)] = st.ID
result.Created++
}
return result, nil
}