From 606ac264804f42b93bc83dad6fa5e971387aee5e Mon Sep 17 00:00:00 2001 From: GotPPay Date: Sun, 22 Apr 2018 15:16:08 +0200 Subject: [PATCH 1/4] controller with error handling --- .gitignore | 5 +++- backend/server.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 backend/server.go diff --git a/.gitignore b/.gitignore index a1338d6..1ae6c8a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ *.so *.dylib +# monitor binary +gomon_spawn + # Test binary, build with `go test -c` *.test @@ -11,4 +14,4 @@ *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ +.glide/ \ No newline at end of file diff --git a/backend/server.go b/backend/server.go new file mode 100644 index 0000000..42a832b --- /dev/null +++ b/backend/server.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "strconv" + "strings" +) + +const validNumberOfURLParams = 5 +const sizeParamIndex = 3 +const countParamIndex = 4 +const validNumberOfSizeParams = 2 + +const ( + invalidURL = "URL is not valid" + invalidNumberOfParamsError = "Number of params not correct, expected /WIDTHxLENGTH/count" + invalidNumberOfSizeParamError = "Size not correct, expected /WIDTHxLENGTH/count" + invalidParamsType = "Params should be numbers" +) + +func errorResponse(w http.ResponseWriter, errType string) { + fmt.Fprintf(w, errType) +} + +func diecutHandler(w http.ResponseWriter, r *http.Request) { + if params := strings.Split(r.URL.Path, "/"); len(params) == validNumberOfURLParams { + if size := strings.Split(params[sizeParamIndex], "x"); len(size) == validNumberOfSizeParams { + count := params[countParamIndex] + + width, wOk := strconv.ParseInt(size[0], 10, 32) + height, hOk := strconv.ParseInt(size[1], 10, 32) + countNumber, cOk := strconv.ParseInt(count, 10, 32) + + if wOk != nil || hOk != nil || cOk != nil { + errorResponse(w, invalidParamsType) + } else { + fmt.Fprintf(w, "%dx%d | %d", width, height, countNumber) + + } + + } else { + errorResponse(w, invalidNumberOfSizeParamError) + } + } else { + errorResponse(w, invalidNumberOfParamsError) + } +} + +func defaultHandler(w http.ResponseWriter, r *http.Request) { + errorResponse(w, invalidURL) +} + +func main() { + http.HandleFunc("/prices/diecut/", diecutHandler) + http.HandleFunc("/", defaultHandler) + + fmt.Println("Server running on localhost:8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +} From eb8c0f941eddc67ec36fc179d57fb5157c772623 Mon Sep 17 00:00:00 2001 From: GotPPay Date: Mon, 23 Apr 2018 22:20:12 +0200 Subject: [PATCH 2/4] load page with JS generated content and get quantity values --- README.md | 4 ++ backend/server.go | 61 ---------------- server.go | 174 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 61 deletions(-) delete mode 100644 backend/server.go create mode 100644 server.go diff --git a/README.md b/README.md index 2714246..c8f231a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # bilal-vjezba-golang bilal vjezba golang + +Requirements : + +phantomjs (apt-get install phantomjs) \ No newline at end of file diff --git a/backend/server.go b/backend/server.go deleted file mode 100644 index 42a832b..0000000 --- a/backend/server.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "strconv" - "strings" -) - -const validNumberOfURLParams = 5 -const sizeParamIndex = 3 -const countParamIndex = 4 -const validNumberOfSizeParams = 2 - -const ( - invalidURL = "URL is not valid" - invalidNumberOfParamsError = "Number of params not correct, expected /WIDTHxLENGTH/count" - invalidNumberOfSizeParamError = "Size not correct, expected /WIDTHxLENGTH/count" - invalidParamsType = "Params should be numbers" -) - -func errorResponse(w http.ResponseWriter, errType string) { - fmt.Fprintf(w, errType) -} - -func diecutHandler(w http.ResponseWriter, r *http.Request) { - if params := strings.Split(r.URL.Path, "/"); len(params) == validNumberOfURLParams { - if size := strings.Split(params[sizeParamIndex], "x"); len(size) == validNumberOfSizeParams { - count := params[countParamIndex] - - width, wOk := strconv.ParseInt(size[0], 10, 32) - height, hOk := strconv.ParseInt(size[1], 10, 32) - countNumber, cOk := strconv.ParseInt(count, 10, 32) - - if wOk != nil || hOk != nil || cOk != nil { - errorResponse(w, invalidParamsType) - } else { - fmt.Fprintf(w, "%dx%d | %d", width, height, countNumber) - - } - - } else { - errorResponse(w, invalidNumberOfSizeParamError) - } - } else { - errorResponse(w, invalidNumberOfParamsError) - } -} - -func defaultHandler(w http.ResponseWriter, r *http.Request) { - errorResponse(w, invalidURL) -} - -func main() { - http.HandleFunc("/prices/diecut/", diecutHandler) - http.HandleFunc("/", defaultHandler) - - fmt.Println("Server running on localhost:8080") - log.Fatal(http.ListenAndServe(":8080", nil)) -} diff --git a/server.go b/server.go new file mode 100644 index 0000000..c9fbeaa --- /dev/null +++ b/server.go @@ -0,0 +1,174 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "strconv" + "strings" + //"gopkg.in/headzoo/surf.v1" + "github.com/PuerkitoBio/goquery" + //"sourcegraph.com/sourcegraph/go-selenium" + "github.com/benbjohnson/phantomjs" +) + +const validNumberOfURLParams = 5 +const sizeParamIndex = 3 +const countParamIndex = 4 +const validNumberOfSizeParams = 2 + +const maxExpectedNumberOfQuantities = 15 + +const stickerMuleURL = "https://www.stickermule.com/uk/products/die-cut-stickers" + +const ( + invalidURL = "URL is not valid" + invalidNumberOfParamsError = "Number of params not correct, expected /WIDTHxLENGTH/count" + invalidNumberOfSizeParamError = "Size not correct, expected /WIDTHxLENGTH/count" + invalidParamsType = "Params should be numbers" + + tooManyQuantities = "Too many quantities. Didn't expect this" +) + +var listOfValidQuantities []int16 +var stickerMuleWebPage *phantomjs.WebPage +var stickerMuleWebPageDocument *goquery.Document + + +func getListOfQuantities() error { + listOfValidQuantities := make([]int16, maxExpectedNumberOfQuantities) + resultCount := 0 + wasError := false + whichError := error(nil) + stickerMuleWebPageDocument.Find("label.quantity").Each(func(_ int, s *goquery.Selection){ + if wasError{ + return + } + + numberWithoutComma := strings.Replace(s.Text(), ",","",-1) + trimmedNumber := strings.TrimSpace(numberWithoutComma) + + + if sizeInt64, err := strconv.ParseInt(trimmedNumber,10,16) ; err != nil { + wasError = true + whichError = err + }else{ + listOfValidQuantities[resultCount] = int16(sizeInt64) + } + resultCount+=1 + if resultCount == 15 { + wasError = true + whichError = fmt.Errorf(tooManyQuantities) + } + }) + if wasError { + return whichError + } + + return nil +} + +func errorResponse(w http.ResponseWriter, errType string) { + fmt.Fprintf(w, errType) +} + +func diecutHandler(w http.ResponseWriter, r *http.Request) { + if params := strings.Split(r.URL.Path, "/"); len(params) == validNumberOfURLParams { + if size := strings.Split(params[sizeParamIndex], "x"); len(size) == validNumberOfSizeParams { + count := params[countParamIndex] + + width, wOk := strconv.ParseInt(size[0], 10, 16) + height, hOk := strconv.ParseInt(size[1], 10, 16) + countNumber, cOk := strconv.ParseInt(count, 10, 16) + + countNumber16 := int16(countNumber) + + foundValidQuantity := false + for _, value := range(listOfValidQuantities){ + if value == countNumber16 { + foundValidQuantity = true + break + } + } + + if wOk != nil || hOk != nil || cOk != nil || !foundValidQuantity { + errorResponse(w, invalidParamsType) + } else { + + fmt.Fprintf(w, "%dx%d | %d", width, height, countNumber) + + } + + } else { + errorResponse(w, invalidNumberOfSizeParamError) + } + } else { + errorResponse(w, invalidNumberOfParamsError) + } +} + +func defaultHandler(w http.ResponseWriter, r *http.Request) { + errorResponse(w, invalidURL) +} + +func initWebPage() error{ + p := phantomjs.DefaultProcess + + stickerMuleWebPage, err := p.CreateWebPage() + if err != nil { + return err + } + + // Open a URL. + if err := stickerMuleWebPage.Open(stickerMuleURL); err != nil { + return err + } + + content, err := stickerMuleWebPage.Content() + if err != nil { + return err + } + + stickerMuleWebPageDocument,err = goquery.NewDocumentFromReader(strings.NewReader(content)) + if err != nil { + return err + } + + return nil +} + +func closingCleanUp(){ + phantomjs.DefaultProcess.Close() + stickerMuleWebPage.Close() +} + +func main() { + //init phantomJS + if err:= phantomjs.DefaultProcess.Open(); err != nil { + fmt.Println("Error : ", err) + return + } + + defer closingCleanUp() + + //init web page and document + if err:= initWebPage() ; err != nil { + fmt.Println("Error : ", err) + return + } + + //init valid values for stickers quantity + if err:= getListOfQuantities() ; err != nil { + fmt.Println("Error : ", err) + return + } + + + http.HandleFunc("/prices/diecut/", diecutHandler) + http.HandleFunc("/", defaultHandler) + + fmt.Println("Server running on localhost:8080") + log.Fatal(http.ListenAndServe(":8080", nil)) + + +} From 8e34bbab637290fac20ed047b37ad6ce37fcacd9 Mon Sep 17 00:00:00 2001 From: GotPPay Date: Mon, 23 Apr 2018 22:28:30 +0200 Subject: [PATCH 3/4] fixed bug with valid quantity values array --- server.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index c9fbeaa..c63492d 100644 --- a/server.go +++ b/server.go @@ -36,7 +36,7 @@ var stickerMuleWebPageDocument *goquery.Document func getListOfQuantities() error { - listOfValidQuantities := make([]int16, maxExpectedNumberOfQuantities) + result := make([]int16, maxExpectedNumberOfQuantities) resultCount := 0 wasError := false whichError := error(nil) @@ -53,7 +53,7 @@ func getListOfQuantities() error { wasError = true whichError = err }else{ - listOfValidQuantities[resultCount] = int16(sizeInt64) + result[resultCount] = int16(sizeInt64) } resultCount+=1 if resultCount == 15 { @@ -65,6 +65,8 @@ func getListOfQuantities() error { return whichError } + listOfValidQuantities = result[:resultCount] + return nil } @@ -163,7 +165,6 @@ func main() { return } - http.HandleFunc("/prices/diecut/", diecutHandler) http.HandleFunc("/", defaultHandler) From 239ea7135fe4bdcb7d5b1f3092be6e7504e9d54a Mon Sep 17 00:00:00 2001 From: GotPPay Date: Tue, 24 Apr 2018 22:47:50 +0200 Subject: [PATCH 4/4] return price on api call, price hardcoded --- server.go | 153 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 29 deletions(-) diff --git a/server.go b/server.go index c63492d..ebfec86 100644 --- a/server.go +++ b/server.go @@ -2,14 +2,13 @@ package main import ( "fmt" + "github.com/PuerkitoBio/goquery" + "github.com/benbjohnson/phantomjs" "log" + "math" "net/http" "strconv" "strings" - //"gopkg.in/headzoo/surf.v1" - "github.com/PuerkitoBio/goquery" - //"sourcegraph.com/sourcegraph/go-selenium" - "github.com/benbjohnson/phantomjs" ) const validNumberOfURLParams = 5 @@ -18,6 +17,15 @@ const countParamIndex = 4 const validNumberOfSizeParams = 2 const maxExpectedNumberOfQuantities = 15 +const numberOfTabsToSizeGroup = 12 +const numberOfArrowDownToCustomSize = 4 + +const validMinWidth = 25.4 +const validMaxWidth = 914.4 +const validMinHeight = 25.4 +const validMaxHeigh = 609.59 + +const epsilon = 0.1 const stickerMuleURL = "https://www.stickermule.com/uk/products/die-cut-stickers" @@ -26,36 +34,39 @@ const ( invalidNumberOfParamsError = "Number of params not correct, expected /WIDTHxLENGTH/count" invalidNumberOfSizeParamError = "Size not correct, expected /WIDTHxLENGTH/count" invalidParamsType = "Params should be numbers" + invalidParamsSize = "Params should be in range" - tooManyQuantities = "Too many quantities. Didn't expect this" + errorSimulatingInput = "Error with simulating user input" + + tooManyQuantities = "Too many quantities. Didn't expect this" + + failedToGetPrice = "Failed to get price" ) var listOfValidQuantities []int16 var stickerMuleWebPage *phantomjs.WebPage var stickerMuleWebPageDocument *goquery.Document - func getListOfQuantities() error { result := make([]int16, maxExpectedNumberOfQuantities) resultCount := 0 wasError := false whichError := error(nil) - stickerMuleWebPageDocument.Find("label.quantity").Each(func(_ int, s *goquery.Selection){ - if wasError{ + stickerMuleWebPageDocument.Find("label.quantity").Each(func(_ int, s *goquery.Selection) { + if wasError { return } - numberWithoutComma := strings.Replace(s.Text(), ",","",-1) + numberWithoutComma := strings.Replace(s.Text(), ",", "", -1) trimmedNumber := strings.TrimSpace(numberWithoutComma) - - if sizeInt64, err := strconv.ParseInt(trimmedNumber,10,16) ; err != nil { + if sizeInt64, err := strconv.ParseInt(trimmedNumber, 10, 16); err != nil { wasError = true whichError = err - }else{ + } else { result[resultCount] = int16(sizeInt64) } - resultCount+=1 + resultCount += 1 if resultCount == 15 { wasError = true whichError = fmt.Errorf(tooManyQuantities) @@ -70,6 +81,80 @@ func getListOfQuantities() error { return nil } +func floatToString(number float64) string { + return strconv.FormatFloat(number, 'f', 1, 64) +} + +func getStickerPrice(width, height float64, quantity int) (int, error) { + + //simulate click on "Custom size radio button" + if _, err := stickerMuleWebPage.Evaluate("function(){document.getElementById('variant_77').click();}"); err != nil { + return 0, err + } + + //set value for width + if _, err := stickerMuleWebPage.Evaluate("function(){document.getElementById('width').value = " + floatToString(width) + ";}"); err != nil { + return 0, err + } + + //set value for height + if _, err := stickerMuleWebPage.Evaluate("function(){document.getElementById('height').value = " + floatToString(height) + ";}"); err != nil { + return 0, err + } + + //check if input boxes accepted passed values + valuesFromInputBoxes, err := stickerMuleWebPage.Evaluate("function(){return {width: document.getElementById('width').value, height: document.getElementById('height').value};}") + if err != nil { + return 0, err + } + + valuesMap, ok := valuesFromInputBoxes.(map[string]interface{}) + if !ok { + return 0, fmt.Errorf(invalidParamsType) + } + + redWidthString, ok := valuesMap["width"].(string) + if !ok { + return 0, fmt.Errorf(invalidParamsType) + } + + redHeightString, ok := valuesMap["height"].(string) + if !ok { + return 0, fmt.Errorf(invalidParamsType) + } + + widthValue, err := strconv.ParseFloat(redWidthString, 64) + if err != nil { + return 0, fmt.Errorf(invalidParamsType) + } + + heightValue, err := strconv.ParseFloat(redHeightString, 64) + if err != nil { + return 0, fmt.Errorf(invalidParamsType) + } + + if math.Abs(widthValue-width) > epsilon || math.Abs(heightValue-height) > epsilon { + return 0, fmt.Errorf(invalidParamsType) + } + + //Now read price for selected quantity + priceValue, err := stickerMuleWebPage.Evaluate("function(){return {price: document.getElementById('price_200_id').innerHTML};}") + //TODO : price is empty because setting value for width and height doesn't fire event listener + //on input fields and prices are not loaded. Find another way to simulate user input + + priceMap, ok := priceValue.(map[string]interface{}) + if !ok { + return 0, fmt.Errorf(failedToGetPrice) + } + + _, ok = priceMap["price"].(string) + if !ok { + return 0, fmt.Errorf(failedToGetPrice) + } + + return 100, nil +} + func errorResponse(w http.ResponseWriter, errType string) { fmt.Fprintf(w, errType) } @@ -79,26 +164,34 @@ func diecutHandler(w http.ResponseWriter, r *http.Request) { if size := strings.Split(params[sizeParamIndex], "x"); len(size) == validNumberOfSizeParams { count := params[countParamIndex] - width, wOk := strconv.ParseInt(size[0], 10, 16) - height, hOk := strconv.ParseInt(size[1], 10, 16) + width, wOk := strconv.ParseFloat(size[0], 64) + height, hOk := strconv.ParseFloat(size[1], 64) countNumber, cOk := strconv.ParseInt(count, 10, 16) countNumber16 := int16(countNumber) foundValidQuantity := false - for _, value := range(listOfValidQuantities){ + for _, value := range listOfValidQuantities { if value == countNumber16 { foundValidQuantity = true break - } + } } - if wOk != nil || hOk != nil || cOk != nil || !foundValidQuantity { + validWidth := width >= validMinWidth && width <= validMaxWidth + validHeight := height >= validMinHeight && height <= validMaxHeigh + + if wOk != nil || hOk != nil || cOk != nil || !foundValidQuantity || !validWidth || !validHeight { errorResponse(w, invalidParamsType) } else { - fmt.Fprintf(w, "%dx%d | %d", width, height, countNumber) - + price, err := getStickerPrice(width, height, int(countNumber)) + if err != nil { + errorResponse(w, failedToGetPrice) + } else { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, "{price:%d, currency: \"USD\"}", price) + } } } else { @@ -113,14 +206,17 @@ func defaultHandler(w http.ResponseWriter, r *http.Request) { errorResponse(w, invalidURL) } -func initWebPage() error{ +func initWebPage() error { p := phantomjs.DefaultProcess - stickerMuleWebPage, err := p.CreateWebPage() + localStickerMuleWebPage, err := p.CreateWebPage() if err != nil { return err } + //keep pointer to web page in global space, so page is not re-created every time API is called + stickerMuleWebPage = localStickerMuleWebPage + // Open a URL. if err := stickerMuleWebPage.Open(stickerMuleURL); err != nil { return err @@ -131,7 +227,7 @@ func initWebPage() error{ return err } - stickerMuleWebPageDocument,err = goquery.NewDocumentFromReader(strings.NewReader(content)) + stickerMuleWebPageDocument, err = goquery.NewDocumentFromReader(strings.NewReader(content)) if err != nil { return err } @@ -139,14 +235,14 @@ func initWebPage() error{ return nil } -func closingCleanUp(){ +func closingCleanUp() { phantomjs.DefaultProcess.Close() stickerMuleWebPage.Close() } func main() { //init phantomJS - if err:= phantomjs.DefaultProcess.Open(); err != nil { + if err := phantomjs.DefaultProcess.Open(); err != nil { fmt.Println("Error : ", err) return } @@ -154,13 +250,13 @@ func main() { defer closingCleanUp() //init web page and document - if err:= initWebPage() ; err != nil { + if err := initWebPage(); err != nil { fmt.Println("Error : ", err) return } //init valid values for stickers quantity - if err:= getListOfQuantities() ; err != nil { + if err := getListOfQuantities(); err != nil { fmt.Println("Error : ", err) return } @@ -170,6 +266,5 @@ func main() { fmt.Println("Server running on localhost:8080") log.Fatal(http.ListenAndServe(":8080", nil)) - - + }