Add new API and client modules for category and product management

This commit introduces new API and client modules that provide functionality for managing categories and products. It includes the creation of `.gitignore`, necessary data models, API endpoints, and client authentication using go-resty. This forms the foundation for further enhancements to the category and product management features.
This commit is contained in:
Павел Синицин 2024-09-07 19:14:29 +03:00
commit e14d4b188b
Signed by: spavelit
GPG Key ID: 2FEC8CEAE5A95DD1
11 changed files with 497 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
cmd/
.vscode/
.idea/

23
api/api.go Normal file
View File

@ -0,0 +1,23 @@
package api
import (
"gitea.24example.ru/spavelit/bpiek/client"
"gitea.24example.ru/spavelit/bpiek/model"
)
type Api struct {
Client *client.Client
}
type IApi interface {
GetParentCategories() ([]model.Category, error)
GetCategories() ([]model.Category, error)
GetProducts() ([]model.Product, error)
}
func NewApi(credentials client.Credentials) IApi {
client := client.NewClient(credentials)
return &Api{
Client: client,
}
}

77
api/categories.go Normal file
View File

@ -0,0 +1,77 @@
package api
import (
"errors"
"gitea.24example.ru/spavelit/bpiek/model"
)
var categories []model.Category
var parentCategories []model.Category
func (a *Api) GetCategories() ([]model.Category, error) {
if len(categories) > 0 {
return categories, nil
}
parentCategoris, err := a.GetParentCategories()
if err != nil {
return nil, err
}
for _, category := range parentCategoris {
categoriesAndProduct, err := a.GetCategoriesAndProductsBySlugParentCategory(category.Slug)
if err != nil {
return nil, err
}
if len(categoriesAndProduct.Categories) != 0 {
categories = append(categories, categoriesAndProduct.Categories...)
}
}
return categories, nil
}
func (a *Api) GetParentCategories() ([]model.Category, error) {
if len(parentCategories) > 0 {
return parentCategories, nil
}
apiResponse, err := a.Client.HTTPClient.R().SetResult(&model.CategoryResponse{}).Get("client/catalog")
if err != nil {
return nil, err
}
if apiResponse.StatusCode() != 200 {
return nil, errors.New("failed to get categories")
}
result := apiResponse.Result().(*model.CategoryResponse).Categories
if len(result) == 0 {
return nil, errors.New("no categories found")
}
parentCategories = result
return parentCategories, nil
}
func (a *Api) GetCategoriesAndProductsBySlugParentCategory(slug string) (*model.CategoriesAndProductsBySlugParentCategoryResponse, error) {
apiResponse, err := a.Client.HTTPClient.
R().
SetResult(&model.CategoriesAndProductsBySlugParentCategoryResponse{}).
Get("client/category/" + slug + "/json")
if err != nil {
return nil, err
}
if apiResponse.StatusCode() != 200 {
return nil, errors.New("failed to get categories and products")
}
return apiResponse.Result().(*model.CategoriesAndProductsBySlugParentCategoryResponse), nil
}

31
api/products.go Normal file
View File

@ -0,0 +1,31 @@
package api
import "gitea.24example.ru/spavelit/bpiek/model"
var products []model.Product
func (a *Api) GetProducts() ([]model.Product, error) {
if len(products) > 0 {
return products, nil
}
parentCategoris, err := a.GetParentCategories()
if err != nil {
return nil, err
}
for _, category := range parentCategoris {
categoriesAndProduct, err := a.GetCategoriesAndProductsBySlugParentCategory(category.Slug)
if err != nil {
return nil, err
}
if len(categoriesAndProduct.Products) != 0 {
products = append(products, categoriesAndProduct.Products...)
}
}
return products, nil
}

57
client/client.go Normal file
View File

