This commit is contained in:
merdan 2023-05-03 13:07:34 +05:00
commit 0cb03a74a9
25 changed files with 2757 additions and 0 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
DB_DSN="usr:pwd@tcp(127.0.0.1:3306)/test_baza?parseTime=true"
UPDATE_PERIOD=30m

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.env
vendor

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/sarga_updater.iml" filepath="$PROJECT_DIR$/.idea/sarga_updater.iml" />
</modules>
</component>
</project>

9
.idea/sarga_updater.iml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

48
bagisto_models/brand.go Normal file
View File

@ -0,0 +1,48 @@
package bagisto_models
import (
"log"
"time"
"github.com/gosimple/slug"
"gorm.io/gorm"
)
type Brand struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
Name string `gorm:"default:TESTBRAND"`
Code string
Status bool
Categories []Category `gorm:"many2many:category_brands;"`
}
func FindOrCreateBrand(db *gorm.DB, brand string, categories []Category) (Brand, error) {
var brandObject Brand
if brand != "" {
code := slug.Make(brand)
err := db.FirstOrCreate(&brandObject, Brand{Name: brand, Code: code}).Error
//err := db.Model(Brand{Name: brand, Code: code}).First(&brandObject).Error
//log.Println(Brand{Name: brand, Code: code})
if err != nil {
//err := db.Omit("Categories.*").FirstOrCreate(&brandObject, Brand{Name: brand, Code: code, Categories: categories}).Error
log.Println("ERR0000000000000001" + err.Error())
return brandObject, err
}
db.Model(&brandObject).Association("Categories").Append(categories)
return brandObject, nil
}
// var brandObject Brand
return brandObject, nil
}

View File

@ -0,0 +1,48 @@
package bagisto_models
import (
"log"
"gorm.io/gorm"
)
type CategoryTranslation struct {
ID uint `gorm:"primaryKey"`
Slug string
CategoryID uint
MetaDescription string
MetaKeywords string
Locale string
}
type Category struct {
ID uint `gorm:"primaryKey"`
Status int8
Position int
ParentId uint
DisplayMode string
Translations []CategoryTranslation
}
func GetMainCategories(db *gorm.DB) ([]Category, error) {
var categories []Category
err := db.Model(&Category{}).Preload("Translations").Find(&categories, "status=1 AND parent_id=1 AND display_mode!='promotion'").Error
if err != nil {
log.Println(err.Error())
return nil, err
}
return categories, nil
}
func GetCatKeywords(db *gorm.DB, catIDs []int) ([]Category, error) {
var categories []Category
if errCat := db.Preload("Translations", "locale=?", "tm").Find(&categories, catIDs).Error; errCat != nil {
log.Println(errCat)
return categories, errCat
}
return categories, nil
}

108
bagisto_models/family.go Normal file
View File

@ -0,0 +1,108 @@
package bagisto_models
import (
"errors"
"log"
"gorm.io/gorm"
)
type AttributeOption struct {
ID uint `gorm:"primaryKey"`
AttributeID uint
AdminName string
SortOrder int `sql:"DEFAULT:NULL" gorm:"default:null"`
Translations []AttributeOptionTranslation
}
type AttributeOptionTranslation struct {
ID uint `gorm:"primaryKey"`
AttributeOptionID uint
Locale string
Label string
}
type Attribute struct {
ID uint `gorm:"primaryKey"`
Code string
AttributeOptions []AttributeOption
}
type AttributeFamily struct {
ID uint `gorm:"primaryKey"`
Code string
Groups []AttributeGroup
}
type AttributeGroup struct {
ID uint `gorm:"primaryKey"`
Name string
AttributeFamilyID uint
Attributes []Attribute `gorm:"many2many:attribute_group_mappings;"`
}
func GetFamilies(db *gorm.DB) ([]AttributeFamily, error) {
var families []AttributeFamily
err := db.Model(&AttributeFamily{}).Preload("Groups").Order("id ASC").Find(&families).Error
return families, err
}
func GetGroups(db *gorm.DB) []AttributeGroup {
var groups []AttributeGroup
err := db.Model(&AttributeGroup{}).Preload("Family").Find(&groups).Error
if err != nil {
log.Println(err.Error())
}
return groups
}
func GetAttributes(db *gorm.DB) ([]Attribute, error) {
var attributes []Attribute
err := db.Model(&Attribute{}).Find(&attributes).Error
return attributes, err
}
func GetAttributeOption(db *gorm.DB, attrbuteID uint, value string) AttributeOption {
var option AttributeOption
err := db.Where("attribute_id=? and admin_name=?", attrbuteID, value).First(&option).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
option = AttributeOption{
AttributeID: attrbuteID,
AdminName: value,
SortOrder: 1000,
Translations: []AttributeOptionTranslation{
{Locale: "tm", Label: value},
{Locale: "ru", Label: value},
{Locale: "tr", Label: value},
},
}
if err := db.Create(&option).Error; err != nil {
log.Println(err.Error())
}
}
return option
}
func GetAttrOptions(db *gorm.DB, attrbuteID uint) ([]AttributeOption, error) {
var options []AttributeOption
err := db.Find(&options, AttributeOption{AttributeID: attrbuteID}).Error
if err != nil {
log.Println(err.Error())
}
return options, err
}

166
bagisto_models/product.go Normal file
View File

@ -0,0 +1,166 @@
package bagisto_models
import (
"context"
"gorm.io/gorm"
"log"
"time"
)
type Product struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
Sku string
Type string
ParentID uint `sql:"DEFAULT:NULL"`
AttributeFamilyID uint
AttributeFamily AttributeFamily
BrandID uint `sql:"DEFAULT:NULL"`
Brand Brand
Images []ProductImage
Categories []Category `gorm:"many2many:product_categories;"`
AttributeValues []ProductAttributeValue
SuperAttributes []Attribute `gorm:"many2many:product_super_attributes;"`
//Variants []*Product `gorm:"many2many:product_relations;foreignKey:child_id;primaryKey:parent_id;"`
}
type ProductRelation struct {
ParentID uint
ChildID uint
}
type ProductFlat struct {
ID uint `gorm:"primary_key"`
Sku string
ProductNumber string `sql:"DEFAULT:NULL" gorm:"default:null"`
Name string `sql:"DEFAULT:''" gorm:"default:''"`
Weight float64 `sql:"DEFAULT:NULL" gorm:"type:decimal(12,4);default:null"`
Status bool `sql:"DEFAULT:NULL" gorm:"default:null"`
VisibleIndividually bool `sql:"DEFAULT:NULL" gorm:"default:null"`
Price float64 `gorm:"type:decimal(12,4)"`
MinPrice float64 `gorm:"type:decimal(12,4)"`
MaxPrice float64 `gorm:"type:decimal(12,4)"`
SpecialPrice float64 `sql:"DEFAULT:NULL" gorm:"type:decimal(12,4);default:null"`
UrlKey string `sql:"DEFAULT:NULL" gorm:"default:null"`
ShortDescription string `sql:"DEFAULT:NULL" gorm:"default:null"`
Description string `sql:"DEFAULT:NULL" gorm:"default:null"`
FavoritesCount uint `sql:"DEFAULT:NULL" gorm:"default:null;column:favoritesCount"`
CreatedAt time.Time `sql:"DEFAULT:NULL" gorm:"default:null"`
UpdatedAt time.Time `sql:"DEFAULT:NULL" gorm:"default:null"`
ProductID uint
Product Product
Channel string `sql:"DEFAULT:default" gorm:"default:default"`
Locale string `sql:"DEFAULT:tm" gorm:"default:tm"`
ParentID uint `sql:"DEFAULT:NULL" gorm:"default:null"`
Variants []ProductFlat `gorm:"foreignKey:ParentID"`
Color int `sql:"DEFAULT:NULL" gorm:"default:null"`
ColorLabel string `sql:"DEFAULT:NULL" gorm:"default:null"`
Size int `sql:"DEFAULT:NULL" gorm:"default:null"`
SizeLabel string `sql:"DEFAULT:NULL" gorm:"default:null"`
Boyut int `sql:"DEFAULT:NULL" gorm:"default:null"`
BoyutLabel string `sql:"DEFAULT:NULL" gorm:"default:null"`
MetaTitle string `sql:"DEFAULT:NULL" gorm:"default:null"`
MetaKeywords string `sql:"DEFAULT:NULL" gorm:"default:null"`
BrandID uint `sql:"DEFAULT:NULL" gorm:"default:null"`
Brand Brand
Cinsiyet int `sql:"DEFAULT:NULL" gorm:"default:null"`
CinsiyetLabel string `sql:"DEFAULT:NULL" gorm:"default:null"`
}
type ProductImage struct {
ID uint `gorm:"primary_key"`
Type string
Path string
ProductID uint
}
type ProductAttributeValue struct {
ID uint `gorm:"primary_key"`
Locale string `sql:"DEFAULT:NULL" gorm:"default:null"`
Channel string `sql:"DEFAULT:NULL" gorm:"default:null"`
ProductID uint
AttributeID uint
TextValue string `sql:"DEFAULT:NULL" gorm:"default:null"`
BooleanValue bool `sql:"DEFAULT:NULL" gorm:"default:null"`
IntegerValue int `sql:"DEFAULT:NULL" gorm:"default:null"`
FloatValue float64 `sql:"DEFAULT:NULL" gorm:"type:decimal(12,4);default:null"`
}
type ProductSuperAttribute struct {
ProductID uint
AttributeID uint
}
type Tabler interface {
TableName() string
}
// TableName overrides the table name used by User to `profiles`
func (ProductFlat) TableName() string {
return "product_flat"
}
func DeleteProducts(db *gorm.DB) error {
//todo delete from elastico
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
//qb := "DELETE FROM products WHERE id NOT IN (select product_id as id from wishlist) AND id NOT IN (select product_id as id from order_items) AND id NOT IN (select parent_id as idfrom order_items);"
qb := "DELETE p FROM products p " +
"LEFT JOIN order_items oi ON p.id = oi.product_id " +
"LEFT JOIN order_items op ON p.id = op.parent_id " +
"LEFT JOIN wishlist wp ON p.id = wp.product_id " +
"LEFT JOIN wishlist wl ON wl.product_id = p.parent_id " +
"JOIN marketplace_products mp ON p.id = mp.product_id " +
"WHERE oi.id IS NULL AND op.id IS NULL AND wp.id IS NULL AND wl.id IS NULL AND mp.marketplace_seller_id=1;"
db.WithContext(ctx).Exec(qb)
//db.WithContext(ctx).Exec("UPDATE product_flat set sku=concat(id,\"-ordered\"), status=0 where status=1 AND product_id IN (SELECT product_id from marketplace_products where marketplace_seller_id=1)" )
//db.WithContext(ctx).Exec("UPDATE products set sku=concat(id,\"-ordered\") WHERE id IN (SELECT product_id from marketplace_products where marketplace_seller_id=1)")
return db.Error
}
func GetWishlistProducts(db *gorm.DB) ([]Product, error) {
var products []Product
err := db.Joins("JOIN wishlist wp ON products.id = wp.product_id").
Joins("JOIN marketplace_products mp ON products.id = mp.product_id where marketplace_seller_id=1").
Find(&products).Error
if err != nil {
log.Println(err.Error())
return nil, err
}
return products, nil
//qb := "SELECT p.id,p.sku FROM products p " +
// "JOIN wishlist wp ON p.id = wp.product_id " +
// "JOIN marketplace_products mp ON p.id = mp.product_id where marketplace_seller_id=1);"
}
func GetFlatSources(db *gorm.DB) ([]ProductAttributeValue, error) {
var productSources []ProductAttributeValue
err := db.Joins("JOIN wishlist wp ON product_attribute_values.product_id = wp.product_id").
Joins("JOIN marketplace_products mp ON product_attribute_values.product_id = mp.product_id where marketplace_seller_id=1").
Find(&productSources, "text_value IS NOT NULL AND attribute_id=31").Error
if err != nil {
log.Println(err.Error())
return nil, err
}
return productSources, nil
}
//func DisableProducts (db *gorm.DB) error {
// ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
// defer cancel()
// db.WithContext(ctx).Exec("UPDATE product_flat set sku=concat(sku,\"-ordered\"), status=0")
// db.WithContext(ctx).Exec("UPDATE products set sku=concat(sku,\"-ordered\")")
// return db.Error
//}
//func Flush() error {
// _, err := helper.SendRequest("GET", os.Getenv("scout_flash"), nil, "")
//
// return err
//}

