package tncservice import ( "bytes" "context" "encoding/base64" "errors" "fmt" "io/ioutil" "net/http" "bitbucket.org/nemt/nemt-portal-api/application/entitymapping" "bitbucket.org/nemt/nemt-portal-api/application/notificationservice" "bitbucket.org/nemt/nemt-portal-api/application/viewmodel" "bitbucket.org/nemt/nemt-portal-api/domain/service" "bitbucket.org/nemt/nemt-portal-api/infra/config" "github.com/lyft/lyft-go-sdk/lyft" "github.com/pquerna/ffjson/ffjson" "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" ) const ( lyftURL = "https://api.lyft.com/v1/" ) // lyftService holds methods to lyft application service type lyftService struct { svc *service.Service mapEntity *entitymapping.Mapper client *lyft.APIClient httpClient *http.Client cfg *config.Config notification *notificationservice.Service lyftConfig clientcredentials.Config refreshToken string } // newLyftProdService returns a lyftService instance with production info func newLyftProdService(svc *service.Service, mapper *entitymapping.Mapper, cfg *config.Config, notificationService *notificationservice.Service) *lyftService { config := clientcredentials.Config{ ClientID: cfg.LyftProd.Lyft.Client, ClientSecret: cfg.LyftProd.Lyft.Secret, TokenURL: "https://api.lyft.com/oauth/token", Scopes: []string{"public", "privileged.rides.dispatch", "rides.read", "rides.request", "profile", "offline"}, } httpClient := config.Client(context.Background()) return &lyftService{ svc: svc, mapEntity: mapper, client: lyft.NewAPIClient(httpClient, "nemt"), httpClient: httpClient, cfg: cfg, notification: notificationService, lyftConfig: config, refreshToken: cfg.LyftProd.Lyft.RefreshToken, } } // newLyftService returns a lyftService instance func newLyftService(svc *service.Service, mapper *entitymapping.Mapper, cfg *config.Config, notificationService *notificationservice.Service) *lyftService { config := clientcredentials.Config{ ClientID: cfg.Lyft.Client, ClientSecret: cfg.Lyft.Secret, TokenURL: "https://api.lyft.com/oauth/token", Scopes: []string{"public", "privileged.rides.dispatch", "rides.read", "rides.request", "profile", "offline"}, } httpClient := config.Client(context.Background()) return &lyftService{ svc: svc, mapEntity: mapper, client: lyft.NewAPIClient(httpClient, "nemt"), httpClient: httpClient, cfg: cfg, notification: notificationService, lyftConfig: config, refreshToken: cfg.Lyft.RefreshToken, } } func (s *lyftService) getToken() string { token, err := s.lyftConfig.Token(context.Background()) if err != nil { fmt.Println("Error to get token: ", err) return "" } return "Bearer " + token.AccessToken } func (s *lyftService) setTokenHeader(req *http.Request) string { type Token struct { GrantType string `json:"grant_type"` RefreshToken string `json:"refresh_token"` } token := Token{ GrantType: "refresh_token", RefreshToken: s.refreshToken, } bObject, err := ffjson.Marshal(token) if err != nil { return "" } tokenReq, err := http.NewRequest("POST", s.lyftConfig.TokenURL, bytes.NewBuffer(bObject)) if err != nil { return "" } tokenReq.Header.Set("Content-Type", "application/json") tokenReq.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", s.lyftConfig.ClientID, s.lyftConfig.ClientSecret)))) tokenReq.Header.Add("cache-control", "no-cache") resp, err := http.DefaultClient.Do(tokenReq) if err != nil { fmt.Println("Error to refresh token: ", err.Error()) return "" } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) var returnToken oauth2.Token err = ffjson.Unmarshal(body, &returnToken) if err != nil { fmt.Println("Error to parse token: ", err.Error()) return "" } returnToken.SetAuthHeader(req) return "Bearer " + returnToken.AccessToken } func (s *lyftService) GetRideDetails(rideRequest viewmodel.RideRequest) (viewmodel.RideRequest, error) { url := lyftURL + "dispatches/" + rideRequest.RideID req, err := http.NewRequest("GET", url, nil) if err != nil { return viewmodel.RideRequest{}, err } s.setTokenHeader(req) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return viewmodel.RideRequest{}, err } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) var raw viewmodel.RideRequest err = ffjson.Unmarshal(body, &raw) if err != nil { return viewmodel.RideRequest{}, err } return raw, nil } func (s *lyftService) GetRideStatus(rideRequest viewmodel.RideRequest, param string) (viewmodel.RideRequest, error) { params := make(map[string]string) params["status"] = param bObject, err := ffjson.Marshal(params) if err != nil { return viewmodel.RideRequest{}, err } fmt.Println("STATUS CHANGE REQUEST ON LYFT") fmt.Println("Ride ID: ", rideRequest.RideID) fmt.Println("Status: ", param) url := lyftURL + "sandbox/dispatches/" + rideRequest.RideID req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(bObject)) if err != nil { return viewmodel.RideRequest{}, err } req.Header.Add("Content-Type", "application/json") resp, err := s.httpClient.Do(req) if err != nil { return viewmodel.RideRequest{}, err } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) fmt.Println("STATUS CHANGE RESPONSE ON LYFT") fmt.Println(string(body)) var raw viewmodel.RideRequest err = ffjson.Unmarshal(body, &raw) if err != nil { return viewmodel.RideRequest{}, err } rideRequest.Status = raw.Status return s.GetRideDetails(rideRequest) } func (s *lyftService) CancelRide(rideRequest viewmodel.RideRequest) error { finalURL := lyftURL + "dispatches/" + rideRequest.RideID + "/cancel" req, err := http.NewRequest("POST", finalURL, bytes.NewBuffer([]byte{})) if err != nil { return err } s.setTokenHeader(req) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) if resp.StatusCode == 204 || resp.StatusCode == 404 { if err != nil { fmt.Println("Error: ", err) } return nil } else { fmt.Println("Response Body: ", string(body)) raw := map[string]interface{}{} err = ffjson.Unmarshal(body, &raw) if err != nil { return err } else { return errors.New(raw["error_description"].(string)) } } } func (s *lyftService) RequestRide(rideRequest viewmodel.RideRequest) (viewmodel.RideRequest, error) { bObject, err := ffjson.Marshal(rideRequest) if err != nil { return viewmodel.RideRequest{}, err } req, err := http.NewRequest("POST", lyftURL+"dispatches", bytes.NewBuffer(bObject)) if err != nil { return viewmodel.RideRequest{}, err } s.setTokenHeader(req) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return viewmodel.RideRequest{}, err } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) var raw viewmodel.RideRequest err = ffjson.Unmarshal(body, &raw) if err != nil { return viewmodel.RideRequest{}, err } if raw.Error != "" { fmt.Println("Error to call Lyft: ", raw.Error+" - "+raw.ErrorDescription) return viewmodel.RideRequest{}, errors.New("Lyft Error: " + raw.Error + " - " + raw.ErrorDescription) } return s.GetRideDetails(raw) } func (s *lyftService) GetETA(lag float64, log float64, params map[string]interface{}) (interface{}, error) { resp, _, err := s.client.PublicApi.GetETA(lag, log, params) if err != nil { return nil, err } return resp, nil } func (s *lyftService) GetCost(lag float64, log float64, params map[string]interface{}) (interface{}, error) { resp, _, err := s.client.PublicApi.GetCost(lag, log, params) if err != nil { return nil, err } return resp, nil } func (s *lyftService) GetDrivers(lag float64, log float64) (interface{}, error) { resp, _, err := s.client.PublicApi.GetDrivers(lag, log) if err != nil { return nil, err } return resp, nil } func (s *lyftService) GetTypes(lag float64, log float64, params map[string]interface{}) (interface{}, error) { resp, _, err := s.client.PublicApi.GetRideTypes(lag, log, params) if err != nil { return nil, err } return resp, nil }