diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index f6bc5ff..1873d8b 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -1,7 +1,6 @@ package v1 import ( - "encoding/csv" "net/http" "github.com/hay-kot/homebox/backend/internal/core/services" @@ -178,8 +177,7 @@ func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc { return validate.NewRequestError(err, http.StatusInternalServerError) } - reader := csv.NewReader(file) - data, err := reader.ReadAll() + data, err := services.ReadCsv(file) if err != nil { log.Err(err).Msg("failed to read csv") return validate.NewRequestError(err, http.StatusInternalServerError) diff --git a/backend/internal/core/services/service_items_csv.go b/backend/internal/core/services/service_items_csv.go index c9748f7..fb5e36a 100644 --- a/backend/internal/core/services/service_items_csv.go +++ b/backend/internal/core/services/service_items_csv.go @@ -1,7 +1,10 @@ package services import ( + "bytes" + "encoding/csv" "errors" + "io" "strconv" "strings" "time" @@ -9,6 +12,44 @@ import ( "github.com/hay-kot/homebox/backend/internal/data/repo" ) +func determineSeparator(data []byte) (rune, error) { + // First row + firstRow := bytes.Split(data, []byte("\n"))[0] + + // find first comma or /t + comma := bytes.IndexByte(firstRow, ',') + tab := bytes.IndexByte(firstRow, '\t') + + switch { + case comma == -1 && tab == -1: + return 0, errors.New("could not determine separator") + case tab > comma: + return '\t', nil + default: + return ',', nil + } +} + +func ReadCsv(r io.Reader) ([][]string, error) { + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + reader := csv.NewReader(bytes.NewReader(data)) + + // Determine separator + sep, err := determineSeparator(data) + + if err != nil { + return nil, err + } + + reader.Comma = sep + + return reader.ReadAll() +} + var ErrInvalidCsv = errors.New("invalid csv") const NumOfCols = 21 diff --git a/backend/internal/core/services/service_items_csv_test.go b/backend/internal/core/services/service_items_csv_test.go index b5b488c..675a1a9 100644 --- a/backend/internal/core/services/service_items_csv_test.go +++ b/backend/internal/core/services/service_items_csv_test.go @@ -2,6 +2,7 @@ package services import ( "bytes" + _ "embed" "encoding/csv" "fmt" "reflect" @@ -11,17 +12,14 @@ import ( "github.com/stretchr/testify/assert" ) -const CSV_DATA = ` -Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Mode Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes -A,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,Description 1,TRUE,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,10/13/2021,,,,10/13/2021, -B,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,Description 2,FALSE,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,10/15/2021,,,,10/15/2021, -C,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,Description 3,TRUE,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,10/13/2021,,,,10/13/2021, -D,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,Description 4,FALSE,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,10/21/2020,,,,10/21/2020, -E,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,Description 5,TRUE,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,10/14/2020,,,,10/14/2020, -F,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,Description 6,FALSE,,39351,Honeywell,,Amazon,65.98,09/30/2020,,09/30/2020,,,,09/30/2020,` +//go:embed testdata/import.csv +var CSVData_Comma []byte + +//go:embed testdata/import.tsv +var CSVData_Tab []byte func loadcsv() [][]string { - reader := csv.NewReader(bytes.NewBuffer([]byte(CSV_DATA))) + reader := csv.NewReader(bytes.NewReader(CSVData_Comma)) records, err := reader.ReadAll() if err != nil { @@ -115,3 +113,52 @@ func Test_csvRow_getLabels(t *testing.T) { }) } } + +func Test_determineSeparator(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + args args + want rune + wantErr bool + }{ + { + name: "comma", + args: args{ + data: CSVData_Comma, + }, + want: ',', + wantErr: false, + }, + { + name: "tab", + args: args{ + data: CSVData_Tab, + }, + want: '\t', + wantErr: false, + }, + { + name: "invalid", + args: args{ + data: []byte("a;b;c"), + }, + want: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := determineSeparator(tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("determineSeparator() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("determineSeparator() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/backend/internal/core/services/testdata/import.csv b/backend/internal/core/services/testdata/import.csv new file mode 100644 index 0000000..08bd9c8 --- /dev/null +++ b/backend/internal/core/services/testdata/import.csv @@ -0,0 +1,7 @@ +Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Mode Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes +A,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,Description 1,TRUE,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,10/13/2021,,,,10/13/2021, +B,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,Description 2,FALSE,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,10/15/2021,,,,10/15/2021, +C,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,Description 3,TRUE,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,10/13/2021,,,,10/13/2021, +D,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,Description 4,FALSE,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,10/21/2020,,,,10/21/2020, +E,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,Description 5,TRUE,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,10/14/2020,,,,10/14/2020, +F,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,Description 6,FALSE,,39351,Honeywell,,Amazon,65.98,09/30/2020,,09/30/2020,,,,09/30/2020, \ No newline at end of file diff --git a/backend/internal/core/services/testdata/import.tsv b/backend/internal/core/services/testdata/import.tsv new file mode 100644 index 0000000..503c777 --- /dev/null +++ b/backend/internal/core/services/testdata/import.tsv @@ -0,0 +1,7 @@ +Import Ref Location Labels Quantity Name Description Insured Serial Number Mode Number Manufacturer Notes Purchase From Purchased Price Purchased Time Lifetime Warranty Warranty Expires Warranty Details Sold To Sold Price Sold Time Sold Notes +A Garage IOT;Home Assistant; Z-Wave 1 Zooz Universal Relay ZEN17 Description 1 TRUE ZEN17 Zooz Amazon 39.95 10/13/2021 10/13/2021 10/13/2021 +B Living Room IOT;Home Assistant; Z-Wave 1 Zooz Motion Sensor Description 2 FALSE ZSE18 Zooz Amazon 29.95 10/15/2021 10/15/2021 10/15/2021 +C Office IOT;Home Assistant; Z-Wave 1 Zooz 110v Power Switch Description 3 TRUE ZEN15 Zooz Amazon 39.95 10/13/2021 10/13/2021 10/13/2021 +D Downstairs IOT;Home Assistant; Z-Wave 1 Ecolink Z-Wave PIR Motion Sensor Description 4 FALSE PIRZWAVE2.5-ECO Ecolink Amazon 35.58 10/21/2020 10/21/2020 10/21/2020 +E Entry IOT;Home Assistant; Z-Wave 1 Yale Security Touchscreen Deadbolt Description 5 TRUE YRD226ZW2619 Yale Amazon 120.39 10/14/2020 10/14/2020 10/14/2020 +F Kitchen IOT;Home Assistant; Z-Wave 1 Smart Rocker Light Dimmer Description 6 FALSE 39351 Honeywell Amazon 65.98 09/30/2020 09/30/2020 09/30/2020 \ No newline at end of file diff --git a/frontend/pages/home.vue b/frontend/pages/home.vue index 0113a0b..7723d95 100644 --- a/frontend/pages/home.vue +++ b/frontend/pages/home.vue @@ -100,7 +100,7 @@
- +