37
bagisto_models/vendor.go Normal file
View File

@ -0,0 +1,37 @@
package bagisto_models
import (
"time"
"gorm.io/gorm"
)
type MarketplaceProduct struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time `sql:"DEFAULT:NULL" gorm:"default:null"`
UpdatedAt time.Time `sql:"DEFAULT:NULL" gorm:"default:null"`
ProductID uint
//Product Product
ParentID *uint `sql:"DEFAULT:NULL" gorm:"default:null"`
Condition string `sql:"DEFAULT:NULL" gorm:"default:null"`
Price float64
Description string `sql:"DEFAULT:NULL" gorm:"default:null"`
IsApproved bool `sql:"DEFAULT:NULL" gorm:"default:null"`
IsOwner bool
MarketplaceSellerID uint
MarketplaceSeller MarketplaceSeller
Variants []MarketplaceProduct `gorm:"foreignKey:ParentID"`
}
type MarketplaceSeller struct {
ID uint `gorm:"primaryKey"`
Url string
}
func GetSellers(db *gorm.DB) ([]MarketplaceSeller, error) {
var sellers []MarketplaceSeller
err := db.Model(&MarketplaceSeller{}).Find(&sellers).Error
return sellers, err
}

View File

@ -0,0 +1,7 @@
package controllers
import "net/http"
func StartUpdate(w http.ResponseWriter, _ *http.Request) {
}

17
go.mod Normal file
View File

@ -0,0 +1,17 @@
module sarga_updater
go 1.19
require (
github.com/gosimple/slug v1.13.1
github.com/leesper/couchdb-golang v1.2.1
gorm.io/driver/mysql v1.5.0
gorm.io/gorm v1.25.0
)
require (
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
)

17
go.sum Normal file
View File

@ -0,0 +1,17 @@
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/leesper/couchdb-golang v1.2.1 h1:FqSaTxxT2mVRLbxGQVkZakRRoSzWhPmV8UEKYjA/GWc=
github.com/leesper/couchdb-golang v1.2.1/go.mod h1:OU3FDAM3mazHx15oi8Hm+egTMneBUqepwnh0LuBSH54=
gorm.io/driver/mysql v1.5.0 h1:6hSAT5QcyIaty0jfnff0z0CLDjyRgZ8mlMHLqSt7uXM=
gorm.io/driver/mysql v1.5.0/go.mod h1:FFla/fJuCvyTi7rJQd27qlNX2v3L6deTR1GgTjSOLPo=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=

12
helpers/global.go Normal file
View File

@ -0,0 +1,12 @@
package helpers
import (
"github.com/leesper/couchdb-golang"
models "sarga_updater/trendyol_models"
)
// CdbServer is global couchdb server
var CdbServer *couchdb.Server
// Categories stores all categories and their weight
var Categories []models.CouchCategory = []models.CouchCategory{}

112
helpers/helper.go Normal file
View File

@ -0,0 +1,112 @@
package helpers
import (
"context"
"log"
"net"
"net/http"
"os"
models "sarga_updater/trendyol_models"
"time"
)
func CheckDBExists(endpoint string) bool {
const ConnectMaxWaitTime = 30 * time.Second
const RequestMaxWaitTime = 60 * time.Second
client := http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: ConnectMaxWaitTime,
}).DialContext,
},
}
ctx, cancel := context.WithTimeout(context.Background(), RequestMaxWaitTime)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
req.Proto = "HTTP/1.0"
if err != nil {
log.Println(err.Error())
return false
}
q := req.URL.Query()
req.URL.RawQuery = q.Encode()
req.Header.Set("Connection", "close")
req.Close = true
client.CloseIdleConnections()
response, err := client.Do(req)
// handle timeout
if e, ok := err.(net.Error); ok && e.Timeout() {
if response != nil {
response.Body.Close()
}
log.Println(err.Error())
return false
} else if err != nil {
if response != nil {
response.Body.Close()
}
log.Println(err.Error())
return false
}
if response != nil {
response.Body.Close()
}
if response.StatusCode == http.StatusOK {
return true
} else {
return false
}
}
func GetCategoryWeight(slug string) string {
db, err := CdbServer.Get(os.Getenv("db_ty_categories"))
if err != nil {
Error("can not get db. Err: " + err.Error())
return "0.5"
}
doc, err := db.Get(slug, nil)
if err != nil {
Error("can not get by slug. Err: " + err.Error())
return "0.5"
}
return doc["weight"].(string)
}
func IsLCWSizeVariantsAdded(items []models.Variant, keyItem models.Variant) bool {
for _, option := range items {
if option.ItemNumber == keyItem.ItemNumber {
return true
}
}
return false
}
func IsImageAdded(images []string, image string) bool {
for _, img := range images {
if img == image {
return true
}
}
return false
}
func IsVariantsAdded(variants []models.Variant, itemNumber int) bool {
for _, variant := range variants {
if variant.ItemNumber == itemNumber {
return true
}
}
return false
}

86
helpers/log.go Normal file
View File

@ -0,0 +1,86 @@
package helpers
import (
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"time"
)
type Level int
var (
DefaultPrefix = ""
DefaultCallerDepth = 2
logger *log.Logger
logPrefix = ""
levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
)
const (
DEBUG Level = iota
INFO
WARNING
ERROR
FATAL
)
// Setup initialize the log instance
func Setup() {
logger = log.New(os.Stdout, DefaultPrefix, log.Flags()&^(log.Ldate|log.Ltime))
}
// Debug output logs at debug level
func Debug(v ...interface{}) {
setPrefix(DEBUG)
logger.Println(v...)
}
// Info output logs at info level
func Info(v ...interface{}) {
setPrefix(INFO)
logger.Println(v...)
}
// Warn output logs at warn level
func Warn(v ...interface{}) {
setPrefix(WARNING)
logger.Println(v...)
}
// Error output logs at error level
func Error(v ...interface{}) {
prefix := setPrefix(ERROR)
logger.Println(v...)
prefix.msg = v
}
// Fatal output logs at fatal level
func Fatal(v ...interface{}) {
setPrefix(FATAL)
logger.Fatalln(v...)
}
// setPrefix set the prefix of the log output
func setPrefix(level Level) prefix {
_, file, line, ok := runtime.Caller(DefaultCallerDepth)
if ok {
logPrefix = fmt.Sprintf("[%s:%d] [%s] ", filepath.Base(file), line, levelFlags[level])
} else {
logPrefix = fmt.Sprintf("[%s] ", levelFlags[level])
}
logPrefix += "| " + time.Now().Format("01.02.2006 15:04:05") + " | "
logger.SetPrefix(logPrefix)
return prefix{prefix: logPrefix}
}
type prefix struct {
prefix string
msg interface{}
}

100
helpers/request.go Normal file
View File

