Skip to content

Commit 3a7a4b6

Browse files
hay-kotOrell Bühler
authored andcommitted
feat: import export rewrite (hay-kot#290)
* WIP: initial work * refactoring * fix failing JS tests * update import docs * fix import headers * fix column headers * update refs on import * remove demo status * finnnneeeee * formatting
1 parent 81dd266 commit 3a7a4b6

39 files changed

+1608
-559
lines changed

backend/app/api/demo.go

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ package main
22

33
import (
44
"context"
5-
"encoding/csv"
65
"strings"
76

87
"github.com/rs/zerolog/log"
98
"github.com/thechosenlan/homebox/backend/internal/core/services"
109
)
1110

1211
func (a *app) SetupDemo() {
13-
csvText := `Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Model Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes
12+
csvText := `HB.import_ref,HB.location,HB.labels,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
1413
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
1514
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
1615
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
@@ -19,13 +18,11 @@ func (a *app) SetupDemo() {
1918
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,‎39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,
2019
`
2120

22-
var (
23-
registration = services.UserRegistration{
24-
25-
Name: "Demo",
26-
Password: "demo",
27-
}
28-
)
21+
registration := services.UserRegistration{
22+
23+
Name: "Demo",
24+
Password: "demo",
25+
}
2926

3027
// First check if we've already setup a demo user and skip if so
3128
_, err := a.services.User.Login(context.Background(), registration.Email, registration.Password)
@@ -42,17 +39,7 @@ func (a *app) SetupDemo() {
4239
token, _ := a.services.User.Login(context.Background(), registration.Email, registration.Password)
4340
self, _ := a.services.User.GetSelf(context.Background(), token.Raw)
4441

45-
// Read CSV Text
46-
reader := csv.NewReader(strings.NewReader(csvText))
47-
reader.Comma = ','
48-
49-
records, err := reader.ReadAll()
50-
if err != nil {
51-
log.Err(err).Msg("Failed to read CSV")
52-
log.Fatal().Msg("Failed to setup demo")
53-
}
54-
55-
_, err = a.services.Items.CsvImport(context.Background(), self.GroupID, records)
42+
_, err = a.services.Items.CsvImport(context.Background(), self.GroupID, strings.NewReader(csvText))
5643
if err != nil {
5744
log.Err(err).Msg("Failed to import CSV")
5845
log.Fatal().Msg("Failed to setup demo")
Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package v1
22

33
import (
4+
"context"
45
"net/http"
56

7+
"github.com/google/uuid"
68
"github.com/rs/zerolog/log"
79
"github.com/thechosenlan/homebox/backend/internal/core/services"
810
"github.com/thechosenlan/homebox/backend/internal/sys/validate"
@@ -13,27 +15,42 @@ type ActionAmountResult struct {
1315
Completed int `json:"completed"`
1416
}
1517

16-
// HandleGroupInvitationsCreate godoc
17-
// @Summary Ensures all items in the database have an asset id
18-
// @Tags Group
19-
// @Produce json
20-
// @Success 200 {object} ActionAmountResult
21-
// @Router /v1/actions/ensure-asset-ids [Post]
22-
// @Security Bearer
23-
func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc {
18+
func actionHandlerFactory(ref string, fn func(context.Context, uuid.UUID) (int, error)) server.HandlerFunc {
2419
return func(w http.ResponseWriter, r *http.Request) error {
2520
ctx := services.NewContext(r.Context())
2621

27-
totalCompleted, err := ctrl.svc.Items.EnsureAssetID(ctx, ctx.GID)
22+
totalCompleted, err := fn(ctx, ctx.GID)
2823
if err != nil {
29-
log.Err(err).Msg("failed to ensure asset id")
24+
log.Err(err).Str("action_ref", ref).Msg("failed to run action")
3025
return validate.NewRequestError(err, http.StatusInternalServerError)
3126
}
3227

3328
return server.Respond(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
3429
}
3530
}
3631

32+
// HandleGroupInvitationsCreate godoc
33+
// @Summary Ensures all items in the database have an asset id
34+
// @Tags Group
35+
// @Produce json
36+
// @Success 200 {object} ActionAmountResult
37+
// @Router /v1/actions/ensure-asset-ids [Post]
38+
// @Security Bearer
39+
func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc {
40+
return actionHandlerFactory("ensure asset IDs", ctrl.svc.Items.EnsureAssetID)
41+
}
42+
43+
// HandleEnsureImportRefs godoc
44+
// @Summary Ensures all items in the database have an import ref
45+
// @Tags Group
46+
// @Produce json
47+
// @Success 200 {object} ActionAmountResult
48+
// @Router /v1/actions/ensure-import-refs [Post]
49+
// @Security Bearer
50+
func (ctrl *V1Controller) HandleEnsureImportRefs() server.HandlerFunc {
51+
return actionHandlerFactory("ensure import refs", ctrl.svc.Items.EnsureImportRef)
52+
}
53+
3754
// HandleItemDateZeroOut godoc
3855
// @Summary Resets all item date fields to the beginning of the day
3956
// @Tags Group
@@ -42,15 +59,5 @@ func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc {
4259
// @Router /v1/actions/zero-item-time-fields [Post]
4360
// @Security Bearer
4461
func (ctrl *V1Controller) HandleItemDateZeroOut() server.HandlerFunc {
45-
return func(w http.ResponseWriter, r *http.Request) error {
46-
ctx := services.NewContext(r.Context())
47-
48-
totalCompleted, err := ctrl.repo.Items.ZeroOutTimeFields(ctx, ctx.GID)
49-
if err != nil {
50-
log.Err(err).Msg("failed to ensure asset id")
51-
return validate.NewRequestError(err, http.StatusInternalServerError)
52-
}
53-
54-
return server.Respond(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
55-
}
62+
return actionHandlerFactory("zero out date time", ctrl.repo.Items.ZeroOutTimeFields)
5663
}

backend/app/api/handlers/v1/v1_ctrl_items.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package v1
22

33
import (
44
"database/sql"
5+
"encoding/csv"
56
"errors"
67
"net/http"
78
"strings"
@@ -255,20 +256,37 @@ func (ctrl *V1Controller) HandleItemsImport() server.HandlerFunc {
255256
return validate.NewRequestError(err, http.StatusInternalServerError)
256257
}
257258

258-
data, err := services.ReadCsv(file)
259+
user := services.UseUserCtx(r.Context())
260+
261+
_, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, file)
259262
if err != nil {
260-
log.Err(err).Msg("failed to read csv")
263+
log.Err(err).Msg("failed to import items")
261264
return validate.NewRequestError(err, http.StatusInternalServerError)
262265
}
263266

264-
user := services.UseUserCtx(r.Context())
267+
return server.Respond(w, http.StatusNoContent, nil)
268+
}
269+
}
265270

266-
_, err = ctrl.svc.Items.CsvImport(r.Context(), user.GroupID, data)
271+
// HandleItemsImport godocs
272+
// @Summary exports items into the database
273+
// @Tags Items
274+
// @Success 200 {string} string "text/csv"
275+
// @Router /v1/items/export [GET]
276+
// @Security Bearer
277+
func (ctrl *V1Controller) HandleItemsExport() server.HandlerFunc {
278+
return func(w http.ResponseWriter, r *http.Request) error {
279+
ctx := services.NewContext(r.Context())
280+
281+
csvData, err := ctrl.svc.Items.ExportTSV(r.Context(), ctx.GID)
267282
if err != nil {
268-
log.Err(err).Msg("failed to import items")
283+
log.Err(err).Msg("failed to export items")
269284
return validate.NewRequestError(err, http.StatusInternalServerError)
270285
}
271286

272-
return server.Respond(w, http.StatusNoContent, nil)
287+
w.Header().Set("Content-Type", "text/tsv")
288+
w.Header().Set("Content-Disposition", "attachment;filename=homebox-items.tsv")
289+
writer := csv.NewWriter(w)
290+
return writer.WriteAll(csvData)
273291
}
274292
}

backend/app/api/handlers/v1/v1_ctrl_reporting.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ func (ctrl *V1Controller) HandleBillOfMaterialsExport() server.HandlerFunc {
1919
return func(w http.ResponseWriter, r *http.Request) error {
2020
actor := services.UseUserCtx(r.Context())
2121

22-
csv, err := ctrl.svc.Reporting.BillOfMaterialsTSV(r.Context(), actor.GroupID)
22+
csv, err := ctrl.svc.Items.ExportBillOfMaterialsTSV(r.Context(), actor.GroupID)
2323
if err != nil {
2424
return err
2525
}
2626

27-
w.Header().Set("Content-Type", "text/csv")
28-
w.Header().Set("Content-Disposition", "attachment; filename=bom.csv")
27+
w.Header().Set("Content-Type", "text/tsv")
28+
w.Header().Set("Content-Disposition", "attachment; filename=bill-of-materials.tsv")
2929
_, err = w.Write(csv)
3030
return err
3131
}

backend/app/api/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func (a *app) mountRoutes(repos *repo.AllRepos) {
8989

9090
a.server.Post(v1Base("/actions/ensure-asset-ids"), v1Ctrl.HandleEnsureAssetID(), userMW...)
9191
a.server.Post(v1Base("/actions/zero-item-time-fields"), v1Ctrl.HandleItemDateZeroOut(), userMW...)
92+
a.server.Post(v1Base("/actions/ensure-import-refs"), v1Ctrl.HandleEnsureImportRefs(), userMW...)
9293

9394
a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), userMW...)
9495
a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), userMW...)
@@ -106,6 +107,7 @@ func (a *app) mountRoutes(repos *repo.AllRepos) {
106107
a.server.Get(v1Base("/items"), v1Ctrl.HandleItemsGetAll(), userMW...)
107108
a.server.Post(v1Base("/items"), v1Ctrl.HandleItemsCreate(), userMW...)
108109
a.server.Post(v1Base("/items/import"), v1Ctrl.HandleItemsImport(), userMW...)
110+
a.server.Get(v1Base("/items/export"), v1Ctrl.HandleItemsExport(), userMW...)
109111
a.server.Get(v1Base("/items/fields"), v1Ctrl.HandleGetAllCustomFieldNames(), userMW...)
110112
a.server.Get(v1Base("/items/fields/values"), v1Ctrl.HandleGetAllCustomFieldValues(), userMW...)
111113

backend/app/api/static/docs/docs.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,30 @@ const docTemplate = `{
4545
}
4646
}
4747
},
48+
"/v1/actions/ensure-import-refs": {
49+
"post": {
50+
"security": [
51+
{
52+
"Bearer": []
53+
}
54+
],
55+
"produces": [
56+
"application/json"
57+
],
58+
"tags": [
59+
"Group"
60+
],
61+
"summary": "Ensures all items in the database have an import ref",
62+
"responses": {
63+
"200": {
64+
"description": "OK",
65+
"schema": {
66+
"$ref": "#/definitions/v1.ActionAmountResult"
67+
}
68+
}
69+
}
70+
}
71+
},
4872
"/v1/actions/zero-item-time-fields": {
4973
"post": {
5074
"security": [
@@ -401,6 +425,27 @@ const docTemplate = `{
401425
}
402426
}
403427
},
428+
"/v1/items/export": {
429+
"get": {
430+
"security": [
431+
{
432+
"Bearer": []
433+
}
434+
],
435+
"tags": [
436+
"Items"
437+
],
438+
"summary": "exports items into the database",
439+
"responses": {
440+
"200": {
441+
"description": "text/csv",
442+
"schema": {
443+
"type": "string"
444+
}
445+
}
446+
}
447+
}
448+
},
404449
"/v1/items/fields": {
405450
"get": {
406451
"security": [

backend/app/api/static/docs/swagger.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,30 @@
3737
}
3838
}
3939
},
40+
"/v1/actions/ensure-import-refs": {
41+
"post": {
42+
"security": [
43+
{
44+
"Bearer": []
45+
}
46+
],
47+
"produces": [
48+
"application/json"
49+
],
50+
"tags": [
51+
"Group"
52+
],
53+
"summary": "Ensures all items in the database have an import ref",
54+
"responses": {
55+
"200": {
56+
"description": "OK",
57+
"schema": {
58+
"$ref": "#/definitions/v1.ActionAmountResult"
59+
}
60+
}
61+
}
62+
}
63+
},
4064
"/v1/actions/zero-item-time-fields": {
4165
"post": {
4266
"security": [
@@ -393,6 +417,27 @@
393417
}
394418
}
395419
},
420+
"/v1/items/export": {
421+
"get": {
422+
"security": [
423+
{
424+
"Bearer": []
425+
}
426+
],
427+
"tags": [
428+
"Items"
429+
],
430+
"summary": "exports items into the database",
431+
"responses": {
432+
"200": {
433+
"description": "text/csv",
434+
"schema": {
435+
"type": "string"
436+
}
437+
}
438+
}
439+
}
440+
},
396441
"/v1/items/fields": {
397442
"get": {
398443
"security": [

backend/app/api/static/docs/swagger.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,20 @@ paths:
640640
summary: Ensures all items in the database have an asset id
641641
tags:
642642
- Group
643+
/v1/actions/ensure-import-refs:
644+
post:
645+
produces:
646+
- application/json
647+
responses:
648+
"200":
649+
description: OK
650+
schema:
651+
$ref: '#/definitions/v1.ActionAmountResult'
652+
security:
653+
- Bearer: []
654+
summary: Ensures all items in the database have an import ref
655+
tags:
656+
- Group
643657
/v1/actions/zero-item-time-fields:
644658
post:
645659
produces:
@@ -1095,6 +1109,18 @@ paths:
10951109
summary: Update Maintenance Entry
10961110
tags:
10971111
- Maintenance
1112+
/v1/items/export:
1113+
get:
1114+
responses:
1115+
"200":
1116+
description: text/csv
1117+
schema:
1118+
type: string
1119+
security:
1120+
- Bearer: []
1121+
summary: exports items into the database
1122+
tags:
1123+
- Items
10981124
/v1/items/fields:
10991125
get:
11001126
produces:

0 commit comments

Comments
 (0)