mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-24 14:31:55 +01:00
ProductBarcode: add first backend API implementation
This commit is contained in:
@@ -2,12 +2,15 @@ package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/hay-kot/httpkit/errchain"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/web/adapters"
|
||||
"github.com/yeqown/go-qrcode/v2"
|
||||
"github.com/yeqown/go-qrcode/writer/standard"
|
||||
@@ -70,3 +73,265 @@ func (ctrl *V1Controller) HandleGenerateQRCode() errchain.HandlerFunc {
|
||||
return qrc.Save(qrwriter)
|
||||
}
|
||||
}
|
||||
|
||||
type BarcodeProduct struct {
|
||||
SearchEngineName string `json:"search_engine_name"`
|
||||
|
||||
//Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
//Description string `json:"description" validate:"max=1000"`
|
||||
|
||||
// Identifications
|
||||
ModelNumber string `json:"modelNumber"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
|
||||
// Extras
|
||||
Country string `json:"notes"`
|
||||
Barcode string `json:"barcode"`
|
||||
|
||||
// TODO: add image attachement
|
||||
// TODO: add asin?
|
||||
ImageURL string `json:"imageURL"`
|
||||
|
||||
item repo.ItemCreate
|
||||
}
|
||||
|
||||
/*
|
||||
ImportRef string `json:"-"`
|
||||
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable"`
|
||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
Quantity int `json:"quantity"`
|
||||
Description string `json:"description" validate:"max=1000"`
|
||||
AssetID AssetID `json:"-"`
|
||||
|
||||
// Edges
|
||||
LocationID uuid.UUID `json:"locationId"`
|
||||
LabelIDs []uuid.UUID `json:"labelIds"`
|
||||
*/
|
||||
|
||||
type UPCITEMDBResponse struct {
|
||||
Code string `json:"code"`
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Items []struct {
|
||||
Ean string `json:"ean"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Upc string `json:"upc"`
|
||||
Brand string `json:"brand"`
|
||||
Model string `json:"model"`
|
||||
Color string `json:"color"`
|
||||
Size string `json:"size"`
|
||||
Dimension string `json:"dimension"`
|
||||
Weight string `json:"weight"`
|
||||
Category string `json:"category"`
|
||||
Currency string `json:"currency"`
|
||||
LowestRecordedPrice float64 `json:"lowest_recorded_price"`
|
||||
HighestRecordedPrice float64 `json:"highest_recorded_price"`
|
||||
Images []string `json:"images"`
|
||||
Offers []struct {
|
||||
Merchant string `json:"merchant"`
|
||||
Domain string `json:"domain"`
|
||||
Title string `json:"title"`
|
||||
Currency string `json:"currency"`
|
||||
ListPrice string `json:"list_price"`
|
||||
Price float64 `json:"price"`
|
||||
Shipping string `json:"shipping"`
|
||||
Condition string `json:"condition"`
|
||||
Availability string `json:"availability"`
|
||||
Link string `json:"link"`
|
||||
UpdatedT int `json:"updated_t"`
|
||||
} `json:"offers"`
|
||||
} `json:"items"`
|
||||
}
|
||||
|
||||
type BARCODESPIDER_COMResponse struct {
|
||||
ItemResponse struct {
|
||||
Code int `json:"code"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
} `json:"item_response"`
|
||||
ItemAttributes struct {
|
||||
Title string `json:"title"`
|
||||
Upc string `json:"upc"`
|
||||
Ean string `json:"ean"`
|
||||
ParentCategory string `json:"parent_category"`
|
||||
Category string `json:"category"`
|
||||
Brand string `json:"brand"`
|
||||
Model string `json:"model"`
|
||||
Mpn string `json:"mpn"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
Publisher string `json:"publisher"`
|
||||
Asin string `json:"asin"`
|
||||
Color string `json:"color"`
|
||||
Size string `json:"size"`
|
||||
Weight string `json:"weight"`
|
||||
Image string `json:"image"`
|
||||
IsAdult string `json:"is_adult"`
|
||||
Description string `json:"description"`
|
||||
} `json:"item_attributes"`
|
||||
Stores []struct {
|
||||
StoreName string `json:"store_name"`
|
||||
Title string `json:"title"`
|
||||
Image string `json:"image"`
|
||||
Price string `json:"price"`
|
||||
Currency string `json:"currency"`
|
||||
Link string `json:"link"`
|
||||
Updated string `json:"updated"`
|
||||
} `json:"Stores"`
|
||||
}
|
||||
|
||||
// HandleGenerateQRCode godoc
|
||||
//
|
||||
// @Summary Search EAN from Barcode
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param data query string false "barcode to be searched"
|
||||
// @Success 200 {object} repo.ItemCreate
|
||||
// @Router /v1/getproductfromean [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleProductSearchEAN() errchain.HandlerFunc {
|
||||
type query struct {
|
||||
// 4,296 characters is the maximum length of a QR code
|
||||
EAN string `schema:"productEAN" validate:"required,max=4296"`
|
||||
}
|
||||
|
||||
/*fn := func(r *http.Request, ID uuid.UUID) (repo.ItemOut, error) {
|
||||
auth := services.NewContext(r.Context())
|
||||
|
||||
return ctrl.repo.Items.GetOneByGroup(auth, auth.GID, ID)
|
||||
}
|
||||
|
||||
return adapters.CommandID("id", fn, http.StatusOK)*/
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
q, err := adapters.DecodeQuery[query](r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msg("========================" + q.EAN)
|
||||
|
||||
// Search on UPCITEMDB
|
||||
var products []BarcodeProduct
|
||||
|
||||
// www.ean-search.org/: not free
|
||||
|
||||
// Example code: dewalt 5035048748428
|
||||
|
||||
upcitemdb := func(iEan string) ([]BarcodeProduct, error) {
|
||||
resp, err := http.Get("https://api.upcitemdb.com/prod/trial/lookup?upc=" + iEan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//We Read the response body on the line below.
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Convert the body to type string
|
||||
sb := string(body)
|
||||
log.Info().Msg("Response: " + sb)
|
||||
|
||||
var result UPCITEMDBResponse
|
||||
if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer
|
||||
log.Error().Msg("Can not unmarshal JSON")
|
||||
}
|
||||
|
||||
var res []BarcodeProduct
|
||||
|
||||
for _, it := range result.Items {
|
||||
var p BarcodeProduct
|
||||
p.SearchEngineName = "upcitemdb.com"
|
||||
p.Barcode = iEan
|
||||
|
||||
p.item.Description = it.Description
|
||||
p.item.Name = it.Title
|
||||
p.Manufacturer = it.Brand
|
||||
p.ModelNumber = it.Model
|
||||
if len(it.Images) != 0 {
|
||||
p.ImageURL = it.Images[0]
|
||||
}
|
||||
|
||||
res = append(res, p)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
ps, err := upcitemdb(q.EAN)
|
||||
if err != nil {
|
||||
log.Error().Msg("Can not retrieve product from upcitemdb.com" + err.Error())
|
||||
}
|
||||
|
||||
// Barcode spider: Freetoken: 43866b1fa5d558a2bd12
|
||||
barcodespider := func(iEan string) ([]BarcodeProduct, error) {
|
||||
req, err := http.NewRequest(
|
||||
"GET", "https://api.barcodespider.com/v1/lookup?upc="+iEan, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("token", "43866b1fa5d558a2bd12")
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//We Read the response body on the line below.
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Convert the body to type string
|
||||
sb := string(body)
|
||||
log.Info().Msg("Response: " + sb)
|
||||
|
||||
var result BARCODESPIDER_COMResponse
|
||||
if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer
|
||||
log.Error().Msg("Can not unmarshal JSON")
|
||||
}
|
||||
|
||||
// TODO: check 200 code on HTTP repsonse.
|
||||
var p BarcodeProduct
|
||||
p.Barcode = iEan
|
||||
p.SearchEngineName = "barcodespider.com"
|
||||
p.item.Name = result.ItemAttributes.Title
|
||||
p.item.Description = result.ItemAttributes.Description
|
||||
p.Manufacturer = result.ItemAttributes.Brand
|
||||
p.ModelNumber = result.ItemAttributes.Model
|
||||
p.ImageURL = result.ItemAttributes.Image
|
||||
|
||||
var res []BarcodeProduct
|
||||
res = append(res, p)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
ps2, err := barcodespider(q.EAN)
|
||||
if err != nil {
|
||||
log.Error().Msg("Can not retrieve product from barcodespider.com" + err.Error())
|
||||
}
|
||||
|
||||
// Merge everything.
|
||||
for i := range ps {
|
||||
products = append(products, ps[i])
|
||||
}
|
||||
|
||||
for i := range ps2 {
|
||||
products = append(products, ps2[i])
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if len(products) != 0 {
|
||||
return json.NewEncoder(w).Encode(products[0].item)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
|
||||
}
|
||||
|
||||
r.Get("/qrcode", chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...))
|
||||
r.Get("/getproductfromean", chain.ToHandlerFunc(v1Ctrl.HandleProductSearchEAN(), userMW...))
|
||||
r.Get(
|
||||
"/items/{id}/attachments/{attachment_id}",
|
||||
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
|
||||
|
||||
@@ -195,6 +195,38 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/getproductfromean": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Items"
|
||||
],
|
||||
"summary": "Search EAN from Barcode",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "barcode to be searched",
|
||||
"name": "data",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
@@ -193,6 +193,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/getproductfromean": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Items"
|
||||
],
|
||||
"summary": "Search EAN from Barcode",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "barcode to be searched",
|
||||
"name": "data",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
@@ -1551,6 +1551,25 @@ paths:
|
||||
summary: Currency
|
||||
tags:
|
||||
- Base
|
||||
/v1/getproductfromean:
|
||||
get:
|
||||
parameters:
|
||||
- description: barcode to be searched
|
||||
in: query
|
||||
name: data
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Search EAN from Barcode
|
||||
tags:
|
||||
- Items
|
||||
/v1/groups:
|
||||
get:
|
||||
produces:
|
||||
|
||||
@@ -193,6 +193,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/getproductfromean": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Items"
|
||||
],
|
||||
"summary": "Search EAN from Barcode",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "barcode to be searched",
|
||||
"name": "data",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/groups": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
@@ -1551,6 +1551,25 @@ paths:
|
||||
summary: Currency
|
||||
tags:
|
||||
- Base
|
||||
/v1/getproductfromean:
|
||||
get:
|
||||
parameters:
|
||||
- description: barcode to be searched
|
||||
in: query
|
||||
name: data
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Search EAN from Barcode
|
||||
tags:
|
||||
- Items
|
||||
/v1/groups:
|
||||
get:
|
||||
produces:
|
||||
|
||||
Reference in New Issue
Block a user