@ -0,0 +1,100 @@
package helpers
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net"
"net/http"
"time"
)
func SendRequest(method string, endpoint string, values []byte, authKey string) ([]byte, error) {
const ConnectMaxWaitTime = time.Minute
const RequestMaxWaitTime = 5 * time.Minute
client := http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: ConnectMaxWaitTime,
}).DialContext,
},
}
ctx, cancel := context.WithTimeout(context.Background(), RequestMaxWaitTime)
defer cancel()
emptyBody := []byte{}
req, err := http.NewRequestWithContext(ctx, method, endpoint, bytes.NewBuffer(values))
req.Proto = "HTTP/2.0"
if err != nil {
Error(err)
return emptyBody, err
}
q := req.URL.Query()
req.URL.RawQuery = q.Encode()
req.Header.Set("Connection", "close")
req.Header.Add("Authorization", authKey)
req.Close = true
client.CloseIdleConnections()
response, err := client.Do(req)
if e, ok := err.(net.Error); ok && e.Timeout() {
if response != nil {
response.Body.Close()
}
Error(err)
return emptyBody, err
} else if err != nil {
if response != nil {
response.Body.Close()
}
Error(err)
return emptyBody, err
}
if response.StatusCode != http.StatusOK {
if response != nil {
response.Body.Close()
}
err := fmt.Errorf("response: code: %d, body: %v", response.StatusCode, response.Body)
Error(err)
return emptyBody, err
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
Error(err)
if response != nil {
response.Body.Close()
}
return emptyBody, nil
}
// if you call it in loop, don't use defer
response.Body.Close()
return body, nil
}
func NewHttpClient() (http.Client, context.Context) {
const ConnectMaxWaitTime = 30 * time.Second
const RequestMaxWaitTime = 60 * time.Second
//proxyUrl, _ := url.Parse("http://79.98.129.183:3128")
client := http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: ConnectMaxWaitTime,
}).DialContext,
//Proxy: http.ProxyURL(proxyUrl),
},
}
ctx, cancel := context.WithTimeout(context.Background(), RequestMaxWaitTime)
defer cancel()
return client, ctx
}

93
main.go Normal file
View File

@ -0,0 +1,93 @@
package sarga_updater
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
"os"
"os/signal"
"sarga_updater/bagisto_models"
"sarga_updater/repositories"
"syscall"
"time"
)
func main() {
// Retrieve database connection information from environment variables
dsn := os.Getenv("DB_DSN")
// Connect to the database
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("Error connecting to database: %s", err)
}
importer, _ := repositories.ParseImporterInstance(db)
updatePeriodStr := os.Getenv("UPDATE_PERIOD")
updatePeriod, err := time.ParseDuration(updatePeriodStr)
if err != nil {
log.Fatal("Error parsing update period:", err)
}
// Start the worker
stopCh := make(chan struct{})
go worker(importer, stopCh, updatePeriod)
// Wait for termination signal
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
<-signalCh
// Stop the worker
close(stopCh)
// Close the database connection
sqlDB, err := db.DB()
if err != nil {
log.Fatalf("Error getting database connection from gorm: %s", err)
}
err = sqlDB.Close()
if err != nil {
log.Fatalf("Error closing database connection: %s", err)
}
}
func worker(importer *repositories.Importer, stopCh <-chan struct{}, updatePeriod time.Duration) {
ticker := time.NewTicker(updatePeriod)
defer ticker.Stop()
for {
select {
case <-stopCh:
return
case <-ticker.C:
result, err := bagisto_models.GetFlatSources(importer.Baza)
if err != nil {
log.Println("Error retrieving products:", err)
continue
}
// Update products as needed
for _, product := range result {
// Retrieve the latest information from the source URL
parser := repositories.NewLinkParser(product.TextValue)
data, err := parser.ParseLink()
if err != nil {
log.Println("Error decoding product information:", err)
continue
}
jsonProduct, err := parser.GetProductDetailWithOptions(data.ID, data.ProductGroupID)
// Update the product in the database
if err := importer.UpdateOrCreate(jsonProduct).Error; err != nil {
log.Println("Error decoding product information:", err)
continue
}
log.Printf("Product %d updated: %+v\n", product.ID, product)
}
}
}
}

View File

