Skip to content

Commit fba6d78

Browse files
add label generation api (#498)
* add label generation api * show location name on labels * add label scan page * dispose of code reader when navigating away from scan page * save label to png * implement code suggestions * fix label padding and margin * update swagger docs * add print from browser dialog Co-authored-by: fidoriel <49869342+fidoriel@users.noreply.github.com> * increase label description font weight * update documentation label file suffix * fix scanner components import * fix linting issues --------- Co-authored-by: fidoriel <49869342+fidoriel@users.noreply.github.com>
1 parent 401fd7f commit fba6d78

File tree

24 files changed

+1233
-50
lines changed

24 files changed

+1233
-50
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
1414
"github.com/sysadminsmedia/homebox/backend/internal/core/services/reporting/eventbus"
1515
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
16+
"github.com/sysadminsmedia/homebox/backend/internal/sys/config"
1617

1718
"github.com/olahol/melody"
1819
)
@@ -72,6 +73,7 @@ type V1Controller struct {
7273
allowRegistration bool
7374
bus *eventbus.EventBus
7475
url string
76+
config *config.Config
7577
}
7678

7779
type (
@@ -92,15 +94,17 @@ type (
9294
Latest services.Latest `json:"latest"`
9395
Demo bool `json:"demo"`
9496
AllowRegistration bool `json:"allowRegistration"`
97+
LabelPrinting bool `json:"labelPrinting"`
9598
}
9699
)
97100

98-
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, options ...func(*V1Controller)) *V1Controller {
101+
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, config *config.Config, options ...func(*V1Controller)) *V1Controller {
99102
ctrl := &V1Controller{
100103
repo: repos,
101104
svc: svc,
102105
allowRegistration: true,
103106
bus: bus,
107+
config: config,
104108
}
105109

106110
for _, opt := range options {
@@ -127,6 +131,7 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.Hand
127131
Latest: ctrl.svc.BackgroundService.GetLatestVersion(),
128132
Demo: ctrl.isDemo,
129133
AllowRegistration: ctrl.allowRegistration,
134+
LabelPrinting: ctrl.config.LabelMaker.PrintCommand != nil,
130135
})
131136
}
132137
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package v1
2+
3+
import (
4+
"net/url"
5+
6+
"github.com/rs/zerolog/log"
7+
)
8+
9+
func GetHBURL(refererHeader, fallback string) (hbURL string) {
10+
hbURL = refererHeader
11+
if hbURL == "" {
12+
hbURL = fallback
13+
}
14+
15+
return stripPathFromURL(hbURL)
16+
}
17+
18+
// stripPathFromURL removes the path from a URL.
19+
// ex. https://example.com/tools -> https://example.com
20+
func stripPathFromURL(rawURL string) string {
21+
parsedURL, err := url.Parse(rawURL)
22+
if err != nil {
23+
log.Err(err).Msg("failed to parse URL")
24+
return ""
25+
}
26+
27+
strippedURL := url.URL{Scheme: parsedURL.Scheme, Host: parsedURL.Host}
28+
29+
return strippedURL.String()
30+
}

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

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"fmt"
88
"math/big"
99
"net/http"
10-
"net/url"
1110
"strings"
1211
"time"
1312

