diff --git a/config/config.go b/config/config.go index 2da32e2..83774b2 100644 --- a/config/config.go +++ b/config/config.go @@ -23,9 +23,10 @@ func Load() error { Service: Service{ // 9000 DEFAULT FOR DEV ENVIRONMENT - Port: getEnv("NOVATECH_SERVICE_PORT", "9000"), - Environment: getEnv("NOVATECH_SERVICE_ENVIRONMENT", "DEV"), - BlockchainSecret: getEnv("NOVATECH_SERVICE_BLOCKCHAIN_SECRET", "novatech_service_blockchain_secret"), + Port: getEnv("NOVATECH_SERVICE_PORT", "9000"), + Environment: getEnv("NOVATECH_SERVICE_ENVIRONMENT", "DEV"), + BlockchainSecret: getEnv("NOVATECH_SERVICE_BLOCKCHAIN_SECRET", "novatech_service_blockchain_secret"), + MapboxAccessToken: getEnv("NOVATECH_SERVICE_MAPBOX_ACCESS_TOKEN", ""), }, AdminService: Service{ // 8080 DEFAULT FOR DEV ENVIRONMENT diff --git a/config/models.go b/config/models.go index bb22212..5c7148e 100644 --- a/config/models.go +++ b/config/models.go @@ -10,10 +10,11 @@ type Config struct { // Service contains configuration for service type Service struct { - Port string - Environment string - WebPageURL string - BlockchainSecret string + Port string + Environment string + WebPageURL string + BlockchainSecret string + MapboxAccessToken string } // Blockchain contains configuration for blockchain diff --git a/controllers/contracts_controller.go b/controllers/contracts_controller.go index d32bafa..dc075c9 100644 --- a/controllers/contracts_controller.go +++ b/controllers/contracts_controller.go @@ -150,9 +150,6 @@ func GetContractStatuses(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"data": models.GetContractStatuses()}) } - - - func CreateContract(c *gin.Context) { var payload models.CreateContractRequestPayload diff --git a/controllers/locations_controller.go b/controllers/locations_controller.go new file mode 100644 index 0000000..7f7039b --- /dev/null +++ b/controllers/locations_controller.go @@ -0,0 +1,32 @@ +package controllers + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "gitlab.com/pactual1/backend/config" + "gitlab.com/pactual1/backend/services/location" +) + +func SearchPlace(c *gin.Context) { + query := c.Query("q") + + if query == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "query is empty"}) + return + } + + mapboxAccessToken := config.AppConfig.Service.MapboxAccessToken + locationClient := location.NewService(mapboxAccessToken) + + places, err := locationClient.SearchPlace(c, query) + if err != nil { + log.Printf("SearchPlace Error: Service error: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Service error"}) + return + } + + // Respond with the buyers and the total count + c.JSON(http.StatusOK, gin.H{"data": places}) +} diff --git a/env-example b/env-example index cfd595b..2724428 100644 --- a/env-example +++ b/env-example @@ -11,4 +11,5 @@ NOVATECH_SERVICE_BLOCKCHAIN_SECRET="abc&1*~#^2^#s0^=)^^7%b34" NOVATECH_BLOCKCHAIN_NETWORK_ENDPOINT="https://polygon-mumbai.infura.io/v3/4458cf4d1689497b9a38b1d6bbf05e78" NOVATECH_BLOCKCHAIN_CONTRACT_ADDRESS=0x121Cb4bFEeDb55d598D8F5e9EeDF8bB14c421d96 NOVATECH_BLOCKCHAIN_WALLET_ADDRESS=0x20eff5decaed29bd64f0c6385956363eeaaf4d3e -NOVATECH_BLOCKCHAIN_WALLET_PRIVATE_KEY=PRIVATE_KEY \ No newline at end of file +NOVATECH_BLOCKCHAIN_WALLET_PRIVATE_KEY=PRIVATE_KEY +NOVATECH_SERVICE_MAPBOX_ACCESS_TOKEN=pk.ey \ No newline at end of file diff --git a/go.mod b/go.mod index a463a15..2d4aef7 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,11 @@ require ( github.com/jinzhu/gorm v1.9.16 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.8.0 + github.com/mailru/easyjson v0.7.7 + github.com/pkg/errors v0.9.1 github.com/qor/admin v1.2.0 github.com/stretchr/testify v1.8.4 + github.com/valyala/fasthttp v1.40.0 ) replace gitlab.com/pactual1/backend => ./ @@ -19,6 +22,7 @@ require ( github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/bits-and-blooms/bitset v1.8.0 // indirect @@ -62,6 +66,7 @@ require ( github.com/holiman/uint256 v1.2.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.1 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect @@ -98,6 +103,7 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/arch v0.5.0 // indirect golang.org/x/crypto v0.13.0 // indirect diff --git a/go.sum b/go.sum index a713412..bd0af66 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,9 @@ github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBK github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= @@ -168,8 +171,11 @@ github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -191,6 +197,8 @@ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNa github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -326,6 +334,11 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc= +github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yosssi/gohtml v0.0.0-20200519115854-476f5b4b8047 h1:YWaOkupKL+BRRJSWRq/uhSkWXc1K0QVIYVG36XUBGOc= @@ -338,6 +351,7 @@ golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= @@ -351,6 +365,8 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= @@ -361,6 +377,11 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -369,11 +390,16 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/models/contract.go b/models/contract.go index c59e0fc..45d39de 100644 --- a/models/contract.go +++ b/models/contract.go @@ -9,60 +9,60 @@ import ( type Contract struct { gorm.Model - Name string `json:"name"` - DeviceIDs pq.Int64Array `json:"deviceIds" gorm:"type:integer[]"` - BuyerID uint `json:"buyerId"` - SellerID uint `json:"sellerId"` - Description string `json:"description"` - StartLat float64 `json:"startLat"` - StartLon float64 `json:"startLon"` - EndLat float64 `json:"endLat"` - EndLon float64 `json:"endLon"` - StartTime time.Time `json:"startTime"` - EndTime time.Time `json:"endTime"` - Status string `json:"status"` - BlockchainID string `json:"blockchainId"` - ContractInfos []ContractInfo `json:"contractInfos"` - ProductID uint `json:"productId"` - MaxTemp float64 `json:"maxTemp"` - MinTemp float64 `json:"minTemp"` - ArrivalDate time.Time `json:"arrivalDate"` - PenaltyType string `json:"penaltyType"` - PenaltyValue int `json:"penaltyValue"` - PenaltyRec string `json:"penaltyRec"` - BuyerName string `json:"buyerName" gorm:"-"` - NumberOfDevices int `json:"numberOfDevices" gorm:"-"` + Name string `json:"name"` + DeviceIDs pq.Int64Array `json:"deviceIds" gorm:"type:integer[]"` + BuyerID uint `json:"buyerId"` + SellerID uint `json:"sellerId"` + Description string `json:"description"` + StartPlaceName string `json:"startPlaceName"` + StartLat float64 `json:"startLat"` + StartLon float64 `json:"startLon"` + EndPlaceName string `json:"endPlaceName"` + EndLat float64 `json:"endLat"` + EndLon float64 `json:"endLon"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Status string `json:"status"` + BlockchainID string `json:"blockchainId"` + ContractInfos []ContractInfo `json:"contractInfos"` + ProductID uint `json:"productId"` + MaxTemp float64 `json:"maxTemp"` + MinTemp float64 `json:"minTemp"` + ArrivalDate time.Time `json:"arrivalDate"` + PenaltyType string `json:"penaltyType"` + PenaltyValue int `json:"penaltyValue"` + PenaltyRec string `json:"penaltyRec"` + BuyerName string `json:"buyerName" gorm:"-"` + NumberOfDevices int `json:"numberOfDevices" gorm:"-"` } type ContractResponse struct { BaseModel - Name string `json:"name"` - DeviceIDs pq.Int64Array `json:"deviceIds" gorm:"type:integer[]"` - BuyerID uint `json:"buyerId"` - SellerID uint `json:"sellerId"` - Description string `json:"description"` - StartLat float64 `json:"startLat"` - StartLon float64 `json:"startLon"` - EndLat float64 `json:"endLat"` - EndLon float64 `json:"endLon"` - StartTime time.Time `json:"startTime"` - EndTime time.Time `json:"endTime"` - Status string `json:"status"` - BlockchainID string `json:"blockchainId"` - ContractInfos []ContractInfo `json:"contractInfos"` - ProductID uint `json:"productId"` - MaxTemp float64 `json:"maxTemp"` - MinTemp float64 `json:"minTemp"` - ArrivalDate time.Time `json:"arrivalDate"` - PenaltyType string `json:"penaltyType"` - PenaltyValue int `json:"penaltyValue"` - PenaltyRec string `json:"penaltyRec"` - BuyerName string `json:"buyerName" gorm:"-"` - NumberOfDevices int `json:"numberOfDevices" gorm:"-"` + Name string `json:"name"` + DeviceIDs pq.Int64Array `json:"deviceIds" gorm:"type:integer[]"` + BuyerID uint `json:"buyerId"` + SellerID uint `json:"sellerId"` + Description string `json:"description"` + StartLat float64 `json:"startLat"` + StartLon float64 `json:"startLon"` + EndLat float64 `json:"endLat"` + EndLon float64 `json:"endLon"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Status string `json:"status"` + BlockchainID string `json:"blockchainId"` + ContractInfos []ContractInfo `json:"contractInfos"` + ProductID uint `json:"productId"` + MaxTemp float64 `json:"maxTemp"` + MinTemp float64 `json:"minTemp"` + ArrivalDate time.Time `json:"arrivalDate"` + PenaltyType string `json:"penaltyType"` + PenaltyValue int `json:"penaltyValue"` + PenaltyRec string `json:"penaltyRec"` + BuyerName string `json:"buyerName" gorm:"-"` + NumberOfDevices int `json:"numberOfDevices" gorm:"-"` } - - func ConvertContractToResponse(contracts []Contract) []ContractResponse { var contractResponses []ContractResponse for _, contract := range contracts { @@ -119,17 +119,18 @@ func ConvertContractToResponseModel(contracts []Contract) []ListContractResponse } listInvoiceResponse := ListContractResponse{ - Status: KeyValue{Key: status.Key, Value: status.Value}, - Buyer: CompanyShortResponse{ID: int(contract.BuyerID), Name: contract.BuyerName}, - ContractID: int(contract.ID), - DateCreated: contract.CreatedAt, - NumberOfDevices: contract.NumberOfDevices, + Status: KeyValue{Key: status.Key, Value: status.Value}, + Buyer: CompanyShortResponse{ID: int(contract.BuyerID), Name: contract.BuyerName}, + ContractID: int(contract.ID), + DateCreated: contract.CreatedAt, + NumberOfDevices: contract.NumberOfDevices, } listInvoiceResponses = append(listInvoiceResponses, listInvoiceResponse) } return listInvoiceResponses } + const ContractStatusActive = "active" const ContractStatusPending = "pending" const ContractStatusDraft = "draft" @@ -138,14 +139,12 @@ const ContractStatusReadyForActivation = "ready_for_activation" const ContractStatusExecuted = "executed" const ContractStatusRevoked = "revoked" - const PenaltyTypeAmount = "amount" const PenaltyTypePercentage = "percentage" const PenaltyRecDaily = "daily" const PenaltyRecMonthly = "monthly" - type Status struct { Key string `json:"key"` Value string `json:"value"` @@ -164,25 +163,24 @@ func GetContractStatuses() []Status { } type ListContractResponse struct { - Status KeyValue `json:"status"` - Buyer CompanyShortResponse `json:"buyer"` - ContractID int `json:"contractID"` - NumberOfDevices int `json:"numberOfDevices"` - DateCreated time.Time `json:"dateCreated"` - + Status KeyValue `json:"status"` + Buyer CompanyShortResponse `json:"buyer"` + ContractID int `json:"contractID"` + NumberOfDevices int `json:"numberOfDevices"` + DateCreated time.Time `json:"dateCreated"` } type CreateContractRequestPayload struct { - SellerID uint `json:"sellerId" binding:"required"` - BuyerID uint `json:"buyerId" binding:"required"` - Description string `json:"description"` - ProductID uint `json:"productId"` - MinTemp float64 `json:"minTemp" binding:"required"` - MaxTemp float64 `json:"maxTemp" binding:"required"` - ArrivalDate int64 `json:"arrivalDate"` - PenaltyType string `json:"penaltyType"` - PenaltyValue int `json:"penaltyValue"` - PenaltyRec string `json:"penaltyRec"` + SellerID uint `json:"sellerId" binding:"required"` + BuyerID uint `json:"buyerId" binding:"required"` + Description string `json:"description"` + ProductID uint `json:"productId"` + MinTemp float64 `json:"minTemp" binding:"required"` + MaxTemp float64 `json:"maxTemp" binding:"required"` + ArrivalDate int64 `json:"arrivalDate"` + PenaltyType string `json:"penaltyType"` + PenaltyValue int `json:"penaltyValue"` + PenaltyRec string `json:"penaltyRec"` } func (Contract) Update() (bool, error) { @@ -194,5 +192,3 @@ func (Contract) Create() (bool, error) { func (Contract) Delete() (bool, error) { return false, nil } - - diff --git a/models/location.go b/models/location.go new file mode 100644 index 0000000..02eae0b --- /dev/null +++ b/models/location.go @@ -0,0 +1,6 @@ +package models + +type Place struct { + Text string `json:"text"` + Coordinates []float64 `json:"coordinates"` +} diff --git a/routes/public_routes.go b/routes/public_routes.go index 70a526b..d26201e 100644 --- a/routes/public_routes.go +++ b/routes/public_routes.go @@ -28,7 +28,10 @@ func RegisterPublicRoutes(r *gin.Engine) { r.GET("/contracts/statuses", controllers.GetContractStatuses) r.GET("/contracts", controllers.GetBuyerContracts) r.POST("/contracts/create", controllers.CreateContract) - + r.GET("/contracts/:contract_id", controllers.GetContractByID) r.PATCH("/contracts/:contract_id", controllers.UpdateContract) + + // Locations + r.GET("/locations", controllers.SearchPlace) } diff --git a/services/location/location_service.go b/services/location/location_service.go new file mode 100644 index 0000000..a582ba7 --- /dev/null +++ b/services/location/location_service.go @@ -0,0 +1,33 @@ +package location + +import ( + "context" + + "gitlab.com/pactual1/backend/models" + "gitlab.com/pactual1/backend/services/location/mapbox_lib" +) + +type service struct { + accessToken string +} + +type Service interface { + SearchPlace(context.Context, string) ([]*models.Place, error) +} + +func NewService(accessToken string) Service { + return service{accessToken: accessToken} +} + +func (s service) SearchPlace(ctx context.Context, query string) ([]*models.Place, error) { + geocoder := mapbox.NewFastHttpGeocoder(mapbox.AccessToken(s.accessToken)) + resp, err := geocoder.ForwardGeocode(ctx, &mapbox.ForwardGeocodeRequest{SearchText: query}) + if err != nil { + return nil, err + } + var places []*models.Place + for _, feature := range resp.Features { + places = append(places, &models.Place{Text: feature.Text, Coordinates: feature.Geometry.Coordinates}) + } + return places, nil +} diff --git a/services/location/mapbox_lib/client.go b/services/location/mapbox_lib/client.go new file mode 100644 index 0000000..7deeb96 --- /dev/null +++ b/services/location/mapbox_lib/client.go @@ -0,0 +1,7 @@ +package mapbox + +// Client covers all Mabpox API +type Client interface { + // Geocoder covers forward and reverse geocoding mapbox API + Geocoder +} \ No newline at end of file diff --git a/services/location/mapbox_lib/config.go b/services/location/mapbox_lib/config.go new file mode 100644 index 0000000..82c7c13 --- /dev/null +++ b/services/location/mapbox_lib/config.go @@ -0,0 +1,103 @@ +package mapbox + +import ( + "context" + "os" + + "github.com/valyala/fasthttp" +) + +const ( + defaultAPI = "https://api.mapbox.com" +) + +// Option allows gradually modify config +type Option func(c config) config + +type config struct { + accessToken string + rootAPI string + client FastHttpClient + logger Logger + // requestLogger will be called instead of testLogger if set. + requestLogger func(ctx context.Context) Logger + + accessTokenGetValue []byte + geocodeEndpoint string +} + +// withEnv overwrites config values with env is not empty +func (c config) withEnv() config { + at := os.Getenv("MAPBOX_ACCESS_TOKEN") + if at != "" { + c.accessToken = at + } + + return c +} + +// prepare prebuilds some reused api parts like access token http get value +func (c config) prepare() config { + c.accessTokenGetValue = []byte(questionMark + access_token + string(equalMark) + c.accessToken) + + return c +} + +func newConfig() config { + return config{ + rootAPI: defaultAPI, + client: &fasthttp.Client{}, + geocodeEndpoint: "mapbox.places", + } +} + +// Log used to debug traces and to log errors. +func Log(l Logger) Option { + return func(c config) config { + c.logger = l + return c + } +} + +// RequestLogger sets the way testLogger could be extracted from request context. +// If set will be used instead of Log. +func RequestLogger(extract func(ctx context.Context) Logger) Option { + return func(c config) config { + c.requestLogger = extract + return c + } +} +// AccessToken sets access_token get param. +// Could be set with MAPBOX_ACCESS_TOKEN too. +func AccessToken(at string) Option { + return func(c config) config { + c.accessToken = at + return c + } +} + +// RootAPI allows to change root api address. +// default to https://api.mapbox.com +func RootAPI(rootAPI string) Option { + return func(c config) config { + c.rootAPI = rootAPI + return c + } +} + +// HttpClient allows to change default fast http client +func HttpClient(c FastHttpClient) Option { + return func(fhc config) config { + fhc.client = c + return fhc + } +} + +// GeocodeEndpoint sets geocode endpoint. +// could be set to mapbox.places-permanent, defualt to mapbox.places +func GeocodeEndpoint(endpoint string) Option { + return func(c config) config { + c.geocodeEndpoint = endpoint + return c + } +} diff --git a/services/location/mapbox_lib/entities.go b/services/location/mapbox_lib/entities.go new file mode 100644 index 0000000..910a72f --- /dev/null +++ b/services/location/mapbox_lib/entities.go @@ -0,0 +1,35 @@ +package mapbox + +type ( + Feature struct { + ID string `json:"id"` + Type string `json:"type"` + PlaceType []string `json:"place_type"` + Relevance float64 `json:"relevance"` + Properties Properties `json:"properties"` + Text string `json:"text"` + PlaceName string `json:"place_name"` + Center []float64 `json:"center"` + Geometry Geometry `json:"geometry"` + Address string `json:"address"` + Context []Context `json:"context"` + BoundingBox []float64 `json:"bbox"` + } + + Properties struct { + Accuracy string `json:"accuracy"` + ShortCode string `json:"short_code"` + } + + Geometry struct { + Type string `json:"type"` + Coordinates []float64 `json:"coordinates"` + } + + Context struct { + ID string `json:"id"` + Text string `json:"text"` + Wikidata string `json:"wikidata"` + ShortCode string `json:"short_code"` + } +) diff --git a/services/location/mapbox_lib/entities_easyjson.go b/services/location/mapbox_lib/entities_easyjson.go new file mode 100644 index 0000000..22a69f5 --- /dev/null +++ b/services/location/mapbox_lib/entities_easyjson.go @@ -0,0 +1,555 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package mapbox + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox(in *jlexer.Lexer, out *Properties) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "accuracy": + out.Accuracy = string(in.String()) + case "short_code": + out.ShortCode = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox(out *jwriter.Writer, in Properties) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"accuracy\":" + out.RawString(prefix[1:]) + out.String(string(in.Accuracy)) + } + { + const prefix string = ",\"short_code\":" + out.RawString(prefix) + out.String(string(in.ShortCode)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v Properties) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Properties) MarshalEasyJSON(w *jwriter.Writer) { + easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Properties) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Properties) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox(l, v) +} +func easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox1(in *jlexer.Lexer, out *Geometry) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "type": + out.Type = string(in.String()) + case "coordinates": + if in.IsNull() { + in.Skip() + out.Coordinates = nil + } else { + in.Delim('[') + if out.Coordinates == nil { + if !in.IsDelim(']') { + out.Coordinates = make([]float64, 0, 8) + } else { + out.Coordinates = []float64{} + } + } else { + out.Coordinates = (out.Coordinates)[:0] + } + for !in.IsDelim(']') { + var v1 float64 + v1 = float64(in.Float64()) + out.Coordinates = append(out.Coordinates, v1) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox1(out *jwriter.Writer, in Geometry) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"type\":" + out.RawString(prefix[1:]) + out.String(string(in.Type)) + } + { + const prefix string = ",\"coordinates\":" + out.RawString(prefix) + if in.Coordinates == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v2, v3 := range in.Coordinates { + if v2 > 0 { + out.RawByte(',') + } + out.Float64(float64(v3)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v Geometry) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Geometry) MarshalEasyJSON(w *jwriter.Writer) { + easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Geometry) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Geometry) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox1(l, v) +} +func easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox2(in *jlexer.Lexer, out *Feature) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = string(in.String()) + case "type": + out.Type = string(in.String()) + case "place_type": + if in.IsNull() { + in.Skip() + out.PlaceType = nil + } else { + in.Delim('[') + if out.PlaceType == nil { + if !in.IsDelim(']') { + out.PlaceType = make([]string, 0, 4) + } else { + out.PlaceType = []string{} + } + } else { + out.PlaceType = (out.PlaceType)[:0] + } + for !in.IsDelim(']') { + var v4 string + v4 = string(in.String()) + out.PlaceType = append(out.PlaceType, v4) + in.WantComma() + } + in.Delim(']') + } + case "relevance": + out.Relevance = float64(in.Float64()) + case "properties": + (out.Properties).UnmarshalEasyJSON(in) + case "text": + out.Text = string(in.String()) + case "place_name": + out.PlaceName = string(in.String()) + case "center": + if in.IsNull() { + in.Skip() + out.Center = nil + } else { + in.Delim('[') + if out.Center == nil { + if !in.IsDelim(']') { + out.Center = make([]float64, 0, 8) + } else { + out.Center = []float64{} + } + } else { + out.Center = (out.Center)[:0] + } + for !in.IsDelim(']') { + var v5 float64 + v5 = float64(in.Float64()) + out.Center = append(out.Center, v5) + in.WantComma() + } + in.Delim(']') + } + case "geometry": + (out.Geometry).UnmarshalEasyJSON(in) + case "address": + out.Address = string(in.String()) + case "context": + if in.IsNull() { + in.Skip() + out.Context = nil + } else { + in.Delim('[') + if out.Context == nil { + if !in.IsDelim(']') { + out.Context = make([]Context, 0, 1) + } else { + out.Context = []Context{} + } + } else { + out.Context = (out.Context)[:0] + } + for !in.IsDelim(']') { + var v6 Context + (v6).UnmarshalEasyJSON(in) + out.Context = append(out.Context, v6) + in.WantComma() + } + in.Delim(']') + } + case "bbox": + if in.IsNull() { + in.Skip() + out.BoundingBox = nil + } else { + in.Delim('[') + if out.BoundingBox == nil { + if !in.IsDelim(']') { + out.BoundingBox = make([]float64, 0, 8) + } else { + out.BoundingBox = []float64{} + } + } else { + out.BoundingBox = (out.BoundingBox)[:0] + } + for !in.IsDelim(']') { + var v7 float64 + v7 = float64(in.Float64()) + out.BoundingBox = append(out.BoundingBox, v7) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox2(out *jwriter.Writer, in Feature) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.String(string(in.ID)) + } + { + const prefix string = ",\"type\":" + out.RawString(prefix) + out.String(string(in.Type)) + } + { + const prefix string = ",\"place_type\":" + out.RawString(prefix) + if in.PlaceType == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v8, v9 := range in.PlaceType { + if v8 > 0 { + out.RawByte(',') + } + out.String(string(v9)) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"relevance\":" + out.RawString(prefix) + out.Float64(float64(in.Relevance)) + } + { + const prefix string = ",\"properties\":" + out.RawString(prefix) + (in.Properties).MarshalEasyJSON(out) + } + { + const prefix string = ",\"text\":" + out.RawString(prefix) + out.String(string(in.Text)) + } + { + const prefix string = ",\"place_name\":" + out.RawString(prefix) + out.String(string(in.PlaceName)) + } + { + const prefix string = ",\"center\":" + out.RawString(prefix) + if in.Center == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v10, v11 := range in.Center { + if v10 > 0 { + out.RawByte(',') + } + out.Float64(float64(v11)) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"geometry\":" + out.RawString(prefix) + (in.Geometry).MarshalEasyJSON(out) + } + { + const prefix string = ",\"address\":" + out.RawString(prefix) + out.String(string(in.Address)) + } + { + const prefix string = ",\"context\":" + out.RawString(prefix) + if in.Context == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v12, v13 := range in.Context { + if v12 > 0 { + out.RawByte(',') + } + (v13).MarshalEasyJSON(out) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"bbox\":" + out.RawString(prefix) + if in.BoundingBox == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v14, v15 := range in.BoundingBox { + if v14 > 0 { + out.RawByte(',') + } + out.Float64(float64(v15)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v Feature) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox2(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Feature) MarshalEasyJSON(w *jwriter.Writer) { + easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox2(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Feature) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox2(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Feature) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox2(l, v) +} +func easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox3(in *jlexer.Lexer, out *Context) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "id": + out.ID = string(in.String()) + case "text": + out.Text = string(in.String()) + case "wikidata": + out.Wikidata = string(in.String()) + case "short_code": + out.ShortCode = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox3(out *jwriter.Writer, in Context) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"id\":" + out.RawString(prefix[1:]) + out.String(string(in.ID)) + } + { + const prefix string = ",\"text\":" + out.RawString(prefix) + out.String(string(in.Text)) + } + { + const prefix string = ",\"wikidata\":" + out.RawString(prefix) + out.String(string(in.Wikidata)) + } + { + const prefix string = ",\"short_code\":" + out.RawString(prefix) + out.String(string(in.ShortCode)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v Context) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox3(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v Context) MarshalEasyJSON(w *jwriter.Writer) { + easyjson3e8ab7adEncodeGithubComHumansNetMapboxSdkGoMapbox3(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *Context) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox3(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *Context) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson3e8ab7adDecodeGithubComHumansNetMapboxSdkGoMapbox3(l, v) +} diff --git a/services/location/mapbox_lib/geocode.go b/services/location/mapbox_lib/geocode.go new file mode 100644 index 0000000..e8e53c5 --- /dev/null +++ b/services/location/mapbox_lib/geocode.go @@ -0,0 +1,421 @@ +package mapbox + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/pkg/errors" + + "github.com/valyala/fasthttp" +) + +const ( + limit = "limit" + types = "types" + country = "country" + language = "language" + reverseMode = "reverseMode" + autocomplete = "autocomplete" + fuzzymatch = "fuzzymatch" + bbox = "bbox" + proximity = "proximity" + routing = "routing" + trueStr = "true" + oneStr = "1" + + access_token = "access_token" + + floatFormatNoExponent = 'f' + + respHeaderRateLimitInterval = "X-Rate-Limit-Interval" + respHeaderRateLimitLimit = "X-Rate-Limit-Limit" + respHeaderRateLimitReset = "X-Rate-Limit-Reset" +) + +var ( + responseFormatJSON = []byte(".json") + getMethod = []byte("GET") +) + +type GeoPoint struct { + Lon float64 + Lat float64 +} + +type ReverseGeocodeRequest struct { + GeoPoint GeoPoint + // Limit results to one or more countries. + Limit int + // Filter results to include only a subset (one or more) of the available feature types. + // Options are country, region, postcode, district, place, locality, neighborhood, address, and poi. + // Multiple options can be comma-separated. Note that poi.landmark is a deprecated type that, while still supported, + // returns the same data as is returned using the poi type. + Types []string + // Permitted values are ISO 3166 alpha 2(https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes separated by commas. + Country string + // Specify the user’s language. This parameter controls the language of the text supplied in responses. + // Options are IETF language tags comprised of a mandatory ISO 639-1 language code and, optionally, + // one or more IETF subtags for country or script. + // More than one value can also be specified, separated by commas, + // for applications that need to display labels in multiple languages. + // For more information on which specific languages are supported, see https://docs.mapbox.com/api/search/#language-coverage + Language string + // Decides how results are sorted in a reverse geocoding query + // if multiple results are requested using a limit other than 1. + // Options are distance (default), which causes the closest feature + // to always be returned first, and score, which allows high-prominence features + // to be sorted higher than nearer, lower-prominence features. + ReverseMode int + // Specify whether to request additional metadata about the recommended navigation destination corresponding + // to the feature (true) or not (false, default). Only applicable for address features. + // For example, if routing=true the response could include data about a point on the road the feature fronts. + // Response features may include an array containing one or more routable points. + // Routable points cannot always be determined. + // Consuming applications should fall back to using the feature’s normal geometry for routing + // if a separate routable point is not returned. + Routing bool +} + +// RateLimit wraps mapbox API rate limit resp headers +type RateLimit struct { + Interval []byte + Limit []byte + Reset []byte +} + +// easyjson:json +type rawReverseGeoResp struct { + Features []Feature `json:"features"` + Query []float64 `json:"query"` +} + +// easyjson:json +type rawForwardGeoResp struct { + Features []Feature `json:"features"` + Query []string `json:"query"` +} + +// GeocodeResponse +type GeocodeResponse struct { + RateLimit RateLimit + // Raw mapbox API response + RawResp []byte + // passed query to mapbox + ReverseQuery GeoPoint + ForwardQuery []string + // response result type + Type string + // response data + Features []Feature +} + +type ForwardGeocodeRequest struct { + //The feature you’re trying to look up. + //This could be an address, a point of interest name, a city name, etc. + //When searching for points of interest, it can also be a category name (for example, “coffee shop”). + //For information on categories, see the Point of interest category coverage section. + //The search text should be expressed as a URL-encoded UTF-8 string, + //and must not contain the semicolon character (either raw or URL-encoded). + //Your search text, once decoded, must consist of at most 20 words and numbers separated by spacing and punctuation, + //and at most 256 characters. + // + //The accuracy of coordinates returned by a forward geocoding request can be impacted + //by how the addresses in the query are formatted. Learn more about address formatting + //best practices in the https://docs.mapbox.com/help/troubleshooting/address-geocoding-format-guide. + SearchText string + + //Specify whether to return autocomplete results (true, default) or not (false). + //When autocomplete is enabled, results will be included that start with the requested string, + //rather than just responses that match it exactly. + //For example, a query for India might return both India and Indiana with autocomplete enabled, + //but only India if it’s disabled. + // + //When autocomplete is enabled, each user keystroke counts as one request to the Geocoding API. + //For example, a search for "coff" would be reflected as four separate Geocoding API requests. + //To reduce the total requests sent, you can configure your application + //to only call the Geocoding API after a specific number of characters are typed. + Autocomplete *bool // default true + + //Limit results to only those contained within the supplied bounding box + //Bounding boxes should be supplied as four numbers separated by commas, + //in minLon,minLat,maxLon,maxLat order. + //The bounding box cannot cross the 180th meridian. + Bbox []float64 + + //Limit results to one or more countries. + //Permitted values are ISO 3166 alpha 2 country codes separated by commas. + Country string + + //Specify whether the Geocoding API should attempt approximate, + //as well as exact, matching when performing searches (true, default), + //or whether it should opt out of this behavior and only attempt exact matching (false). + //For example, the default setting might return Washington, DC for a query of wahsington, + //even though the query was misspelled. + FuzzyMatch *bool // default true + + //Specify the user’s language. + //This parameter controls the language of the text supplied in responses, and also affects result scoring, + //with results matching the user’s query in the requested language being preferred over results + //that match in another language. For example, an autocomplete query for things + //that start with Frank might return Frankfurt as the first result with an English (en) language parameter, + //but Frankreich (“France”) with a German (de) language parameter. + // + //Options are IETF language tags comprised of a mandatory ISO 639-1 language code and, optionally, + //one or more IETF subtags for country or script. + // + //More than one value can also be specified, separated by commas, + //for applications that need to display labels in multiple languages. + // + //For more information on which specific languages are supported, see the https://docs.mapbox.com/api/search/#language-coverage. + Language string + + //Specify the maximum number of results to return. The default is 5 and the maximum supported is 10. + Limit int // default 5 + + //Bias the response to favor results that are closer to this location + Proximity *GeoPoint + + //Specify whether to request additional metadata about the recommended navigation destination + //corresponding to the feature (true) or not (false, default). Only applicable for address features. + // + //For example, if routing=true the response could include data about a point on the road the feature fronts. + //Response features may include an array containing one or more routable points. + //Routable points cannot always be determined. + //Consuming applications should fall back to using the feature’s normal geometry for routing + //if a separate routable point is not returned. + Routing bool //default false + + //Filter results to include only a subset (one or more) of the available feature types. + //Options are country, region, postcode, district, place, locality, neighborhood, address, and poi. + //Multiple options can be comma-separated. Note that poi.landmark is a deprecated type that, + //while still supported, returns the same data as is returned using the poi type. + // + //For more information on the available types, see the https://docs.mapbox.com/api/search/#data-types. + Types []string +} + +// Geocoder encapsulates forward and reverse geocode calls. +type Geocoder interface { + // ReverseGeocode calls geocode/v5 reverse mapbox API + ReverseGeocode(ctx context.Context, req *ReverseGeocodeRequest) (*GeocodeResponse, error) + // ReverseGeocode calls geocode/v5 reverse mapbox API + ForwardGeocode(ctx context.Context, req *ForwardGeocodeRequest) (*GeocodeResponse, error) +} + +// FastHttpGeocoder is a fasthttp Geocoder implementation +type FastHttpGeocoder struct { + config + + geocodeAPIURL []byte + + stringBufPull *stringsBufferPool +} + +// ReverseGeocode calls geocode/v5 reverse mapbox API thought fasthttp client. +func (c *FastHttpGeocoder) ReverseGeocode(ctx context.Context, req *ReverseGeocodeRequest) (*GeocodeResponse, error) { + freq := fasthttp.AcquireRequest() + defer fasthttp.ReleaseRequest(freq) + + fresp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseResponse(fresp) + + // split multivalues to limit memory consumption + values := make(map[string]string, 5) + + if req.Country != "" { + values[country] = req.Country + } + if req.Limit != 0 { + values[limit] = strconv.Itoa(req.Limit) + } + if req.Language != "" { + values[language] = req.Language + } + if req.Routing { + values[routing] = trueStr + } + if req.ReverseMode == 1 { + values[reverseMode] = oneStr + } + if len(req.Types) > 0 { + values[types] = strings.Join(req.Types, ",") + } + + buf := c.stringBufPull.acquireStringsBuilder() + defer c.stringBufPull.releaseStringsBuilder(buf) + + buf.Write(c.geocodeAPIURL) + buf.WriteString(strconv.FormatFloat(req.GeoPoint.Lon, floatFormatNoExponent, 6, 64)) + buf.WriteByte(comma) + buf.WriteString(strconv.FormatFloat(req.GeoPoint.Lat, floatFormatNoExponent, 6, 64)) + buf.Write(responseFormatJSON) + buf.Write(c.accessTokenGetValue) + + encodeValues(buf, values) + + reqURI := buf.Bytes() + + c.withLogger(ctx, func(logger Logger) { + logger.Debugf("mapbox_sdk: reverse geocode request %s", buf.String()) + }) + + freq.Header.SetMethodBytes(getMethod) + freq.SetRequestURIBytes(reqURI) + + if err := c.client.Do(freq, fresp); err != nil { + return nil, err + } + + respBytes := make([]byte, len(fresp.Body())) + copy(respBytes, fresp.Body()) + + c.withLogger(ctx, func(logger Logger) { + logger.Debugf("mapbox_sdk: reverse geocode response %s", string(respBytes)) + }) + + if fresp.Header.StatusCode() != http.StatusOK { + return nil, errors.Errorf("failed to reverse geocode URI %s statusCode %d resp %s", + reqURI, fresp.Header.StatusCode(), string(respBytes)) + } + + respRaw := rawReverseGeoResp{} + if err := respRaw.UnmarshalJSON(respBytes); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshall raw reverse geocode resp %s", string(respBytes)) + } + + if len(respRaw.Query) != 2 { + return nil, errors.Errorf("unexpected len of query coordinates in resp %s", string(respBytes)) + } + + return &GeocodeResponse{ + RateLimit: readRespRateLimit(fresp), + RawResp: respBytes, + ReverseQuery: GeoPoint{ + Lon: respRaw.Query[0], + Lat: respRaw.Query[1], + }, + Features: respRaw.Features, + }, nil +} + +// ReverseGeocode calls geocode/v5 reverse mapbox API thought fasthttp client. +func (c *FastHttpGeocoder) ForwardGeocode(ctx context.Context, req *ForwardGeocodeRequest) (*GeocodeResponse, error) { + freq := fasthttp.AcquireRequest() + defer fasthttp.ReleaseRequest(freq) + + fresp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseResponse(fresp) + + // split multivalues to limit memory consumption + values := make(map[string]string, 9) + + if req.Country != "" { + values[country] = req.Country + } + if req.Limit != 0 { + values[limit] = strconv.Itoa(req.Limit) + } + if req.Language != "" { + values[language] = req.Language + } + if req.Routing { + values[routing] = trueStr + } + if req.Autocomplete != nil { + values[autocomplete] = fmt.Sprint(*req.Autocomplete) + } else { + values[autocomplete] = trueStr + } + if req.FuzzyMatch != nil { + values[fuzzymatch] = fmt.Sprint(*req.FuzzyMatch) + } else { + values[fuzzymatch] = trueStr + } + if len(req.Bbox) == 4 { + values[bbox] = fmt.Sprintf("%f,%f,%f,%f", req.Bbox[0], req.Bbox[1], req.Bbox[2], req.Bbox[3]) + } + if req.Proximity != nil { + values[proximity] = fmt.Sprintf("%f,%f", req.Proximity.Lon, req.Proximity.Lat) + } + values[routing] = fmt.Sprint(req.Routing) + if len(req.Types) > 0 { + values[types] = strings.Join(req.Types, ",") + } + + buf := c.stringBufPull.acquireStringsBuilder() + defer c.stringBufPull.releaseStringsBuilder(buf) + + buf.Write(c.geocodeAPIURL) + buf.WriteString(req.SearchText) + buf.Write(responseFormatJSON) + buf.Write(c.accessTokenGetValue) + + encodeValues(buf, values) + + reqURI := buf.Bytes() + + c.withLogger(ctx, func(logger Logger) { + logger.Debugf("mapbox_sdk: forward geocode request %s", buf.String()) + }) + + freq.Header.SetMethodBytes(getMethod) + freq.SetRequestURIBytes(reqURI) + + if err := c.client.Do(freq, fresp); err != nil { + return nil, err + } + + respBytes := make([]byte, len(fresp.Body())) + copy(respBytes, fresp.Body()) + + c.withLogger(ctx, func(logger Logger) { + logger.Debugf("mapbox_sdk: forward geocode response %s", string(respBytes)) + }) + + if fresp.Header.StatusCode() != http.StatusOK { + return nil, errors.Errorf("failed to reverse geocode URI %s statusCode %d resp %s", + reqURI, fresp.Header.StatusCode(), string(respBytes)) + } + + respRaw := rawForwardGeoResp{} + if err := respRaw.UnmarshalJSON(respBytes); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshall raw reverse geocode resp %s", string(respBytes)) + } + + return &GeocodeResponse{ + RateLimit: readRespRateLimit(fresp), + RawResp: respBytes, + Features: respRaw.Features, + ForwardQuery: respRaw.Query, + }, nil +} + +func NewFastHttpGeocoder(opts ...Option) *FastHttpGeocoder { + c := FastHttpGeocoder{ + config: newConfig(), + stringBufPull: newStringsBufferPool(), + geocodeAPIURL: []byte("/geocoding/v5/"), + } + + for _, o := range opts { + c.config = o(c.config) + } + + c.config = c.config.withEnv() + c.config = c.config.prepare() + + c.geocodeAPIURL = []byte(c.rootAPI + string(c.geocodeAPIURL) + c.geocodeEndpoint + slash) + + return &c +} + +func readRespRateLimit(resp *fasthttp.Response) RateLimit { + return RateLimit{ + Interval: resp.Header.Peek(respHeaderRateLimitInterval), + Limit: resp.Header.Peek(respHeaderRateLimitLimit), + Reset: resp.Header.Peek(respHeaderRateLimitReset), + } +} diff --git a/services/location/mapbox_lib/geocode_easyjson.go b/services/location/mapbox_lib/geocode_easyjson.go new file mode 100644 index 0000000..7a4343e --- /dev/null +++ b/services/location/mapbox_lib/geocode_easyjson.go @@ -0,0 +1,293 @@ +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. + +package mapbox + +import ( + json "encoding/json" + easyjson "github.com/mailru/easyjson" + jlexer "github.com/mailru/easyjson/jlexer" + jwriter "github.com/mailru/easyjson/jwriter" +) + +// suppress unused package warning +var ( + _ *json.RawMessage + _ *jlexer.Lexer + _ *jwriter.Writer + _ easyjson.Marshaler +) + +func easyjson46e924aeDecodeGithubComHumansNetMapboxSdkGoMapbox(in *jlexer.Lexer, out *rawReverseGeoResp) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "features": + if in.IsNull() { + in.Skip() + out.Features = nil + } else { + in.Delim('[') + if out.Features == nil { + if !in.IsDelim(']') { + out.Features = make([]Feature, 0, 1) + } else { + out.Features = []Feature{} + } + } else { + out.Features = (out.Features)[:0] + } + for !in.IsDelim(']') { + var v1 Feature + (v1).UnmarshalEasyJSON(in) + out.Features = append(out.Features, v1) + in.WantComma() + } + in.Delim(']') + } + case "query": + if in.IsNull() { + in.Skip() + out.Query = nil + } else { + in.Delim('[') + if out.Query == nil { + if !in.IsDelim(']') { + out.Query = make([]float64, 0, 8) + } else { + out.Query = []float64{} + } + } else { + out.Query = (out.Query)[:0] + } + for !in.IsDelim(']') { + var v2 float64 + v2 = float64(in.Float64()) + out.Query = append(out.Query, v2) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson46e924aeEncodeGithubComHumansNetMapboxSdkGoMapbox(out *jwriter.Writer, in rawReverseGeoResp) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"features\":" + out.RawString(prefix[1:]) + if in.Features == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v3, v4 := range in.Features { + if v3 > 0 { + out.RawByte(',') + } + (v4).MarshalEasyJSON(out) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"query\":" + out.RawString(prefix) + if in.Query == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v5, v6 := range in.Query { + if v5 > 0 { + out.RawByte(',') + } + out.Float64(float64(v6)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v rawReverseGeoResp) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson46e924aeEncodeGithubComHumansNetMapboxSdkGoMapbox(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v rawReverseGeoResp) MarshalEasyJSON(w *jwriter.Writer) { + easyjson46e924aeEncodeGithubComHumansNetMapboxSdkGoMapbox(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *rawReverseGeoResp) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson46e924aeDecodeGithubComHumansNetMapboxSdkGoMapbox(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *rawReverseGeoResp) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson46e924aeDecodeGithubComHumansNetMapboxSdkGoMapbox(l, v) +} +func easyjson46e924aeDecodeGithubComHumansNetMapboxSdkGoMapbox1(in *jlexer.Lexer, out *rawForwardGeoResp) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeString() + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "features": + if in.IsNull() { + in.Skip() + out.Features = nil + } else { + in.Delim('[') + if out.Features == nil { + if !in.IsDelim(']') { + out.Features = make([]Feature, 0, 1) + } else { + out.Features = []Feature{} + } + } else { + out.Features = (out.Features)[:0] + } + for !in.IsDelim(']') { + var v7 Feature + (v7).UnmarshalEasyJSON(in) + out.Features = append(out.Features, v7) + in.WantComma() + } + in.Delim(']') + } + case "query": + if in.IsNull() { + in.Skip() + out.Query = nil + } else { + in.Delim('[') + if out.Query == nil { + if !in.IsDelim(']') { + out.Query = make([]string, 0, 4) + } else { + out.Query = []string{} + } + } else { + out.Query = (out.Query)[:0] + } + for !in.IsDelim(']') { + var v8 string + v8 = string(in.String()) + out.Query = append(out.Query, v8) + in.WantComma() + } + in.Delim(']') + } + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson46e924aeEncodeGithubComHumansNetMapboxSdkGoMapbox1(out *jwriter.Writer, in rawForwardGeoResp) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"features\":" + out.RawString(prefix[1:]) + if in.Features == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v9, v10 := range in.Features { + if v9 > 0 { + out.RawByte(',') + } + (v10).MarshalEasyJSON(out) + } + out.RawByte(']') + } + } + { + const prefix string = ",\"query\":" + out.RawString(prefix) + if in.Query == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { + out.RawString("null") + } else { + out.RawByte('[') + for v11, v12 := range in.Query { + if v11 > 0 { + out.RawByte(',') + } + out.String(string(v12)) + } + out.RawByte(']') + } + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v rawForwardGeoResp) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson46e924aeEncodeGithubComHumansNetMapboxSdkGoMapbox1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v rawForwardGeoResp) MarshalEasyJSON(w *jwriter.Writer) { + easyjson46e924aeEncodeGithubComHumansNetMapboxSdkGoMapbox1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *rawForwardGeoResp) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson46e924aeDecodeGithubComHumansNetMapboxSdkGoMapbox1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *rawForwardGeoResp) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson46e924aeDecodeGithubComHumansNetMapboxSdkGoMapbox1(l, v) +} diff --git a/services/location/mapbox_lib/http.go b/services/location/mapbox_lib/http.go new file mode 100644 index 0000000..5d5df5e --- /dev/null +++ b/services/location/mapbox_lib/http.go @@ -0,0 +1,9 @@ +package mapbox + +import ( + "github.com/valyala/fasthttp" +) + +type FastHttpClient interface { + Do(req *fasthttp.Request, resp *fasthttp.Response) error +} diff --git a/services/location/mapbox_lib/logger.go b/services/location/mapbox_lib/logger.go new file mode 100644 index 0000000..609ba55 --- /dev/null +++ b/services/location/mapbox_lib/logger.go @@ -0,0 +1,22 @@ +package mapbox + +import ( + "context" +) + +type Logger interface { + Debugf(msg string, params ...interface{}) + Errorf(msg string, params ...interface{}) +} + +// withLogger helps to reduce unnecessary allocations +func (c *config) withLogger(ctx context.Context, do func(Logger)) { + if c.requestLogger != nil { + do(c.requestLogger(ctx)) + return + } + + if c.logger != nil { + do(c.logger) + } +} \ No newline at end of file diff --git a/services/location/mapbox_lib/pools.go b/services/location/mapbox_lib/pools.go new file mode 100644 index 0000000..7ea2377 --- /dev/null +++ b/services/location/mapbox_lib/pools.go @@ -0,0 +1,31 @@ +package mapbox + +import ( + "bytes" + "sync" +) + +type noCopy struct{} + +func (*noCopy) Lock() {} +func (*noCopy) Unlock() {} + +type stringsBufferPool struct { + noCopy noCopy + p sync.Pool +} + +func newStringsBufferPool() *stringsBufferPool { + return &stringsBufferPool{p: sync.Pool{New: func() interface{} { + return &bytes.Buffer{} + }}} +} + +func (pool *stringsBufferPool) acquireStringsBuilder() *bytes.Buffer { + return pool.p.Get().(*bytes.Buffer) +} + +func (pool *stringsBufferPool) releaseStringsBuilder(b *bytes.Buffer) { + b.Reset() + pool.p.Put(b) +} diff --git a/services/location/mapbox_lib/values.go b/services/location/mapbox_lib/values.go new file mode 100644 index 0000000..b37944b --- /dev/null +++ b/services/location/mapbox_lib/values.go @@ -0,0 +1,23 @@ +package mapbox + +import ( + "bytes" +) + +const ( + slash = "/" + comma = ',' + questionMark = "?" + equalMark = '=' + ampersandMark = '&' +) + +// encodeValues do almost the same as url.Values.Encode() but faster and reuses *strings.Builder +func encodeValues(buf *bytes.Buffer, values map[string]string) { + for k, v := range values { + buf.WriteByte(ampersandMark) + buf.WriteString(k) + buf.WriteByte(equalMark) + buf.WriteString(v) + } +}