@ -0,0 +1,842 @@
package repositories
import (
"encoding/json"
"errors"
"fmt"
"gorm.io/gorm"
"log"
"math"
gm "sarga_updater/bagisto_models"
helper "sarga_updater/helpers"
models "sarga_updater/trendyol_models"
"strconv"
"sync"
)
type Importer struct {
mainCategories []gm.Category
Baza *gorm.DB
families []gm.AttributeFamily
sellers map[string]gm.MarketplaceSeller
//wishlist map[string]gm.Product
AttributesMap map[string]gm.Attribute
Error error
ImportWGroup sync.WaitGroup
ColorOptions map[string]gm.AttributeOption
SexOptions map[string]gm.AttributeOption
ColorMutex sync.Mutex
SexMutex sync.Mutex
}
type GormErr struct {
Number int `json:"Number"`
Message string `json:"Message"`
}
func ParseImporterInstance(db *gorm.DB) (instance *Importer, err error) {
if err != nil {
log.Println(err)
return nil, err
}
instance = &Importer{Baza: db}
instance.ImportWGroup.Add(3)
//load families to memory
go func() {
defer instance.ImportWGroup.Done()
instance.families, instance.Error = gm.GetFamilies(db)
}()
//load attributes to memory
go func() {
defer instance.ImportWGroup.Done()
if attributes, err := gm.GetAttributes(db); err != nil {
instance.Error = err
return
} else {
instance.AttributesMap = make(map[string]gm.Attribute, len(attributes))
for _, attribute := range attributes {
instance.AttributesMap[attribute.Code] = attribute
}
}
if colorOptions, err := gm.GetAttrOptions(db, instance.AttributesMap["color"].ID); err != nil {
instance.Error = err
return
} else {
instance.ColorOptions = make(map[string]gm.AttributeOption, len(colorOptions))
for _, option := range colorOptions {
instance.ColorOptions[option.AdminName] = option
}
}
if sexOPtions, err := gm.GetAttrOptions(db, instance.AttributesMap["cinsiyet"].ID); err != nil {
instance.Error = err
return
} else {
instance.SexOptions = make(map[string]gm.AttributeOption, len(sexOPtions))
for _, option := range sexOPtions {
instance.SexOptions[option.AdminName] = option
}
}
}()
//load sellers to memory
go func() {
defer instance.ImportWGroup.Done()
var vendors, err = gm.GetSellers(db)
if err != nil {
instance.Error = err
return
}
//init sellers map
instance.sellers = make(map[string]gm.MarketplaceSeller, len(vendors))
for _, vendor := range vendors {
instance.sellers[vendor.Url] = vendor
}
}()
if instance.Error != nil {
log.Println(instance.Error)
return nil, instance.Error
}
return instance, nil
}
func (importer *Importer) ImportProduct(product models.Product) (instance *Importer) {
var linkedProducts []gm.Product
var firstProduct *gm.Product
if firstProduct, importer.Error = importer.importVariant(product); importer.Error != nil {
return importer
} else if product.ColorVariants != nil && len(*product.ColorVariants) > 0 {
linkedProducts = append(linkedProducts, *firstProduct)
for _, colorVariant := range *product.ColorVariants {
if !colorVariant.IsSellable {
continue
}
if variant, err := importer.importVariant(colorVariant); err == nil {
linkedProducts = append(linkedProducts, *variant)
}
}
}
if len(linkedProducts) > 1 {
var relation []gm.ProductRelation
for index, variant := range linkedProducts {
//spoint := "color" + strconv.Itoa(index)
temp := make([]gm.Product, len(linkedProducts))
copy(temp, linkedProducts)
if index+1 <= len(temp) {
temp = append(temp[:index], temp[index+1:]...)
for _, item := range temp {
relation = append(relation, gm.ProductRelation{ParentID: variant.ID, ChildID: item.ID})
}
}
}
if err := importer.Baza.Create(&relation).Error; err != nil {
log.Println(err)
}
}
return importer
}
func (importer *Importer) importVariant(product models.Product) (*gm.Product, error) {
// check if wishlisted then update if.
//if _, ok := importer.wishlist[product.ProductNumber]; ok {
// delete(importer.wishlist,product.ProductNumber)
// return importer.updateVariant(product)
//}
productRepo := InitProductRepo(&product, importer.GetColorOption(product.Color), importer.GetSexOption(product.Cinsiyet))
if categories, err := gm.GetCatKeywords(importer.Baza, product.Categories); err != nil {
return nil, err
} else {
productRepo.SetCategories(categories)
}
if brand, err := gm.FindOrCreateBrand(importer.Baza, product.Brand, productRepo.Categories); err != nil {
return nil, err
} else {
productRepo.Brand = brand
}
mainPorduct := productRepo.makeProduct(importer)
//BEGIN TRANSACTION
tx := importer.Baza.Begin()
if err := tx.Omit("Categories.*", "SuperAttributes.*", "ParentID").Create(&mainPorduct).Error; err != nil {
//todo update categories
byteErr, _ := json.Marshal(err)
var newError GormErr
if err1 := json.Unmarshal((byteErr), &newError); err1 != nil {
tx.Rollback()
log.Println(err1, "err2")
return nil, err1
}
if newError.Number == 1062 {
var barProduct gm.Product
if err2 := tx.First(&barProduct, "sku = ?", mainPorduct.Sku).Error; err2 != nil {
tx.Rollback()
log.Println(err2, "err3")
return nil, err2
}
if err3 := tx.Model(&barProduct).Association("Categories").Append(mainPorduct.Categories); err3 != nil {
tx.Rollback()
log.Println(err3, "err4")
return nil, err3
}
if err4 := tx.Commit().Error; err4 == nil {
return importer.updateVariant(product)
}
} else {
tx.Rollback()
log.Println(err, "er1")
return nil, err
}
}
mainFlat := productRepo.makeProductFlat(mainPorduct.ID)
if err := tx.Create(&mainFlat).Error; err != nil {
tx.Rollback()
log.Println(err, "er5")
return nil, err
}
if productRepo.HasSizeVariants() {
var sizeVariants []gm.ProductFlat
for index, variant := range *product.SizeVariants {
if !variant.Sellable {
continue
}
savePoint := "size" + strconv.Itoa(index)
tx.SavePoint(savePoint)
var sizeOPtion gm.AttributeOption
if variant.AttributeName == "Beden" {
sizeOPtion = gm.GetAttributeOption(tx, importer.AttributesMap["size"].ID, variant.AttributeValue)
} else {
sizeOPtion = gm.GetAttributeOption(tx, importer.AttributesMap["boyut"].ID, variant.AttributeValue)
}
sku := fmt.Sprintf("%s-%d", product.ProductNumber, variant.ItemNumber)
variantProduct := productRepo.makeVariant(mainPorduct.ID, mainPorduct.AttributeFamilyID, sku)
variantProduct.AttributeValues = productRepo.getVariantAttributes(importer.AttributesMap, &variant, sizeOPtion.ID)
if err := tx.Omit("Categories.*").Create(&variantProduct).Error; err != nil {
log.Println("Variant Product Create Error: " + err.Error())
tx.RollbackTo(savePoint)
continue
}
variantFlat := productRepo.makeVariantFlat(variant, sizeOPtion.ID, mainFlat.ID, variantProduct.ID)
if err := tx.Create(&variantFlat).Error; err != nil {
log.Println("Variant Flat Create Error: " + err.Error())
tx.RollbackTo(savePoint)
continue
}
sizeVariants = append(sizeVariants, variantFlat)
}
if len(sizeVariants) == 0 {
tx.Rollback()
return nil, errors.New("size variantlary yok bolsa main productam girayenok")
} else {
calcPrice(sizeVariants, &mainFlat)
err := tx.Omit("ParentID", "CreatedAt", "Variants", "SpecialPrice").Save(&mainFlat).Error
mainFlat.Variants = sizeVariants
if err != nil {
tx.Rollback()
log.Println(err, "er6")
return nil, err
}
}
}
sProduct := importer.createSellerProduct(&mainFlat, product.Vendor)
if errSProduct := tx.Create(&sProduct).Error; errSProduct != nil {
tx.Rollback()
return nil, errSProduct
}
if err := tx.Commit().Error; err != nil {
return nil, err
}
return &mainPorduct, nil
}
func (importer *Importer) createSellerProduct(flat *gm.ProductFlat, sellerURL string) gm.MarketplaceProduct {
sellerID := importer.sellers[sellerURL].ID
if sellerID == 0 {
sellerID = 1
}
sellerProduct := gm.MarketplaceProduct{
MarketplaceSellerID: sellerID,
IsApproved: true,
Condition: "new",
Description: "scraped",
IsOwner: true,
ProductID: flat.ProductID,
}
for _, variant := range flat.Variants {
sellerProduct.Variants = append(sellerProduct.Variants, gm.MarketplaceProduct{
ProductID: variant.ProductID,
IsOwner: true,
IsApproved: true,
MarketplaceSellerID: sellerID,
Condition: "new",
})
}
return sellerProduct
}
func calcPrice(variants []gm.ProductFlat, flat *gm.ProductFlat) {
for _, variant := range variants {
if !variant.Status {
continue
}
if flat.MinPrice == 0 || flat.MinPrice > variant.MinPrice {
flat.MinPrice = variant.MinPrice
}
flat.MaxPrice = math.Max(flat.MaxPrice, variant.MaxPrice)
}
}
func (importer *Importer) GetColorOption(optionName string) gm.AttributeOption {
if optionName == "" {
return gm.AttributeOption{}
}
importer.ColorMutex.Lock()
var option gm.AttributeOption
var ok bool
if option, ok = importer.ColorOptions[optionName]; !ok {
option := gm.GetAttributeOption(importer.Baza, importer.AttributesMap["color"].ID, optionName)
importer.ColorOptions[optionName] = option
}
importer.ColorMutex.Unlock()
return option
}
func (importer *Importer) GetSexOption(optionName string) gm.AttributeOption {
if optionName == "" {
return gm.AttributeOption{}
}
importer.SexMutex.Lock()
var option gm.AttributeOption
var ok bool
if option, ok = importer.ColorOptions[optionName]; !ok {
option = gm.GetAttributeOption(importer.Baza, importer.AttributesMap["cinsiyet"].ID, optionName)
importer.SexOptions[optionName] = option
}
importer.SexMutex.Unlock()
return option
}
func (importer *Importer) updateVariant(product models.Product) (*gm.Product, error) {
var flat gm.ProductFlat
err := importer.Baza.Preload("Product").Preload("Variants").First(&flat, "sku = ?", product.ProductNumber).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return importer.importVariant(product)
}
//todo not found bolsa create etmeli
return nil, err
}
if flat.Product.Type == "configurable" {
productRepo := InitProductRepo(&product, importer.GetColorOption(product.Color), importer.GetSexOption(product.Cinsiyet))
if brand, err := gm.FindOrCreateBrand(importer.Baza, product.Brand, productRepo.Categories); err != nil {
return nil, err
} else {
productRepo.Brand = brand
}
for _, variant := range *product.SizeVariants {
found := false
for _, flatVariant := range flat.Variants {
if variant.AttributeValue == flatVariant.BoyutLabel || variant.AttributeValue == flatVariant.SizeLabel {
if !variant.Sellable {
importer.Baza.Model(&flatVariant).Update("status", false)
} else {
importer.updatePrice(variant.Price, &flatVariant)
if !flatVariant.Status {
importer.Baza.Model(&flatVariant).Update("status", true)
}
}
found = true
break
}
}
if variant.Sellable && !found {
// insert variant
var sizeOPtion gm.AttributeOption
if variant.AttributeName == "Beden" {
sizeOPtion = gm.GetAttributeOption(importer.Baza, importer.AttributesMap["size"].ID, variant.AttributeValue)
} else {
sizeOPtion = gm.GetAttributeOption(importer.Baza, importer.AttributesMap["boyut"].ID, variant.AttributeValue)
}
sku := fmt.Sprintf("%s-%d", product.ProductNumber, variant.ItemNumber)
variantProduct := productRepo.makeVariant(flat.ProductID, flat.Product.AttributeFamilyID, sku)
variantProduct.AttributeValues = productRepo.getVariantAttributes(importer.AttributesMap, &variant, sizeOPtion.ID)
if err := importer.Baza.Omit("Categories.*").Create(&variantProduct).Error; err != nil {
log.Println("Variant Product Create Error: " + err.Error())
} else {
variantFlat := productRepo.makeVariantFlat(variant, sizeOPtion.ID, flat.ID, variantProduct.ID)
if err := importer.Baza.Create(&variantFlat).Error; err != nil {
log.Println("Variant Flat Create Error: " + err.Error())
}
flat.Variants = append(flat.Variants, variantFlat)
}
}
}
calcPrice(flat.Variants, &flat)
importer.Baza.Omit("ParentID", "CreatedAt", "Variants", "SpecialPrice").Save(&flat)
} else { //simple
importer.updatePrice(product.Price, &flat)
}
return &flat.Product, nil
}
func (importer *Importer) updatePrice(price models.Price, flat *gm.ProductFlat) {
if price.OriginalPrice.Value > price.DiscountedPrice.Value {
importer.Baza.Model(&flat).Updates(map[string]interface{}{
"price": price.OriginalPrice.Value,
"special_price": price.DiscountedPrice.Value,
"min_price": price.DiscountedPrice.Value,
"max_price": price.OriginalPrice.Value,
})
importer.Baza.Model(&gm.ProductAttributeValue{}).
Where("attribute_id = 11 and product_id = ?", flat.ProductID).
Update("float_value", price.OriginalPrice.Value)
importer.Baza.Model(&gm.ProductAttributeValue{}).
Where("attribute_id = 13 and product_id = ?", flat.ProductID).
Update("float_value", price.DiscountedPrice.Value)
} else {
importer.Baza.Model(&flat).Updates(map[string]interface{}{
"price": price.DiscountedPrice.Value,
"special_price": nil,
"min_price": price.DiscountedPrice.Value,
"max_price": price.DiscountedPrice.Value,
})
importer.Baza.Model(&gm.ProductAttributeValue{}).
Where("attribute_id = 11 and product_id = ?", flat.ProductID).
Update("float_value", price.DiscountedPrice.Value)
importer.Baza.Where("attribute_id = 13 and product_id = ?", flat.ProductID).
Delete(&gm.ProductAttributeValue{})
}
}
func (importer *Importer) importParsedVariant(product models.Product) (gm.Product, error) {
//todo search if exists
productRepo := InitProductRepo(&product, importer.GetColorOption(product.Color), importer.GetSexOption(product.Cinsiyet))
productRepo.ImageType = "lcw"
if brand, err := gm.FindOrCreateBrand(importer.Baza, product.Brand, productRepo.Categories); err != nil {
return gm.Product{}, err
} else {
productRepo.Brand = brand
}
mainPorduct := productRepo.makeProduct(importer)
//BEGIN TRANSACTION
tx := importer.Baza.Begin()
if err := tx.Omit("Categories", "SuperAttributes.*", "ParentID").Create(&mainPorduct).Error; err != nil {
tx.Rollback()
return gm.Product{}, err
}
mainFlat := productRepo.makeProductFlat(mainPorduct.ID)
if err := tx.Create(&mainFlat).Error; err != nil {
tx.Rollback()
return gm.Product{}, err
}
if productRepo.HasSizeVariants() {
var sizeVariants []gm.ProductFlat
for index, variant := range *product.SizeVariants {
if !variant.Sellable {
continue
}
savePoint := "size" + strconv.Itoa(index)
tx.SavePoint(savePoint)
var sizeOPtion gm.AttributeOption
if variant.AttributeName == "Beden" {
sizeOPtion = gm.GetAttributeOption(tx, importer.AttributesMap["size"].ID, variant.AttributeValue)
} else {
sizeOPtion = gm.GetAttributeOption(tx, importer.AttributesMap["boyut"].ID, variant.AttributeValue)
}
sku := fmt.Sprintf("%s-%d", product.ProductNumber, variant.ItemNumber)
log.Println(sku)
log.Println(variant)
variantProduct := productRepo.makeVariant(mainPorduct.ID, mainPorduct.AttributeFamilyID, sku)
variantProduct.AttributeValues = productRepo.getVariantAttributes(importer.AttributesMap, &variant, sizeOPtion.ID)
if err := tx.Omit("Categories.*").Create(&variantProduct).Error; err != nil {
log.Println("Variant Product Create Error: " + err.Error())
tx.RollbackTo(savePoint)
continue
}
variantFlat := productRepo.makeVariantFlat(variant, sizeOPtion.ID, mainFlat.ID, variantProduct.ID)
if err := tx.Create(&variantFlat).Error; err != nil {
log.Println("Variant Flat Create Error: " + err.Error())
tx.RollbackTo(savePoint)
continue
}
sizeVariants = append(sizeVariants, variantFlat)
}
if len(sizeVariants) == 0 {
tx.Rollback()
return gm.Product{}, errors.New("siz variantlary yok bolsa main productam girayenok")
} else {
calcPrice(sizeVariants, &mainFlat)
err := tx.Omit("ParentID", "CreatedAt", "Variants", "SpecialPrice").Save(&mainFlat).Error
mainFlat.Variants = sizeVariants
if err != nil {
tx.Rollback()
return gm.Product{}, err
}
}
}
log.Println(product.Vendor)
sProduct := importer.createSellerProduct(&mainFlat, product.Vendor)
if errSProduct := tx.Create(&sProduct).Error; errSProduct != nil {
tx.Rollback()
return gm.Product{}, errSProduct
}
if err := tx.Commit().Error; err != nil {
return gm.Product{}, err
}
return mainPorduct, nil
}
func (importer *Importer) updateParsedVariant(product models.Product) (gm.Product, error) {
var flat gm.ProductFlat
err := importer.Baza.Preload("Product").Preload("Variants").First(&flat, "sku = ?", product.ProductNumber).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return importer.importParsedVariant(product)
}
//todo not found bolsa create etmeli
return gm.Product{}, err
}
if flat.Product.Type == "configurable" {
productRepo := InitProductRepo(&product, importer.GetColorOption(product.Color), importer.GetSexOption(product.Cinsiyet))
productRepo.ImageType = "lcw"
if brand, err := gm.FindOrCreateBrand(importer.Baza, product.Brand, productRepo.Categories); err != nil {
return gm.Product{}, err
} else {
productRepo.Brand = brand
}
for _, variant := range *product.SizeVariants {
found := false
for _, flatVariant := range flat.Variants {
if variant.AttributeValue == flatVariant.BoyutLabel || variant.AttributeValue == flatVariant.SizeLabel {
if !variant.Sellable {
importer.Baza.Model(&flatVariant).Update("status", false)
} else {
importer.updatePrice(variant.Price, &flatVariant)
if !flatVariant.Status {
importer.Baza.Model(&flatVariant).Update("status", true)
}
}
found = true
break
}
}
if variant.Sellable && !found {
// insert variant
var sizeOPtion gm.AttributeOption
if variant.AttributeName == "Beden" {
sizeOPtion = gm.GetAttributeOption(importer.Baza, importer.AttributesMap["size"].ID, variant.AttributeValue)
} else {
sizeOPtion = gm.GetAttributeOption(importer.Baza, importer.AttributesMap["boyut"].ID, variant.AttributeValue)
}
log.Println(variant)
sku := fmt.Sprintf("%s-%d", product.ProductNumber, variant.ItemNumber)
log.Println(sku)
variantProduct := productRepo.makeVariant(flat.ProductID, flat.Product.AttributeFamilyID, sku)
variantProduct.AttributeValues = productRepo.getVariantAttributes(importer.AttributesMap, &variant, sizeOPtion.ID)
if err := importer.Baza.Omit("Categories").Create(&variantProduct).Error; err != nil {
log.Println("Variant Product Create Error: " + err.Error())
}
variantFlat := productRepo.makeVariantFlat(variant, sizeOPtion.ID, flat.ID, variantProduct.ID)
if err := importer.Baza.Create(&variantFlat).Error; err != nil {
log.Println("Variant Flat Create Error: " + err.Error())
}
flat.Variants = append(flat.Variants, variantFlat)
}
}
calcPrice(flat.Variants, &flat)
importer.Baza.Omit("ParentID", "CreatedAt", "Variants", "SpecialPrice").Save(&flat)
} else { //simple
importer.updatePrice(product.Price, &flat)
}
return flat.Product, nil
}
func (importer *Importer) UpdateOrCreateLCW(product models.Product) (instance *Importer) {
var firstProduct gm.Product
result := importer.Baza.First(&firstProduct, "sku=?", product.ProductNumber)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
firstProduct, importer.Error = importer.importParsedVariant(product)
} else {
firstProduct, importer.Error = importer.updateParsedVariant(product)
}
var newProducts []gm.Product
if importer.Error != nil {
return importer
} else if &firstProduct != nil {
newProducts = append(newProducts, firstProduct)
}
if product.ColorVariants != nil && len(*product.ColorVariants) > 0 {
linkedProducts := []gm.Product{firstProduct}
for _, colorVariant := range *product.ColorVariants {
var (
variant gm.Product
err error
)
if !colorVariant.IsSellable {
if err = importer.Baza.Model(&gm.ProductFlat{}).Where("sku=?", colorVariant.ProductNumber).Update("status", false).Error; err != nil {
log.Println(err)
}
continue
} else {
//todo check parsed import variant
if variant, err = importer.importParsedVariant(colorVariant); err != nil {
if variant, importer.Error = importer.updateParsedVariant(colorVariant); importer.Error != nil {
return importer
}
linkedProducts = append(linkedProducts, variant)
} else {
newProducts = append(newProducts, variant)
}
}
}
if len(newProducts) > 0 {
// relation
var relation []gm.ProductRelation
for _, linkedProduct := range linkedProducts {
for _, newProd := range newProducts {
relation = append(relation, gm.ProductRelation{ParentID: linkedProduct.ID, ChildID: newProd.ID})
relation = append(relation, gm.ProductRelation{ParentID: newProd.ID, ChildID: linkedProduct.ID})
}
}
if err := importer.Baza.Create(&relation).Error; err != nil {
log.Println(err)
}
}
}
return importer
}
func (importer *Importer) UpdateOrCreate(product models.Product) (instance *Importer) {
firstProduct, err := importer.updateVariant(product)
var newProducts []gm.Product
if err != nil {
helper.Error(err)
firstProduct, importer.Error = importer.updateVariant(product)
if importer.Error != nil {
return importer
}
} else if &firstProduct != nil {
newProducts = append(newProducts, *firstProduct)
}
if product.ColorVariants != nil && len(*product.ColorVariants) > 0 {
linkedProducts := []gm.Product{*firstProduct}
for _, colorVariant := range *product.ColorVariants {
var (
variant *gm.Product
err error
)
if !colorVariant.IsSellable {
if err = importer.Baza.Model(&gm.ProductFlat{}).Where("sku=?", colorVariant.ProductNumber).Update("status", false).Error; err != nil {
log.Println(err)
}
continue
} else {
if variant, err = importer.importVariant(colorVariant); err != nil {
if variant, importer.Error = importer.updateVariant(colorVariant); importer.Error != nil {
return importer
}
linkedProducts = append(linkedProducts, *variant)
} else {
newProducts = append(newProducts, *variant)
}
}
}
if len(newProducts) > 0 {
// relation
var relation []gm.ProductRelation
for _, linkedProduct := range linkedProducts {
for _, newProd := range newProducts {
relation = append(relation, gm.ProductRelation{ParentID: linkedProduct.ID, ChildID: newProd.ID})
relation = append(relation, gm.ProductRelation{ParentID: newProd.ID, ChildID: linkedProduct.ID})
}
}
if err := importer.Baza.Create(&relation).Error; err != nil {
log.Println(err)
}
}
}
return importer
}