@@ -339,7 +338,7 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
339338
return func(w http.ResponseWriter, r *http.Request) error {
340339
ctx := services.NewContext(r.Context())
341340

342-
csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID, getHBURL(r.Header.Get("Referer"), ctrl.url))
341+
csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID, GetHBURL(r.Header.Get("Referer"), ctrl.url))
343342
if err != nil {
344343
log.Err(err).Msg("failed to export items")
345344
return validate.NewRequestError(err, http.StatusInternalServerError)
@@ -356,26 +355,3 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
356355
return writer.WriteAll(csvData)
357356
}
358357
}
359-
360-
func getHBURL(refererHeader, fallback string) (hbURL string) {
361-
hbURL = refererHeader
362-
if hbURL == "" {
363-
hbURL = fallback
364-
}
365-
366-
return stripPathFromURL(hbURL)
367-
}
368-
369-
// stripPathFromURL removes the path from a URL.
370-
// ex. https://example.com/tools -> https://example.com
371-
func stripPathFromURL(rawURL string) string {
372-
parsedURL, err := url.Parse(rawURL)
373-
if err != nil {
374-
log.Err(err).Msg("failed to parse URL")
375-
return ""
376-
}
377-
378-
strippedURL := url.URL{Scheme: parsedURL.Scheme, Host: parsedURL.Host}
379-
380-
return strippedURL.String()
381-
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package v1
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/go-chi/chi/v5"
10+
"github.com/hay-kot/httpkit/errchain"
11+
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
12+
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
13+
"github.com/sysadminsmedia/homebox/backend/internal/sys/validate"
14+
"github.com/sysadminsmedia/homebox/backend/internal/web/adapters"
15+
"github.com/sysadminsmedia/homebox/backend/pkgs/labelmaker"
16+
)
17+
18+
func generateOrPrint(ctrl *V1Controller, w http.ResponseWriter, r *http.Request, title string, description string, url string) error {
19+
params := labelmaker.NewGenerateParams(int(ctrl.config.LabelMaker.Width), int(ctrl.config.LabelMaker.Height), int(ctrl.config.LabelMaker.Margin), int(ctrl.config.LabelMaker.Padding), ctrl.config.LabelMaker.FontSize, title, description, url)
20+
21+
print := queryBool(r.URL.Query().Get("print"))
22+
23+
if print {
24+
err := labelmaker.PrintLabel(ctrl.config, &params)
25+
if err != nil {
26+
return err
27+
}
28+
29+
_, err = w.Write([]byte("Printed!"))
30+
return err
31+
} else {
32+
return labelmaker.GenerateLabel(w, &params)
33+
}
34+
}
35+
36+
// HandleGetLocationLabel godoc
37+
//
38+
// @Summary Get Location label
39+
// @Tags Locations
40+
// @Produce json
41+
// @Param id path string true "Location ID"
42+
// @Param print query bool false "Print this label, defaults to false"
43+
// @Success 200 {string} string "image/png"
44+
// @Router /v1/labelmaker/location/{id} [GET]
45+
// @Security Bearer
46+
func (ctrl *V1Controller) HandleGetLocationLabel() errchain.HandlerFunc {
47+
return func(w http.ResponseWriter, r *http.Request) error {
48+
ID, err := adapters.RouteUUID(r, "id")
49+
if err != nil {
50+
return err
51+
}
52+
53+
auth := services.NewContext(r.Context())
54+
location, err := ctrl.repo.Locations.GetOneByGroup(auth, auth.GID, ID)
55+
if err != nil {
56+
return err
57+
}
58+
59+
hbURL := GetHBURL(r.Header.Get("Referer"), ctrl.url)
60+
return generateOrPrint(ctrl, w, r, location.Name, "Homebox Location", fmt.Sprintf("%s/location/%s", hbURL, location.ID))
61+
}
62+
}
63+
64+
// HandleGetItemLabel godoc
65+
//
66+
// @Summary Get Item label
67+
// @Tags Items
68+
// @Produce json
69+
// @Param id path string true "Item ID"
70+
// @Param print query bool false "Print this label, defaults to false"
71+
// @Success 200 {string} string "image/png"
72+
// @Router /v1/labelmaker/item/{id} [GET]
73+
// @Security Bearer
74+
func (ctrl *V1Controller) HandleGetItemLabel() errchain.HandlerFunc {
75+
return func(w http.ResponseWriter, r *http.Request) error {
76+
ID, err := adapters.RouteUUID(r, "id")
77+
if err != nil {
78+
return err
79+
}
80+
81+
auth := services.NewContext(r.Context())
82+
item, err := ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
83+
if err != nil {
84+
return err
85+
}
86+
87+
description := ""
88+
89+
if item.Location != nil {
90+
description += fmt.Sprintf("\nLocation: %s", item.Location.Name)
91+
}
92+
93+
hbURL := GetHBURL(r.Header.Get("Referer"), ctrl.url)
94+
return generateOrPrint(ctrl, w, r, item.Name, description, fmt.Sprintf("%s/item/%s", hbURL, item.ID))
95+
}
96+
}
97+
98+
// HandleGetAssetLabel godoc
99+
//
100+
// @Summary Get Asset label
101+
// @Tags Items
102+
// @Produce json
103+
// @Param id path string true "Asset ID"
104+
// @Param print query bool false "Print this label, defaults to false"
105+
// @Success 200 {string} string "image/png"
106+
// @Router /v1/labelmaker/assets/{id} [GET]
107+
// @Security Bearer
108+
func (ctrl *V1Controller) HandleGetAssetLabel() errchain.HandlerFunc {
109+
return func(w http.ResponseWriter, r *http.Request) error {
110+
assetIDParam := chi.URLParam(r, "id")
111+
assetIDParam = strings.ReplaceAll(assetIDParam, "-", "")
112+
assetID, err := strconv.ParseInt(assetIDParam, 10, 64)
113+
if err != nil {
114+
return err
115+
}
116+
117+
auth := services.NewContext(r.Context())
118+
item, err := ctrl.repo.Items.QueryByAssetID(auth, auth.GID, repo.AssetID(assetID), 0, 1)
119+
if err != nil {
120+
return err
121+
}
122+
123+
if len(item.Items) == 0 {
124+
return validate.NewRequestError(fmt.Errorf("failed to find asset id"), http.StatusNotFound)
125+
}
126+
127+
description := item.Items[0].Name
128+
129+
if item.Items[0].Location != nil {
130+
description += fmt.Sprintf("\nLocation: %s", item.Items[0].Location.Name)
131+
}
132+
133+
hbURL := GetHBURL(r.Header.Get("Referer"), ctrl.url)
134+
return generateOrPrint(ctrl, w, r, item.Items[0].AssetID.String(), description, fmt.Sprintf("%s/a/%s", hbURL, item.Items[0].AssetID.String()))
135+
}
136+
}

backend/app/api/routes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
5252
a.services,
5353
a.repos,
5454
a.bus,
55+
a.conf,
5556
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
5657
v1.WithRegistration(a.conf.Options.AllowRegistration),
5758
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
@@ -161,6 +162,11 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
161162
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
162163
)
163164

165+
// Labelmaker
166+
r.Get("/labelmaker/location/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGetLocationLabel(), userMW...))
167+
r.Get("/labelmaker/item/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGetItemLabel(), userMW...))
168+
r.Get("/labelmaker/asset/{id}", chain.ToHandlerFunc(v1Ctrl.HandleGetAssetLabel(), userMW...))
169+
164170
// Reporting Services
165171
r.Get("/reporting/bill-of-materials", chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))
166172

0 commit comments

Comments
 (0)