From 55146a82f6dcf590fb40c56e33c1f49a4e1ce09c Mon Sep 17 00:00:00 2001 From: Dmitry Shihovtsev Date: Fri, 5 Nov 2021 11:30:50 +0600 Subject: [PATCH] Autodetect client location if we didn't passed it --- cmd/main.go | 16 ++++++------ config/options.go | 13 ++++++++++ location/location.go | 59 ++++++++++++++++++++++++++++++++++++++++++++ proposal/api.go | 22 +++++++++++++++-- 4 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 location/location.go diff --git a/cmd/main.go b/cmd/main.go index 8aa1627..42dd001 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -13,11 +13,17 @@ import ( "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" + "github.com/rs/zerolog/log" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + _ "go.uber.org/automaxprocs" + "github.com/mysteriumnetwork/discovery/config" "github.com/mysteriumnetwork/discovery/db" _ "github.com/mysteriumnetwork/discovery/docs" "github.com/mysteriumnetwork/discovery/health" "github.com/mysteriumnetwork/discovery/listener" + "github.com/mysteriumnetwork/discovery/location" "github.com/mysteriumnetwork/discovery/price" "github.com/mysteriumnetwork/discovery/price/pricing" "github.com/mysteriumnetwork/discovery/proposal" @@ -25,12 +31,6 @@ import ( "github.com/mysteriumnetwork/discovery/quality/oracleapi" "github.com/mysteriumnetwork/discovery/tags" mlog "github.com/mysteriumnetwork/logger" - "github.com/rs/zerolog/log" - swaggerFiles "github.com/swaggo/files" - ginSwagger "github.com/swaggo/gin-swagger" - - // unconfuse the number of cores go can use in k8s - _ "go.uber.org/automaxprocs" ) var Version = "" @@ -83,8 +83,10 @@ func main() { go proposalService.StartExpirationJob() defer proposalService.Shutdown() + locationProvider := location.NewLocationProvider(cfg.LocationAddress.String(), cfg.LocationUser, cfg.LocationPass) + v3 := r.Group("/api/v3") - proposal.NewAPI(proposalService, proposalRepo).RegisterRoutes(v3) + proposal.NewAPI(proposalService, proposalRepo, locationProvider).RegisterRoutes(v3) health.NewAPI(rdb, database).RegisterRoutes(v3) cfger := pricing.NewConfigProviderDB(rdb) diff --git a/config/options.go b/config/options.go index 24080a6..8c09ed0 100644 --- a/config/options.go +++ b/config/options.go @@ -20,6 +20,9 @@ type Options struct { RedisPass string RedisDB int BadgerAddress url.URL + LocationAddress url.URL + LocationUser string + LocationPass string } func Read() (*Options, error) { @@ -48,6 +51,13 @@ func Read() (*Options, error) { return nil, err } + locationUser := OptionalEnv("LOCATION_USER", "") + locationPass := OptionalEnv("LOCATION_PASS", "") + locationAddress, err := RequiredEnvURL("LOCATION_ADDRESS") + if err != nil { + return nil, err + } + redisPass := OptionalEnv("REDIS_PASS", "") redisDBint := 0 @@ -68,6 +78,9 @@ func Read() (*Options, error) { RedisPass: redisPass, RedisDB: redisDBint, BadgerAddress: *badgerAddress, + LocationAddress: *locationAddress, + LocationUser: locationUser, + LocationPass: locationPass, }, nil } diff --git a/location/location.go b/location/location.go new file mode 100644 index 0000000..e82a289 --- /dev/null +++ b/location/location.go @@ -0,0 +1,59 @@ +// Copyright (c) 2021 BlockDev AG +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +package location + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" +) + +type locationProvider struct { + address string + user string + pass string +} + +// NewLocationProvider create a new location provider instance. +func NewLocationProvider(address, user, pass string) *locationProvider { + return &locationProvider{ + address: address, + user: user, + pass: pass, + } +} + +// Country returns a country code for the provided IP-address. +func (lp *locationProvider) Country(ip string) (countryCode string, err error) { + url := fmt.Sprintf("%s/%s", lp.address, ip) + + req, err := http.NewRequest("GET", url, nil) + req.SetBasicAuth(lp.user, lp.pass) + + ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second) + defer cancel() + + req = req.WithContext(ctx) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + + defer resp.Body.Close() + + var country struct { + Country string `json:"country"` + } + + err = json.NewDecoder(resp.Body).Decode(&country) + if err != nil { + return "", err + } + + return country.Country, nil +} diff --git a/proposal/api.go b/proposal/api.go index 32e8b77..790886e 100644 --- a/proposal/api.go +++ b/proposal/api.go @@ -17,10 +17,19 @@ import ( type API struct { service *Service repository *Repository + location locationProvider } -func NewAPI(service *Service, repository *Repository) *API { - return &API{service: service, repository: repository} +type locationProvider interface { + Country(ip string) (countryCode string, err error) +} + +func NewAPI(service *Service, repository *Repository, location locationProvider) *API { + return &API{ + service: service, + repository: repository, + location: location, + } } // Proposals list proposals. @@ -52,6 +61,15 @@ func (a *API) Proposals(c *gin.Context) { tags: c.Query("tags"), } + if len(opts.from) != 2 { + country, err := a.location.Country(c.ClientIP()) + if err != nil { + log.Warn().Err(err).Msg("Failed to autodetect client country") + } else { + opts.from = country + } + } + pids, _ := c.GetQueryArray("provider_id") opts.providerIDS = pids