View File

@ -0,0 +1,375 @@
package repositories
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"regexp"
helper "sarga_updater/helpers"
models "sarga_updater/trendyol_models"
"strconv"
"strings"
)
type LinkParser struct {
link string
}
func NewLinkParser(link string) LinkParser {
return LinkParser{link: link}
}
func (l LinkParser) ParseLink() (models.TrendyolProductDetailModel, error) {
helper.Info("link: ", l.link)
productId := ""
if isShortLink(l.link) {
productId = getProductIdFromShortLink(l.link)
} else {
productId = getProductIdFromLink(l.link)
}
if len(productId) == 0 {
parseErr := errors.New("can not parse product id")
helper.Error(parseErr)
return models.TrendyolProductDetailModel{}, parseErr
}
helper.Info("productId: ", productId)
return GetProductDetails(productId)
}
func getProductIdFromShortLink(shortLink string) string {
var productId string
client, _ := helper.NewHttpClient()
req, err := http.NewRequest("GET", shortLink, nil)
req.Proto = "HTTP/2.0"
if err != nil {
helper.Error(err)
return ""
}
q := req.URL.Query()
req.URL.RawQuery = q.Encode()
req.Header.Set("Connection", "close")
req = req.WithContext(req.Context())
req.Close = true
client.CloseIdleConnections()
response, err := client.Do(req)
if err != nil {
helper.Error(err)
return productId
}
defer response.Body.Close()
url := response.Request.URL.Path
helper.Info("link url: ", url)
productId = getProductIdFromLink(url)
helper.Info("productId: ", productId)
return productId
}
func getProductIdFromLink(link string) string {
var productId string
if strings.Contains(link, "?") {
link = strings.Split(link, "?")[0]
}
strArr := strings.Split(link, "-")
productId = strArr[len(strArr)-1]
return productId
}
func isShortLink(link string) bool {
return !strings.Contains(link, "trendyol.com")
}
// GetProductDetails return JSON object. Merges color option
func GetProductDetails(productId string) (models.TrendyolProductDetailModel, error) {
var response models.TrendyolProductDetailResponse
productDetailModel := models.TrendyolProductDetailModel{}
// set linearVariants false to get variants
url := "https://public.trendyol.com/discovery-web-productgw-service/api/productDetail/" + productId + "?storefrontId=1&culture=tr-TR&linearVariants=false"
body, err := helper.SendRequest("GET", url, nil, "")
if err != nil {
return productDetailModel, err
}
err = json.Unmarshal(body, &response)
if err != nil {
helper.Error(err)
return productDetailModel, err
}
productDetailModel = response.Result
return productDetailModel, nil
}
// getProductDetailWithOptions returns JSON with variants
func (l LinkParser) GetProductDetailWithOptions(productId, productGroupId int) (models.Product, error) {
primaryProductDetail, err := GetProductDetails(strconv.Itoa(productId))
if err != nil {
return models.Product{}, err
}
productDetailJSON := CreateJSONFromModel(primaryProductDetail)
if productDetailJSON != nil {
colorVariants, err := GetProductColorVariants(productGroupId)
var colorVariantsJson []map[string]interface{}
if err != nil {
return models.Product{}, err
}
// get the color variant products
for _, slicingAttribute := range colorVariants.Result.SlicingAttributes {
for _, attribute := range slicingAttribute.Attributes {
for _, content := range attribute.Contents {
// Don't fetch primary product again.
if content.ID != primaryProductDetail.ID {
productVariantDetail, errGPD := GetProductDetails(strconv.Itoa(content.ID))
if errGPD == nil {
productVariantJSON := CreateJSONFromModel(productVariantDetail)
if productVariantJSON != nil {
colorVariantsJson = append(colorVariantsJson, productVariantJSON)
}
}
}
}
}
// Color variant count
if len(slicingAttribute.Attributes) > 0 {
productDetailJSON["color_variant_count"] = len(slicingAttribute.Attributes)
productDetailJSON["color_variants"] = colorVariantsJson
}
}
}
jsonString, _ := json.Marshal(productDetailJSON)
// convert json to struct
converted := models.Product{}
json.Unmarshal(jsonString, &converted)
return converted, nil
}
// getProductColorVariants returns color options of product
func GetProductColorVariants(productGroupId int) (models.TrendyolProductVariantsResponse, error) {
url := "https://public.trendyol.com/discovery-web-productgw-service/api/productGroup/" + strconv.Itoa(productGroupId) + "?storefrontId=1&culture=tr-TR"
var response models.TrendyolProductVariantsResponse
body, err := helper.SendRequest("GET", url, nil, "")
if err != nil {
return response, err
}
err = json.Unmarshal(body, &response)
if err != nil {
helper.Error(err)
return response, err
}
return response, nil
}
// createJSONFromModel creates json from [trendyol.TrendyolProductDetailModel] and returns
func CreateJSONFromModel(model models.TrendyolProductDetailModel) map[string]interface{} {
json := map[string]interface{}{}
// get weight from categories (Stored in helper array (RAM))
weight := helper.GetCategoryWeight(model.Category.BeautifiedName)
productGroupId := strconv.Itoa(model.ProductGroupID)
json["_id"] = productGroupId
json["product_group_id"] = productGroupId
json["vendor"] = "trendyol"
json["sku"] = "p-" + strconv.Itoa(model.ID)
json["product_number"] = strconv.Itoa(model.ID)
json["product_code"] = model.ProductCode
json["name"] = model.Name
json["sellable"] = model.IsSellable
json["favorite_count"] = model.FavoriteCount
json["weight"] = weight
json["name_with_product_code"] = model.NameWithProductCode
json["url_key"] = "https://www.trendyol.com" + model.URL
json["images"] = model.Images
json["brand"] = model.Brand.Name
json["cinsiyet"] = model.Gender.Name
json["description"] = model.Description
json["descriptions"] = model.ContentDescriptions
json["short_description"] = model.Description
// nested structure
json["price"] = make(map[string]interface{})
json["price"].(map[string]interface{})["originalPrice"] = model.Price.OriginalPrice
json["price"].(map[string]interface{})["sellingPrice"] = model.Price.SellingPrice
json["price"].(map[string]interface{})["discountedPrice"] = model.Price.DiscountedPrice
attrLen := len(model.Attributes)
if attrLen != 0 {
for i := 0; i < attrLen; i++ {
attribute := model.Attributes[i]
if attribute.Key.Name == "Renk" {
json["color"] = attribute.Value.Name
}
}
}
// set categories with value 1
json["categories"] = []int{1}
attributes := make([]map[string]string, 0)
for _, attr := range model.Attributes {
var re = regexp.MustCompile(`/[^A-Z0-9]/ig`)
keyStr := re.ReplaceAllString(attr.Key.Name, `_`)
key := strings.ToLower(keyStr)
attribute := map[string]string{
key: attr.Value.Name,
}
attributes = append(attributes, attribute)
}
json["attributes"] = attributes
var variants []models.Variant
// if show variants, then it is configurable product.
if model.ShowVariants {
for i := 0; i < len(model.Variants); i++ {
variant := model.Variants[i]
if variant.Sellable {
stockType := reflect.TypeOf(variant.Stock)
if stockType == nil {
variants = append(variants, variant)
} else {
// scrape is via link parse
// we need to parse other variants
// convert stock to int
stock, ok := variant.Stock.(float64)
if ok {
if stock > 0 {
variants = append(variants, variant)
}
}
}
}
}
for i := 0; i < len(model.AllVariants); i++ {
singleVariant := model.AllVariants[i]
// get the first variant for attribute info
fv := variants[0]
variant := models.Variant{
AttributeID: fv.AttributeID, //
AttributeName: fv.AttributeName, // Sample: "Beden"
AttributeType: fv.AttributeType, // Sample: "Size"
AttributeValue: singleVariant.Value,
Price: models.Price{
ProfitMargin: 0,
DiscountedPrice: struct {
Text string "json:\"text\""
Value float64 "json:\"value\""
}{
Text: fmt.Sprintf("%f", singleVariant.Price),
Value: singleVariant.Price,
},
SellingPrice: struct {
Text string "json:\"text\""
Value float64 "json:\"value\""
}{
Text: fmt.Sprintf("%f", singleVariant.Price),
Value: singleVariant.Price,
},
OriginalPrice: struct {
Text string "json:\"text\""
Value float64 "json:\"value\""
}{
Text: fmt.Sprintf("%f", singleVariant.Price),
Value: singleVariant.Price,
},
Currency: singleVariant.Currency,
},
ItemNumber: singleVariant.ItemNumber,
Sellable: singleVariant.InStock,
}
exists := helper.IsVariantsAdded(variants, variant.ItemNumber)
if !exists {
variants = append(variants, variant)
}
}
for i := 0; i < len(model.AlternativeVariants); i++ {
alternativeVariant := model.AlternativeVariants[i]
if len(variants) > 0 {
// get the first variant for attribute info
fv := variants[0]
variant := models.Variant{
AttributeID: fv.AttributeID, //
AttributeName: fv.AttributeName, // Sample: "Beden"
AttributeType: fv.AttributeType, // Sample: "Size"
AttributeValue: alternativeVariant.AttributeValue,
Price: alternativeVariant.Price,
ItemNumber: alternativeVariant.ItemNumber,
Sellable: alternativeVariant.Quantity > 0,
}
exists := helper.IsVariantsAdded(variants, variant.ItemNumber)
if !exists {
variants = append(variants, variant)
}
}
}
json["size_variants"] = variants
}
if model.ShowVariants && len(variants) == 0 {
return nil
}
return json
}