@ -0,0 +1,57 @@
package client
import "github.com/go-resty/resty/v2"
// import (
// "net/http"
// "net/url"
// )
const (
AUTH_API_URL = "https://bp.iek.ru/oauth/login"
BASE_API_URL = "https://bp.iek.ru/api/catalog/v1/"
)
type (
Client struct {
HTTPClient *resty.Client
}
AuthSuccess struct {
TokenType string `json:"token_type"`
AccessToken string `json:"access_token"`
IdToken string `json:"id_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
)
func NewClient(credentials Credentials) *Client {
authSuccess := &AuthSuccess{}
client := resty.New()
client.SetBaseURL(BASE_API_URL)
response, err := client.R().
SetFormData(map[string]string{
"username": credentials.GetUsername(),
"password": credentials.GetPassword(),
}).
SetResult(&authSuccess).
Post(AUTH_API_URL)
if err != nil {
panic(err)
}
if response.StatusCode() != 200 {
panic("Auth failed. Invalid credentials")
}
client.SetHeader("Content-Type", "application/json")
client.SetHeader("Authorization", "Bearer "+authSuccess.AccessToken)
return &Client{
HTTPClient: client,
}
}

17
client/credential.go Normal file
View File

@ -0,0 +1,17 @@
package client
type (
// Credentials provides constant credential values.
Credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
)
func (c *Credentials) GetUsername() string {
return c.Username
}
func (c *Credentials) GetPassword() string {
return c.Password
}

6
go.mod Normal file
View File

@ -0,0 +1,6 @@
module gitea.24example.ru/spavelit/bpiek
go 1.23.1
require github.com/go-resty/resty/v2 v2.14.0
require golang.org/x/net v0.27.0 // indirect

74
go.sum Normal file
View File

@ -0,0 +1,74 @@
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/go-resty/resty/v2 v2.14.0 h1:/rhkzsAqGQkozwfKS5aFAbb6TyKd3zyFRWcdRXLPCAU=
github.com/go-resty/resty/v2 v2.14.0/go.mod h1:IW6mekUOsElt9C7oWr0XRt9BNSD6D5rr9mhk6NjmNHg=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

8
model/category.go Normal file
View File

@ -0,0 +1,8 @@
package model
type Category struct {
Slug string `json:"slug"`
Name string `json:"name"`
Url string `json:"url"`
ApiUrl string `json:"apiUrl"`
}

164
model/product.go Normal file
View File

@ -0,0 +1,164 @@
package model
type VideosType string
type WarehouseDataIncomingType string
const (
VideoTypeFile VideosType = "file"
VideoTypeUrl VideosType = "url"
)
const (
WarehouseDataIncomingTypeShipping WarehouseDataIncomingType = "shipping"
WarehouseDataIncomingTypeProduction WarehouseDataIncomingType = "production"
)
type (
ImageVariant struct {
Url string `json:"url"`
Ext string `json:"ext"`
Width int `json:"width"`
}
Complects struct {
Article string `json:"article"`
Name string `json:"name"`
Quantity int `json:"quantity"`
}
LeftPeriod struct {
Name string `json:"name"`
Value string `json:"value"`
}
LeftPeriodRaw struct {
Lifespan interface{} `json:"lifespan"`
Warranty interface{} `json:"warranty"`
}
DesignFeatures struct {
ImageUrl string `json:"imageUrl"`
Description string `json:"description"`
}
Video struct {
Name string `json:"name"`
Description string `json:"description"`
Url string `json:"url"`
Type VideosType `json:"type"`
}
Software struct {
Name string `json:"name"`
Description string `json:"description"`
Url string `json:"url"`
Size int `json:"size"`
}
Analog struct {
Article string `json:"article"`
Name string `json:"name"`
ShortName string `json:"shortName"`
Description string `json:"description"`
ImageUrl string `json:"imageUrl"`
ImageUrls []string `json:"imageUrls"`
ImageVariants []ImageVariant `json:"imageVariants"`
IsArchived bool `json:"isArchived"`
Tm string `json:"tm"`
}
WarehouseData struct {
WarehouseId string `json:"warehouseId"`
WarehouseName string `json:"warehouseName"`
AvailableAmount int `json:"availableAmount"`
Incoming []struct {
DateBegan string `json:"dateBegan"`
DateEnd string `json:"dateEnd"`
Amount int `json:"amount"`
Type WarehouseDataIncomingType `json:"type"`
}
}
Etim struct {
Features []struct {
Id string `json:"id"`
Name string `json:"name"`
Sort int `json:"sort"`
Unit string `json:"unit"`
Value string `json:"value"`
} `json:"features"`
Class struct {
Id string `json:"id"`
Name string `json:"name"`
} `json:"class"`
}
LogisticParams struct {
Name string `json:"name"`
NameOrig string `json:"nameOrig"`
Value struct {
Group string `json:"group"`
Individual string `json:"individual"`
Transport string `json:"transport"`
} `json:"value"`
}
LogisticParamsData struct {
SinglePackage struct {
Multiplicity int `json:"multiplicity"`
Unit string `json:"unit"`
} `json:"singlePackage"`
}
ShortProduct struct {
Article string `json:"article"`
Name string `json:"name"`
Multiplicity int `json:"multiplicity"`
PriceBase int `json:"priceBase"`
PriceRrc int `json:"priceRrc"`
Available int `json:"available"`
Units string `json:"units"`
WarehouseData []WarehouseData `json:"warehouseData"`
}
Product struct {
ShortName string `json:"shortName"`
Description string `json:"description"`
CategoryName string `json:"categoryName"`
Category string `json:"category"`
Slug string `json:"slug"`
Tm string `json:"tm"`
Url string `json:"url"`
IsArchived bool `json:"isArchived"`
ImageUrl string `json:"imageUrl"`
ImageUrls []string `json:"imageUrls"`
ImageVariants []ImageVariant `json:"imageVariants"`
Advantages string `json:"advantages"`
Etim Etim `json:"etim"`
Complects []Complects `json:"complects"`
Complectations string `json:"complectations"`
Files []interface{} `json:"files"`
LeftPeriod []LeftPeriod `json:"leftPeriod"`
LeftPeriodRaw LeftPeriodRaw `json:"leftPeriodRaw"`
LogisticParams []LogisticParams `json:"logisticParams"`
LogisticParamsData LogisticParamsData `json:"logisticParamsData"`
Novelty bool `json:"novelty"`
DesignFeatures []DesignFeatures `json:"designFeatures"`
Videos []Video `json:"videos"`
Software []Software `json:"software"`
Banner string `json:"banner"`
LastModified string `json:"lastModified"`
CountryOfProduction string `json:"countryOfProduction"`
FirstSaleDate string `json:"firstSaleDate"`
Feacn string `json:"feacn"`
Family string `json:"family"`
Series string `json:"series"`
IndPacking []string `json:"indPacking"`
Analogs []Analog `json:"analogs"`
Related []Analog `json:"related"`
QrCode string `json:"qrCode"`
IsOutOfAssortment bool `json:"isOutOfAssortment"`
IsOutOfProduction bool `json:"isOutOfProduction"`
}
)

37
model/response.go Normal file
View File

@ -0,0 +1,37 @@
package model
type CategoryResponse struct {
Categories []Category `json:"categories"`
}
type CategoriesAndProductsBySlugParentCategoryResponse struct {
Date string `json:"date"`
Slug string `json:"slug"`
Name string `json:"name"`
Url string `json:"url"`
Categories []Category `json:"categories"`
Products []Product `json:"products"`
}
type NewProductsResponse struct {
Data struct {
Products []Product `json:"products"`
} `json:"data"`
Meta struct {
Page int `json:"page"`
TotalPages int `json:"totalPages"`
TotalCount int `json:"totalCount"`
PageSize int `json:"pageSize"`
} `json:"_meta"`
}
type RemainsAndPlanresiduesResponse struct {
Date string `json:"date"`
Products []ShortProduct `json:"products"`
}
type TreeCategoriesResponse struct {
Category
Children []Category `json:"children"`
}