diff --git a/backend/app/api/handlers/v1/controller.go b/backend/app/api/handlers/v1/controller.go index 33fdd631..135b1446 100644 --- a/backend/app/api/handlers/v1/controller.go +++ b/backend/app/api/handlers/v1/controller.go @@ -57,6 +57,12 @@ func WithSecureCookies(secure bool) func(*V1Controller) { } } +func WithURL(url string) func(*V1Controller) { + return func(ctrl *V1Controller) { + ctrl.url = url + } +} + type V1Controller struct { cookieSecure bool repo *repo.AllRepos @@ -65,6 +71,7 @@ type V1Controller struct { isDemo bool allowRegistration bool bus *eventbus.EventBus + url string } type ( diff --git a/backend/app/api/handlers/v1/v1_ctrl_items.go b/backend/app/api/handlers/v1/v1_ctrl_items.go index a978d031..1eddc0fc 100644 --- a/backend/app/api/handlers/v1/v1_ctrl_items.go +++ b/backend/app/api/handlers/v1/v1_ctrl_items.go @@ -6,6 +6,7 @@ import ( "errors" "math/big" "net/http" + "net/url" "strings" "github.com/google/uuid" @@ -333,7 +334,7 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { ctx := services.NewContext(r.Context()) - csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID) + csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID, getHBURL(r.Header.Get("Referer"), ctrl.url)) if err != nil { log.Err(err).Msg("failed to export items") return validate.NewRequestError(err, http.StatusInternalServerError) @@ -347,3 +348,26 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc { return writer.WriteAll(csvData) } } + +func getHBURL(refererHeader, fallback string) (hbURL string) { + hbURL = refererHeader + if hbURL == "" { + hbURL = fallback + } + + return stripPathFromURL(hbURL) +} + +// stripPathFromURL removes the path from a URL. +// ex. https://example.com/tools -> https://example.com +func stripPathFromURL(rawURL string) string { + parsedURL, err := url.Parse(rawURL) + if err != nil { + log.Err(err).Msg("failed to parse URL") + return "" + } + + strippedURL := url.URL{Scheme: parsedURL.Scheme, Host: parsedURL.Host} + + return strippedURL.String() +} diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 551582e0..faca9906 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -3,6 +3,7 @@ package main import ( "embed" "errors" + "fmt" "io" "mime" "net/http" @@ -54,6 +55,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize), v1.WithRegistration(a.conf.Options.AllowRegistration), v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode + v1.WithURL(fmt.Sprintf("%s:%s", a.conf.Web.Host, a.conf.Web.Port)), ) r.Route(prefix+"/v1", func(r chi.Router) { diff --git a/backend/internal/core/services/reporting/io_row.go b/backend/internal/core/services/reporting/io_row.go index ca3c8010..1e3415b4 100644 --- a/backend/internal/core/services/reporting/io_row.go +++ b/backend/internal/core/services/reporting/io_row.go @@ -18,6 +18,7 @@ type ExportCSVRow struct { LabelStr LabelString `csv:"HB.labels"` AssetID repo.AssetID `csv:"HB.asset_id"` Archived bool `csv:"HB.archived"` + URL string `csv:"HB.url"` Name string `csv:"HB.name"` Quantity int `csv:"HB.quantity"` diff --git a/backend/internal/core/services/reporting/io_sheet.go b/backend/internal/core/services/reporting/io_sheet.go index 08676bd0..21d07d1b 100644 --- a/backend/internal/core/services/reporting/io_sheet.go +++ b/backend/internal/core/services/reporting/io_sheet.go @@ -153,7 +153,7 @@ func (s *IOSheet) Read(data io.Reader) error { } // ReadItems writes the sheet to a writer. -func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos) error { +func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos, hbURL string) error { s.Rows = make([]ExportCSVRow, len(items)) extraHeaders := map[string]struct{}{} @@ -178,6 +178,8 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid. labelString[i] = l.Name } + url := generateItemURL(item, hbURL) + customFields := make([]ExportItemFields, len(item.Fields)) for i, f := range item.Fields { @@ -201,6 +203,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid. Description: item.Description, Insured: item.Insured, Archived: item.Archived, + URL: url, PurchasePrice: item.PurchasePrice, PurchaseFrom: item.PurchaseFrom, @@ -252,6 +255,14 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid. return nil } +func generateItemURL(item repo.ItemOut, d string) string { + url := "" + if item.ID != uuid.Nil { + url = fmt.Sprintf("%s/item/%s", d, item.ID.String()) + } + return url +} + // CSV writes the current sheet to a 2d array, for compatibility with TSV/CSV files. func (s *IOSheet) CSV() ([][]string, error) { memcsv := make([][]string, len(s.Rows)+1) diff --git a/backend/internal/core/services/service_items.go b/backend/internal/core/services/service_items.go index d6f1896c..88b7e28a 100644 --- a/backend/internal/core/services/service_items.go +++ b/backend/internal/core/services/service_items.go @@ -329,7 +329,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re return finished, nil } -func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID) ([][]string, error) { +func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID, hbURL string) ([][]string, error) { items, err := svc.repo.Items.GetAll(ctx, GID) if err != nil { return nil, err @@ -337,7 +337,7 @@ func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID) ([][]strin sheet := reporting.IOSheet{} - err = sheet.ReadItems(ctx, items, GID, svc.repo) + err = sheet.ReadItems(ctx, items, GID, svc.repo, hbURL) if err != nil { return nil, err }