View File

@ -0,0 +1,308 @@
package repositories
import (
"fmt"
"math"
gm "sarga_updater/bagisto_models"
models "sarga_updater/trendyol_models"
"strconv"
)
type ProductRepo struct {
Categories []gm.Category
Brand gm.Brand
Weight float64
Description string
Keywords string
Data *models.Product
Error error
ColorOption gm.AttributeOption
SexOption gm.AttributeOption
ImageType string
}
func InitProductRepo(data *models.Product, color, sex gm.AttributeOption) *ProductRepo {
weight, wError := strconv.ParseFloat(data.Weight, 64)
if wError != nil || weight == 0 {
weight = 0.5
}
var description string
for _, desc := range data.Descriptions {
description += "<p>" + desc.Description + "</p>"
}
instance := &ProductRepo{
Weight: weight,
Description: description,
Data: data,
ColorOption: color,
SexOption: sex,
ImageType: "cdn",
}
return instance
}
func (pr *ProductRepo) SetCategories(categories []gm.Category) {
pr.Categories = categories
pr.Keywords = pr.Data.Brand
for _, cat := range categories {
//log.Println(cat)
if len(cat.Translations) > 0 && cat.Translations[0].MetaKeywords != "" {
translation := cat.Translations[0]
pr.Keywords += "," + translation.MetaKeywords
}
}
}
func (pr *ProductRepo) HasSizeVariants() bool {
return pr.Data.SizeVariants != nil && len(*pr.Data.SizeVariants) > 0
}
func (pr *ProductRepo) HasColorVariants() bool {
return pr.Data.ColorVariants != nil && len(*pr.Data.ColorVariants) > 0
}
func (pr *ProductRepo) makeProduct(imp *Importer) gm.Product {
var famID uint = 1
if len(imp.families) > 0 { //todo make real fam function
famID = imp.families[0].ID
}
product := gm.Product{
Sku: pr.Data.ProductNumber,
AttributeFamilyID: famID,
Categories: pr.Categories,
AttributeValues: pr.getProductAttributes(imp.AttributesMap, pr.Data),
}
if pr.Data.Brand != "" {
product.BrandID = pr.Brand.ID
}
if pr.HasSizeVariants() {
product.Type = "configurable"
if (*pr.Data.SizeVariants)[0].AttributeName == "Beden" {
product.SuperAttributes = []gm.Attribute{imp.AttributesMap["size"]}
} else {
product.SuperAttributes = []gm.Attribute{imp.AttributesMap["boyut"]}
product.AttributeFamilyID = 4 // todo fix hardcode
}
} else {
product.Type = "simple"
price := pr.Data.Price
if price.OriginalPrice.Value > price.DiscountedPrice.Value {
product.AttributeValues = append(product.AttributeValues, []gm.ProductAttributeValue{
{AttributeID: imp.AttributesMap["price"].ID, FloatValue: price.OriginalPrice.Value},
{AttributeID: imp.AttributesMap["special_price"].ID, FloatValue: price.DiscountedPrice.Value},
}...)
} else {
product.AttributeValues = append(product.AttributeValues, gm.ProductAttributeValue{
AttributeID: imp.AttributesMap["price"].ID, FloatValue: price.DiscountedPrice.Value,
})
}
}
for _, element := range pr.Data.Images {
product.Images = append(product.Images, gm.ProductImage{Type: pr.ImageType, Path: element})
}
return product
}
func (pr *ProductRepo) makeVariant(parentID, famID uint, sku string) gm.Product {
product := gm.Product{
Sku: sku,
Type: "simple",
AttributeFamilyID: famID,
Categories: pr.Categories,
ParentID: parentID,
}
if pr.Data.Brand != "" {
product.BrandID = pr.Brand.ID
}
return product
}
func (pr *ProductRepo) makeProductFlat(productId uint) gm.ProductFlat {
flat := gm.ProductFlat{
ProductID: productId,
Status: true,
VisibleIndividually: true,
Name: pr.Data.Name,
Sku: pr.Data.ProductNumber,
//ProductNumber: pr.Data.ProductNumber,
Description: pr.Description,
//UrlKey: pr.Data.ProductGroupID,
Weight: pr.Weight,
FavoritesCount: uint(pr.Data.FavoriteCount),
MetaKeywords: pr.Keywords,
MaxPrice: 0,
MinPrice: 0,
Price: 0,
}
if pr.Data.Color != "" {
flat.Color = int(pr.ColorOption.ID)
flat.ColorLabel = pr.Data.Color
}
if pr.Data.Brand != "" {
flat.BrandID = pr.Brand.ID
}
if pr.Data.Cinsiyet != "" {
flat.Cinsiyet = int(pr.SexOption.ID)
flat.CinsiyetLabel = pr.Data.Cinsiyet
}
if !pr.HasSizeVariants() {
flat.MinPrice = pr.Data.Price.DiscountedPrice.Value
flat.MaxPrice = math.Max(pr.Data.Price.OriginalPrice.Value, pr.Data.Price.DiscountedPrice.Value)
flat.Price = flat.MaxPrice
if flat.MinPrice < flat.MaxPrice {
flat.SpecialPrice = flat.MinPrice
}
}
//else {//todo calculate price after variants inserted
// for _, variant := range *pr.Data.SizeVariants {
// price := variant.Price
//
// if flat.MinPrice == 0 || flat.MinPrice > price.DiscountedPrice.Value {
// flat.MaxPrice = price.DiscountedPrice.Value
// }
//
// maxPrice := math.Max(price.OriginalPrice.Value, price.DiscountedPrice.Value)
// if flat.MaxPrice == 0 || flat.MaxPrice < maxPrice {
// flat.MaxPrice = maxPrice
// }
// }
//
//}
return flat
}
func (pr *ProductRepo) makeVariantFlat(variant models.Variant, SizID, parentID, productID uint) gm.ProductFlat {
maxPRice := math.Max(variant.Price.OriginalPrice.Value, variant.Price.DiscountedPrice.Value)
sku := fmt.Sprintf("%s-%d", pr.Data.ProductNumber, variant.ItemNumber)
flat := gm.ProductFlat{
ParentID: parentID,
Status: true,
Name: pr.Data.Name,
Sku: sku,
ProductNumber: fmt.Sprintf("%d", variant.ItemNumber),
Weight: pr.Weight,
FavoritesCount: uint(pr.Data.FavoriteCount),
//SizeLabel: variant.AttributeValue,
//Size: int(SizID),
MaxPrice: maxPRice,
MinPrice: variant.Price.DiscountedPrice.Value,
Price: maxPRice,
ProductID: productID,
}
if variant.AttributeName == "Beden" {
flat.Size = int(SizID)
flat.SizeLabel = variant.AttributeValue
} else {
flat.Boyut = int(SizID)
flat.BoyutLabel = variant.AttributeValue
}
if flat.MaxPrice > flat.MinPrice {
flat.SpecialPrice = flat.MinPrice
}
if pr.Data.Color != "" {
flat.Color = int(pr.ColorOption.ID)
flat.ColorLabel = pr.Data.Color
}
if pr.Data.Brand != "" {
flat.BrandID = pr.Brand.ID
}
if pr.Data.Cinsiyet != "" {
flat.Cinsiyet = int(pr.SexOption.ID)
flat.CinsiyetLabel = pr.Data.Cinsiyet
}
return flat
}
func (pr *ProductRepo) getProductAttributes(AttributesMap map[string]gm.Attribute, product *models.Product) []gm.ProductAttributeValue {
attributes := []gm.ProductAttributeValue{
{AttributeID: AttributesMap["source"].ID, TextValue: product.URLKey},
{AttributeID: AttributesMap["favoritesCount"].ID, IntegerValue: product.FavoriteCount},
{AttributeID: AttributesMap["sku"].ID, TextValue: product.ProductNumber},
{AttributeID: AttributesMap["name"].ID, TextValue: product.Name, Channel: "default", Locale: "tm"},
{AttributeID: AttributesMap["weight"].ID, TextValue: product.Weight},
{AttributeID: AttributesMap["status"].ID, BooleanValue: true},
{AttributeID: AttributesMap["visible_individually"].ID, BooleanValue: true},
{AttributeID: AttributesMap["description"].ID, TextValue: pr.Description, Channel: "default", Locale: "tm"},
{AttributeID: AttributesMap["meta_keywords"].ID, TextValue: pr.Keywords, Channel: "default", Locale: "tm"},
}
if product.Color != "" {
attributes = append(attributes, gm.ProductAttributeValue{AttributeID: AttributesMap["color"].ID, IntegerValue: int(pr.ColorOption.ID)})
}
if product.Cinsiyet != "" {
attributes = append(attributes, gm.ProductAttributeValue{AttributeID: AttributesMap["cinsiyet"].ID, IntegerValue: int(pr.SexOption.ID)})
}
return attributes
}
func (pr *ProductRepo) getVariantAttributes(AttributesMap map[string]gm.Attribute, product *models.Variant, SizID uint) []gm.ProductAttributeValue {
price := math.Max(product.Price.OriginalPrice.Value, product.Price.DiscountedPrice.Value)
sku := fmt.Sprintf("%s-%d", pr.Data.ProductNumber, product.ItemNumber)
attributes := []gm.ProductAttributeValue{
{AttributeID: AttributesMap["source"].ID, TextValue: pr.Data.URLKey},
{AttributeID: AttributesMap["sku"].ID, TextValue: sku}, //todo unique
{AttributeID: AttributesMap["product_number"].ID, TextValue: fmt.Sprintf("%d", product.ItemNumber)}, //todo unique
{AttributeID: AttributesMap["name"].ID, TextValue: pr.Data.Name, Channel: "default", Locale: "tm"},
{AttributeID: AttributesMap["weight"].ID, TextValue: pr.Data.Weight},
{AttributeID: AttributesMap["status"].ID, BooleanValue: true},
//{AttributeID: AttributesMap["size"].ID, IntegerValue: int(SizID)},
{AttributeID: AttributesMap["price"].ID, FloatValue: price},
}
if product.AttributeName == "Beden" {
attributes = append(attributes, gm.ProductAttributeValue{AttributeID: AttributesMap["size"].ID, IntegerValue: int(SizID)})
} else {
attributes = append(attributes, gm.ProductAttributeValue{AttributeID: AttributesMap["boyut"].ID, IntegerValue: int(SizID)})
}
if pr.Data.Color != "" {
attributes = append(attributes, gm.ProductAttributeValue{AttributeID: AttributesMap["color"].ID, IntegerValue: int(pr.ColorOption.ID)})
}
if pr.Data.Cinsiyet != "" {
attributes = append(attributes, gm.ProductAttributeValue{AttributeID: AttributesMap["cinsiyet"].ID, IntegerValue: int(pr.SexOption.ID)})
}
if product.Price.OriginalPrice.Value > product.Price.DiscountedPrice.Value {
attributes = append(attributes, gm.ProductAttributeValue{AttributeID: AttributesMap["special_price"].ID, FloatValue: product.Price.DiscountedPrice.Value})
}
return attributes
}

