link parse test ready

This commit is contained in:
merdan 2022-09-07 17:55:55 +05:00
parent 62617e2b6b
commit 841c4c66b3
13 changed files with 851 additions and 62 deletions

View File

@ -1,4 +1,4 @@
package importer package controller
import ( import (
gm "db_service/gorm_models" gm "db_service/gorm_models"
@ -18,14 +18,16 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
var mainCategories []gm.Category var (
var baza *gorm.DB mainCategories []gm.Category
var mainImportWG, famAndSellerWG sync.WaitGroup baza *gorm.DB
var families []gm.AttributeFamily mainImportWG, famAndSellerWG sync.WaitGroup
var sellers = make(map[string]gm.MarketplaceSeller) families []gm.AttributeFamily
var attributesMap = make(map[string]gm.Attribute) sellers = make(map[string]gm.MarketplaceSeller)
attributesMap = make(map[string]gm.Attribute)
)
func Start(w http.ResponseWriter, route *http.Request) { func StartImport(w http.ResponseWriter, route *http.Request) {
start := time.Now() start := time.Now()
r := new(big.Int) r := new(big.Int)
@ -95,7 +97,7 @@ func Start(w http.ResponseWriter, route *http.Request) {
for _, element := range mainCategories { for _, element := range mainCategories {
slug := element.Translations[0].Slug slug := element.Translations[0].Slug
go startImport("ty_db_"+slug, baza) go importCategoryProducts("ty_db_"+slug, baza)
// fmt.Println(<-result) // fmt.Println(<-result)
} }
@ -113,7 +115,7 @@ func Start(w http.ResponseWriter, route *http.Request) {
} }
func startImport(dbName string, db *gorm.DB) { func importCategoryProducts(dbName string, db *gorm.DB) {
defer mainImportWG.Done() defer mainImportWG.Done()
dbExists := helper.CheckDBExists(os.Getenv("couch_db_source") + dbName) dbExists := helper.CheckDBExists(os.Getenv("couch_db_source") + dbName)
@ -135,7 +137,7 @@ func startImport(dbName string, db *gorm.DB) {
skip += limit skip += limit
body, err := helper.SendRequest("GET", url, nil, "", true) body, err := helper.SendRequest("GET", url, nil, "")
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
@ -151,7 +153,7 @@ func startImport(dbName string, db *gorm.DB) {
//itearate 100 row products //itearate 100 row products
for _, element := range response.Rows { for _, element := range response.Rows {
importProduct(element.Doc, db) ImportProduct(element.Doc, db)
} }
} }
} else { } else {
@ -166,7 +168,7 @@ func getTotalDocumentCount(db string) int {
url := os.Getenv("couch_db_source") + db url := os.Getenv("couch_db_source") + db
body, err := helper.SendRequest("GET", url, nil, "", true) body, err := helper.SendRequest("GET", url, nil, "")
if err != nil { if err != nil {
log.Println(err.Error()) log.Println(err.Error())
@ -206,7 +208,7 @@ func getCats(db *gorm.DB, catIDs []int) ([]gm.Category, string, error) {
return categories, keywords, nil return categories, keywords, nil
} }
func importProduct(product models.Product, db *gorm.DB) { func ImportProduct(product models.Product, db *gorm.DB) error {
famAndSellerWG.Wait() //wait until attribute families and sellers are not get from mysql famAndSellerWG.Wait() //wait until attribute families and sellers are not get from mysql
@ -214,7 +216,7 @@ func importProduct(product models.Product, db *gorm.DB) {
if errCat != nil { if errCat != nil {
log.Println(errCat) log.Println(errCat)
return return errCat
} }
var brand gm.Brand var brand gm.Brand
@ -262,7 +264,7 @@ func importProduct(product models.Product, db *gorm.DB) {
errMainProduct := db.Omit("Categories.*", "SuperAttributes.*", "ParentID").Create(&iproduct).Error errMainProduct := db.Omit("Categories.*", "SuperAttributes.*", "ParentID").Create(&iproduct).Error
if errMainProduct != nil { if errMainProduct != nil {
log.Println(errMainProduct.Error()) log.Println(errMainProduct.Error())
return return errMainProduct
} }
mainProductFlat.ProductID = iproduct.ID mainProductFlat.ProductID = iproduct.ID
@ -273,7 +275,7 @@ func importProduct(product models.Product, db *gorm.DB) {
if errProductMainFlat != nil { if errProductMainFlat != nil {
log.Println(errProductMainFlat) log.Println(errProductMainFlat)
return return errProductMainFlat
} }
if len(product.ColorVariants) > 0 { if len(product.ColorVariants) > 0 {
@ -381,7 +383,7 @@ func importProduct(product models.Product, db *gorm.DB) {
if errProdVariant != nil { if errProdVariant != nil {
log.Println(errProdVariant) log.Println(errProdVariant)
return return errProdVariant
} }
flatVariant.ProductID = productVariant.ID flatVariant.ProductID = productVariant.ID
@ -390,7 +392,7 @@ func importProduct(product models.Product, db *gorm.DB) {
if errVariant != nil { if errVariant != nil {
log.Println(errVariant) log.Println(errVariant)
return return errVariant
} }
mainProductFlat.Variants = append(mainProductFlat.Variants, flatVariant) mainProductFlat.Variants = append(mainProductFlat.Variants, flatVariant)
@ -431,7 +433,7 @@ func importProduct(product models.Product, db *gorm.DB) {
if errProdVariant != nil { if errProdVariant != nil {
log.Println(errProdVariant) log.Println(errProdVariant)
return return errProdVariant
} }
variantFlat.ProductID = productVariant.ID variantFlat.ProductID = productVariant.ID
@ -444,7 +446,7 @@ func importProduct(product models.Product, db *gorm.DB) {
if errVariant != nil { if errVariant != nil {
log.Println(errVariant) log.Println(errVariant)
return return errVariant
} }
mainProductFlat.Variants = append(mainProductFlat.Variants, variantFlat) mainProductFlat.Variants = append(mainProductFlat.Variants, variantFlat)
@ -540,7 +542,7 @@ func importProduct(product models.Product, db *gorm.DB) {
if errSizeVar != nil { if errSizeVar != nil {
log.Println(errSizeVar) log.Println(errSizeVar)
return return errSizeVar
} }
flatVariant.ProductID = sizeVariantProduct.ID flatVariant.ProductID = sizeVariantProduct.ID
@ -549,7 +551,7 @@ func importProduct(product models.Product, db *gorm.DB) {
if errVariant != nil { if errVariant != nil {
log.Println(errVariant) log.Println(errVariant)
return return errVariant
} }
mainProductFlat.Variants = append(mainProductFlat.Variants, flatVariant) mainProductFlat.Variants = append(mainProductFlat.Variants, flatVariant)
@ -571,6 +573,8 @@ func importProduct(product models.Product, db *gorm.DB) {
if errSProduct != nil { if errSProduct != nil {
log.Println(errSProduct) log.Println(errSProduct)
} }
return nil
} }
func createSellerProduct(flat *gm.ProductFlat, sellerURL string) gm.MarketplaceProduct { func createSellerProduct(flat *gm.ProductFlat, sellerURL string) gm.MarketplaceProduct {
@ -719,3 +723,36 @@ func prepearAttributesWithFlat(data *models.Product) ([]gm.ProductAttributeValue
return productAttributeValues, flat return productAttributeValues, flat
} }
//func productAttributesAndFlat(data *models.Product) ([]gm.ProductAttributeValue,gm.ProductFlat){
//
// var description string
//
// for _, desc := range data.Descriptions {
// description += "<p>" + desc.Description + "</p>"
// }
//
// weight, _ := strconv.ParseFloat(data.Weight, 64)
//
// flat := gm.ProductFlat{
// Status: true,
// VisibleIndividually: true,
// Name: data.Name,
// Sku: data.ProductGroupID,
// ProductNumber: data.ProductNumber,
// Description: description,
// UrlKey: data.ProductGroupID,
// Weight: weight,
// FavoritesCount: uint(data.FavoriteCount),
// }
// return []gm.ProductAttributeValue{
// {AttributeID: attributesMap["favoritesCount"].ID, IntegerValue: data.FavoriteCount},
// {AttributeID: attributesMap["source"].ID, TextValue: data.URLKey},
// {AttributeID: attributesMap["product_number"].ID, TextValue: data.ProductNumber},
// {AttributeID: attributesMap["name"].ID, TextValue: data.Name, Channel: "default", Locale: "tm"},
// {AttributeID: attributesMap["weight"].ID, TextValue: data.Weight},
// {AttributeID: attributesMap["status"].ID, BooleanValue: true},
// {AttributeID: attributesMap["visible_individually"].ID, BooleanValue: true},
// {AttributeID: attributesMap["description"].ID, TextValue: description, Channel: "default", Locale: "tm"},
// },flat
//}

View File

@ -0,0 +1,64 @@
package controller
import (
"db_service/gorm_models"
"db_service/models"
helper "db_service/pkg"
"db_service/repositories"
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
"net/http"
"os"
"strconv"
"time"
)
func ParseLink(w http.ResponseWriter, route *http.Request) {
start := time.Now()
link := route.URL.Query().Get("url")
helper.Info("link: ", link)
linkParser := repositories.NewLinkParser(link)
//todo
product, err := linkParser.ParseLink()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{
"msg": err.Error(),
})
return
}
jsonProduct, err := linkParser.GetProductDetailWithOptions(product.ID, product.ProductGroupID)
if err != nil {
helper.Error(err)
return
}
baza, err := gorm.Open(mysql.Open(os.Getenv("database_url")), &gorm.Config{})
if err != nil {
helper.Error(err)
return
}
ImportProduct(jsonProduct, baza)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"msg": "Link parsed successfully",
"productGroupId": strconv.Itoa(product.ProductGroupID),
})
elapsed := time.Since(start)
log.Printf("end parse took %s", elapsed)
http.Error(w, fmt.Sprintf("end import took %s", elapsed), http.StatusOK)
}

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gosimple/slug v1.12.0 github.com/gosimple/slug v1.12.0
github.com/joho/godotenv v1.4.0 github.com/joho/godotenv v1.4.0
github.com/leesper/couchdb-golang v1.2.1
gorm.io/driver/mysql v1.3.5 gorm.io/driver/mysql v1.3.5
gorm.io/gorm v1.23.8 gorm.io/gorm v1.23.8
) )

2
go.sum
View File

@ -13,6 +13,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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.3.5 h1:iWBTVW/8Ij5AG4e0G/zqzaJblYkBI1VIL1LG2HUGsvY= gorm.io/driver/mysql v1.3.5 h1:iWBTVW/8Ij5AG4e0G/zqzaJblYkBI1VIL1LG2HUGsvY=
gorm.io/driver/mysql v1.3.5/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= gorm.io/driver/mysql v1.3.5/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE= gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE=

11
main.go
View File

@ -1,7 +1,7 @@
package main package main
import ( import (
importer "db_service/controllers" "db_service/controllers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"log" "log"
@ -23,17 +23,12 @@ func init() {
func main() { func main() {
route := mux.NewRouter() route := mux.NewRouter()
route.HandleFunc("/init-importer", importer.Start) route.HandleFunc("/init-importer", controller.StartImport)
route.HandleFunc("/parse-link", controller.ParseLink)
err := http.ListenAndServe(os.Getenv("port"), route) err := http.ListenAndServe(os.Getenv("port"), route)
if err != nil { if err != nil {
log.Fatal("error: ", err) log.Fatal("error: ", err)
} }
//err := importer.Start()
//if err != nil {
// fmt.Println("err: ", err.Error())
//}
// dene()
} }

View File

@ -16,3 +16,15 @@ type Category struct {
type BagistoResponse struct { type BagistoResponse struct {
Data []Category 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"`
}

View File

@ -77,3 +77,237 @@ type Row struct {
type BagistoModelResponse struct { type BagistoModelResponse struct {
Rows []Row `json:"rows"` 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"`
}

12
pkg/global.go Normal file
View File

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

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"os"
"time" "time"
) )
@ -64,3 +65,22 @@ func CheckDBExists(endpoint string) bool {
return false 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)
}

86
pkg/log.go Normal file
View File

@ -0,0 +1,86 @@
package helper
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{}
}

View File

@ -5,16 +5,15 @@ import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"net/http" "net/http"
"time" "time"
) )
func SendRequest(method string, endpoint string, values []byte, authKey string, isCouchDbReq bool) ([]byte, error) { func SendRequest(method string, endpoint string, values []byte, authKey string) ([]byte, error) {
const ConnectMaxWaitTime = 30 * time.Second const ConnectMaxWaitTime = 30 * time.Second
const RequestMaxWaitTime = 10 * time.Minute const RequestMaxWaitTime = 60 * time.Second
client := http.Client{ client := http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
@ -32,7 +31,7 @@ func SendRequest(method string, endpoint string, values []byte, authKey string,
req, err := http.NewRequestWithContext(ctx, method, endpoint, bytes.NewBuffer(values)) req, err := http.NewRequestWithContext(ctx, method, endpoint, bytes.NewBuffer(values))
req.Proto = "HTTP/2.0" req.Proto = "HTTP/2.0"
if err != nil { if err != nil {
log.Println(err.Error()) Error(err)
return emptyBody, err return emptyBody, err
} }
@ -49,45 +48,27 @@ func SendRequest(method string, endpoint string, values []byte, authKey string,
if response != nil { if response != nil {
response.Body.Close() response.Body.Close()
} }
log.Println(err.Error()) Error(err)
return emptyBody, err return emptyBody, err
} else if err != nil { } else if err != nil {
if response != nil { if response != nil {
response.Body.Close() response.Body.Close()
} }
log.Println(err.Error()) Error(err)
return emptyBody, err return emptyBody, err
} }
if response.StatusCode != http.StatusOK {
// 200 OK if response != nil {
// 201 Created response.Body.Close()
// 202 Accepted
// 404 Not Found
// 409 Conflict
if isCouchDbReq {
// fmt.Printf("responseStatusCode: %d\n", response.StatusCode)
if response.StatusCode == http.StatusNotFound {
if response != nil {
response.Body.Close()
}
return emptyBody, nil
}
// TODO: handle conflict error
} else {
if response.StatusCode != http.StatusOK {
if response != nil {
response.Body.Close()
}
err := fmt.Errorf("response: code: %d, body: %v", response.StatusCode, response.Body)
log.Println(err.Error())
return emptyBody, err
} }
err := fmt.Errorf("response: code: %d, body: %v", response.StatusCode, response.Body)
Error(err)
return emptyBody, err
} }
body, err := ioutil.ReadAll(response.Body) body, err := ioutil.ReadAll(response.Body)
if err != nil { if err != nil {
log.Println(err.Error()) Error(err)
if response != nil { if response != nil {
response.Body.Close() response.Body.Close()
} }
@ -99,3 +80,20 @@ func SendRequest(method string, endpoint string, values []byte, authKey string,
return body, nil return body, nil
} }
func NewHttpClient() (http.Client, context.Context) {
const ConnectMaxWaitTime = 80 * time.Second
const RequestMaxWaitTime = 120 * time.Second
client := http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: ConnectMaxWaitTime,
}).DialContext,
},
}
ctx, cancel := context.WithTimeout(context.Background(), RequestMaxWaitTime)
defer cancel()
return client, ctx
}

View File

@ -0,0 +1,325 @@
package repositories
import (
"db_service/models"
helper "db_service/pkg"
"encoding/json"
"errors"
"net/http"
"reflect"
"regexp"
"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.AlternativeVariants); i++ {
alternativeVariant := model.AlternativeVariants[i]
stockType := reflect.TypeOf(alternativeVariant.Stock)
if stockType == nil && len(variants) > 0 {
// get the first variant for attribute info
fv := variants[0]
variant := models.Variant{
AttributeID: fv.AttributeID,
AttributeName: fv.AttributeName,
AttributeType: fv.AttributeType,
AttributeValue: alternativeVariant.AttributeValue,
Price: alternativeVariant.Price,
}
variants = append(variants, variant)
}
}
json["size_variants"] = variants
}
if model.ShowVariants && len(variants) == 0 {
return nil
}
return json
}

3
vendor/modules.txt vendored
View File

@ -19,6 +19,9 @@ github.com/jinzhu/now
# github.com/joho/godotenv v1.4.0 # github.com/joho/godotenv v1.4.0
## explicit; go 1.12 ## explicit; go 1.12
github.com/joho/godotenv github.com/joho/godotenv
# github.com/leesper/couchdb-golang v1.2.1
## explicit
github.com/leesper/couchdb-golang
# gorm.io/driver/mysql v1.3.5 # gorm.io/driver/mysql v1.3.5
## explicit; go 1.14 ## explicit; go 1.14
gorm.io/driver/mysql gorm.io/driver/mysql