Merge pull request #1 from senaduka/prices-getter

Prices getter
This commit is contained in:
Senad Uka
2018-04-25 11:43:29 +02:00
committed by GitHub
3 changed files with 278 additions and 1 deletions

5
.gitignore vendored
View File

@@ -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/

View File

@@ -1,2 +1,6 @@
# bilal-vjezba-golang
bilal vjezba golang
Requirements :
phantomjs (apt-get install phantomjs)

270
server.go Normal file
View File

@@ -0,0 +1,270 @@
package main
import (
"fmt"
"github.com/PuerkitoBio/goquery"
"github.com/benbjohnson/phantomjs"
"log"
"math"
"net/http"
"strconv"
"strings"
)
const validNumberOfURLParams = 5
const sizeParamIndex = 3
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"
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"
invalidParamsSize = "Params should be in range"
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 {
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 {
result[resultCount] = int16(sizeInt64)
}
resultCount += 1
if resultCount == 15 {
wasError = true
whichError = fmt.Errorf(tooManyQuantities)
}
})
if wasError {
return whichError
}
listOfValidQuantities = result[:resultCount]
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)
}
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.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 {
if value == countNumber16 {
foundValidQuantity = true
break
}
}
validWidth := width >= validMinWidth && width <= validMaxWidth
validHeight := height >= validMinHeight && height <= validMaxHeigh
if wOk != nil || hOk != nil || cOk != nil || !foundValidQuantity || !validWidth || !validHeight {
errorResponse(w, invalidParamsType)
} else {
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 {
errorResponse(w, invalidNumberOfSizeParamError)
}
} else {
errorResponse(w, invalidNumberOfParamsError)
}
}
func defaultHandler(w http.ResponseWriter, r *http.Request) {
errorResponse(w, invalidURL)
}
func initWebPage() error {
p := phantomjs.DefaultProcess
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
}
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))
}