View File

@ -0,0 +1,30 @@
package trendyol_models
type Category struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
TrendyolURL string `json:"trendyol_url"`
LcwURL string `json:"lcw_url"`
DisplayMode string `json:"display_mode"`
ImageURL interface{} `json:"image_url"`
CategoryIconPath interface{} `json:"category_icon_path"`
ProductLimit int `json:"product_limit"`
Children []Category
}
type BagistoResponse struct {
Data []Category
}
type CouchCategory struct {
Rev string `json:"_rev"`
CreatedAt string `json:"createdAt"`
ID string `json:"id"`
Name string `json:"name"`
Order string `json:"order"`
ParentID string `json:"parent_id"`
SargaID string `json:"sarga_id"`
Slug string `json:"slug"`
UpdatedAt string `json:"updatedAt"`
Weight string `json:"weight"`
}

309
trendyol_models/product.go Normal file
View File

@ -0,0 +1,309 @@
package trendyol_models
type Product struct {
Attributes []map[string]string `json:"attributes"`
Brand string `json:"brand"`
Categories []int `json:"categories"`
Cinsiyet string `json:"cinsiyet"`
Color string `json:"color"`
ColorVariantCount int `json:"color_variant_count"`
ColorVariants *[]Product `json:"color_variants"`
Description string `json:"description"`
IsSellable bool `json:"sellable"`
FavoriteCount int `json:"favorite_count"`
Descriptions []struct {
Description string `json:"description"`
Bold bool `json:"bold"`
} `json:"descriptions"`
Images []string `json:"images"`
Name string `json:"name"`
NameWithProductCode string `json:"name_with_product_code"`
Price Price `json:"price"`
ProductCode string `json:"product_code"`
ProductGroupID string `json:"product_group_id"`
ProductNumber string `json:"product_number"`
ShortDescription string `json:"short_description"`
SizeVariants *[]Variant `json:"size_variants"`
Sku string `json:"sku"`
Stock interface{} `json:"stock"`
URLKey string `json:"url_key"`
Vendor string `json:"vendor"`
Weight string `json:"weight"`
}
type Price struct {
ProfitMargin int `json:"profitMargin"`
DiscountedPrice PriceValue `json:"discountedPrice"`
SellingPrice PriceValue `json:"sellingPrice"`
OriginalPrice PriceValue `json:"originalPrice"`
Currency string `json:"currency"`
}
type PriceValue struct {
Text string `json:"text"`
Value float64 `json:"value"`
}
type Variant struct {
AttributeID int `json:"attributeId"`
AttributeName string `json:"attributeName"`
AttributeType string `json:"attributeType"`
AttributeValue string `json:"attributeValue"`
Stamps []interface{} `json:"stamps"`
Price Price `json:"price"`
FulfilmentType string `json:"fulfilmentType"`
AttributeBeautifiedValue string `json:"attributeBeautifiedValue"`
IsWinner bool `json:"isWinner"`
ListingID string `json:"listingId"`
Stock interface{} `json:"stock"`
Sellable bool `json:"sellable"`
AvailableForClaim bool `json:"availableForClaim"`
Barcode string `json:"barcode"`
ItemNumber int `json:"itemNumber"`
DiscountedPriceInfo string `json:"discountedPriceInfo"`
HasCollectable bool `json:"hasCollectable"`
RushDeliveryMerchantListingExist bool `json:"rushDeliveryMerchantListingExist"`
LowerPriceMerchantListingExist bool `json:"lowerPriceMerchantListingExist"`
}
type Row struct {
Key string `json:"key"`
Value map[string]interface{} `json:"value"`
Doc Product
}
type BagistoModelResponse struct {
Rows []Row `json:"rows"`
}
type TrendyolProductDetailResponse struct {
IsSuccess bool `json:"isSuccess"`
StatusCode int `json:"statusCode"`
Error interface{} `json:"error"`
Result TrendyolProductDetailModel `json:"result"`
Headers struct {
Tysidecarcachable string `json:"tysidecarcachable"`
} `json:"headers"`
}
type TrendyolProductDetailModel struct {
AlternativeVariants []AlternativeVariant `json:"alternativeVariants"`
Attributes []struct {
Key struct {
Name string `json:"name"`
ID int `json:"id"`
} `json:"key"`
Value struct {
Name string `json:"name"`
ID int `json:"id"`
} `json:"value"`
Starred bool `json:"starred"`
} `json:"attributes"`
Variants []Variant `json:"variants"`
OtherMerchants []interface{} `json:"otherMerchants"`
Campaign struct {
ID int `json:"id"`
Name string `json:"name"`
StartDate string `json:"startDate"`
EndDate string `json:"endDate"`
IsMultipleSupplied bool `json:"isMultipleSupplied"`
StockTypeID int `json:"stockTypeId"`
URL string `json:"url"`
ShowTimer bool `json:"showTimer"`
} `json:"campaign"`
Category struct {
ID int `json:"id"`
Name string `json:"name"`
Hierarchy string `json:"hierarchy"`
Refundable bool `json:"refundable"`
BeautifiedName string `json:"beautifiedName"`
IsVASEnabled bool `json:"isVASEnabled"`
} `json:"category"`
Brand struct {
IsVirtual bool `json:"isVirtual"`
BeautifiedName string `json:"beautifiedName"`
ID int `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
} `json:"brand"`
Color string `json:"color"`
MetaBrand struct {
ID int `json:"id"`
Name string `json:"name"`
BeautifiedName string `json:"beautifiedName"`
IsVirtual bool `json:"isVirtual"`
Path string `json:"path"`
} `json:"metaBrand"`
ShowVariants bool `json:"showVariants"`
ShowSexualContent bool `json:"showSexualContent"`
BrandCategoryBanners []interface{} `json:"brandCategoryBanners"`
AllVariants []struct {
ItemNumber int `json:"itemNumber"`
Value string `json:"value"`
InStock bool `json:"inStock"`
Currency string `json:"currency"`
Barcode string `json:"barcode"`
Price float64 `json:"price"`
} `json:"allVariants"`
OtherMerchantVariants []interface{} `json:"otherMerchantVariants"`
InstallmentBanner interface{} `json:"installmentBanner"`
IsVasEnabled bool `json:"isVasEnabled"`
OriginalCategory struct {
ID int `json:"id"`
Name string `json:"name"`
Hierarchy string `json:"hierarchy"`
Refundable bool `json:"refundable"`
BeautifiedName string `json:"beautifiedName"`
IsVASEnabled bool `json:"isVASEnabled"`
} `json:"originalCategory"`
Landings []interface{} `json:"landings"`
ID int `json:"id"`
ProductCode string `json:"productCode"`
Name string `json:"name"`
NameWithProductCode string `json:"nameWithProductCode"`
Description string `json:"description"`
ContentDescriptions []struct {
Description string `json:"description"`
Bold bool `json:"bold"`
} `json:"contentDescriptions"`
ProductGroupID int `json:"productGroupId"`
Tax int `json:"tax"`
BusinessUnit string `json:"businessUnit"`
MaxInstallment int `json:"maxInstallment"`
Gender struct {
Name string `json:"name"`
ID int `json:"id"`
} `json:"gender"`
URL string `json:"url"`
Images []string `json:"images"`
IsSellable bool `json:"isSellable"`
IsBasketDiscount bool `json:"isBasketDiscount"`
HasStock bool `json:"hasStock"`
Price Price `json:"price"`
IsFreeCargo bool `json:"isFreeCargo"`
Promotions []struct {
PromotionRemainingTime string `json:"promotionRemainingTime"`
Type int `json:"type"`
Text string `json:"text"`
ID int `json:"id"`
Link string `json:"link"`
} `json:"promotions"`
Merchant struct {
IsSearchableMerchant bool `json:"isSearchableMerchant"`
Stickers []interface{} `json:"stickers"`
ID int `json:"id"`
Name string `json:"name"`
OfficialName string `json:"officialName"`
CityName string `json:"cityName"`
TaxNumber string `json:"taxNumber"`
SellerScore float64 `json:"sellerScore"`
SellerScoreColor string `json:"sellerScoreColor"`
DeliveryProviderName string `json:"deliveryProviderName"`
SellerLink string `json:"sellerLink"`
} `json:"merchant"`
DeliveryInformation struct {
IsRushDelivery bool `json:"isRushDelivery"`
DeliveryDate string `json:"deliveryDate"`
} `json:"deliveryInformation"`
CargoRemainingDays int `json:"cargoRemainingDays"`
IsMarketplace bool `json:"isMarketplace"`
ProductStamps []struct {
Type string `json:"type"`
ImageURL string `json:"imageUrl"`
Position string `json:"position"`
AspectRatio float64 `json:"aspectRatio"`
Priority int `json:"priority"`
PriceTagStamp bool `json:"priceTagStamp,omitempty"`
} `json:"productStamps"`
HasHTMLContent bool `json:"hasHtmlContent"`
FavoriteCount int `json:"favoriteCount"`
UxLayout string `json:"uxLayout"`
IsDigitalGood bool `json:"isDigitalGood"`
IsRunningOut bool `json:"isRunningOut"`
ScheduledDelivery bool `json:"scheduledDelivery"`
RatingScore struct {
AverageRating float64 `json:"averageRating"`
TotalRatingCount int `json:"totalRatingCount"`
TotalCommentCount int `json:"totalCommentCount"`
} `json:"ratingScore"`
ShowStarredAttributes bool `json:"showStarredAttributes"`
ReviewsURL string `json:"reviewsUrl"`
QuestionsURL string `json:"questionsUrl"`
SellerQuestionEnabled bool `json:"sellerQuestionEnabled"`
SizeExpectationAvailable bool `json:"sizeExpectationAvailable"`
CrossPromotionAward struct {
AwardType interface{} `json:"awardType"`
AwardValue interface{} `json:"awardValue"`
ContentID int `json:"contentId"`
MerchantID int `json:"merchantId"`
} `json:"crossPromotionAward"`
RushDeliveryMerchantListingExist bool `json:"rushDeliveryMerchantListingExist"`
LowerPriceMerchantListingExist bool `json:"lowerPriceMerchantListingExist"`
ShowValidFlashSales bool `json:"showValidFlashSales"`
ShowExpiredFlashSales bool `json:"showExpiredFlashSales"`
WalletRebate struct {
MinPrice int `json:"minPrice"`
MaxPrice int `json:"maxPrice"`
RebateRatio float64 `json:"rebateRatio"`
} `json:"walletRebate"`
IsArtWork bool `json:"isArtWork"`
}
type TrendyolProductVariantsResponse struct {
IsSuccess bool `json:"isSuccess"`
StatusCode int `json:"statusCode"`
Error interface{} `json:"error"`
Result struct {
SlicingAttributes []struct {
Brand struct {
BeautifiedName string `json:"beautifiedName"`
ID int `json:"id"`
Name string `json:"name"`
IsVirtual bool `json:"isVirtual"`
Path string `json:"path"`
} `json:"brand"`
Attributes []struct {
Contents []struct {
URL string `json:"url"`
ID int `json:"id"`
ImageURL string `json:"imageUrl"`
Name string `json:"name"`
Price struct {
DiscountedPrice struct {
Text string `json:"text"`
Value float64 `json:"value"`
} `json:"discountedPrice"`
OriginalPrice struct {
Text string `json:"text"`
Value float64 `json:"value"`
} `json:"originalPrice"`
SellingPrice struct {
Text string `json:"text"`
Value float64 `json:"value"`
} `json:"sellingPrice"`
} `json:"price"`
} `json:"contents"`
Name string `json:"name"`
BeautifiedName string `json:"beautifiedName"`
} `json:"attributes"`
Type string `json:"type"`
DisplayName string `json:"displayName"`
Order int `json:"order"`
DisplayType int `json:"displayType"`
} `json:"slicingAttributes"`
} `json:"result"`
Headers struct {
Tysidecarcachable string `json:"tysidecarcachable"`
} `json:"headers"`
}
type AlternativeVariant struct {
AttributeValue string `json:"attributeValue"`
AttributeBeautifiedValue string `json:"attributeBeautifiedValue"`
CampaignID int `json:"campaignId"`
MerchantID int `json:"merchantId"`
URLQuery string `json:"urlQuery"`
ListingID string `json:"listingId"`
ItemNumber int `json:"itemNumber"`
Barcode string `json:"barcode"`
Stock interface{} `json:"stock"`
Quantity int `json:"quantity"`
Price Price `json:"price"`
}

View File

@ -0,0 +1,7 @@
package trendyol_models
type SizeVariants struct {
Price float64 `json:"price"`
Size string `json:"size"`
Stock int `json:"stock"`
}