package carpool import ( "encoding/json" "errors" "fmt" "io" "net/http" "strconv" "time" "git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss" "github.com/google/uuid" "github.com/rs/zerolog/log" ) type BBCDailyCarpoolAPI struct { OperatorId string OperatorName string APIKey string BaseURL string } func NewBBCDailyCarpoolAPI(operatorId string, operatorName string, api_key string, baseURL string) (*BBCDailyCarpoolAPI, error) { return &BBCDailyCarpoolAPI{ OperatorId: operatorId, OperatorName: operatorName, APIKey: api_key, BaseURL: baseURL, }, nil } type BBCDailyResult struct { ID *string `json:"id"` Duration *time.Duration `json:"duration"` Distance *int64 `json:"distance"` PickupLatitude *float64 `json:"pickup_latitude"` PickupLongitude *float64 `json:"pickup_longitude"` PickupDatetime *string `json:"pickup_datetime"` DropoffLatitude *float64 `json:"dropoff_latitude"` DropoffLongitude *float64 `json:"dropoff_longitude"` DropoffDatetime *string `json:"dropoff_datetime"` WebURL *string `json:"web_url"` DepartureToPickupWalkingTime *time.Duration `json:"departure_to_pickup_walking_time"` DropoffToArrivalWalkingTime *time.Duration `json:"dropoff_to_arrival_walking_time"` JourneyPolyline *string `json:"journey_polyline"` Price *struct { Currency string `json:"currency"` Amount float64 `json:"amount"` } `json:"price"` AvailableSeats *int64 `json:"available_seats"` } func (api *BBCDailyCarpoolAPI) GetOperatorId() string { return api.OperatorId } func (api *BBCDailyCarpoolAPI) GetDriverJourneys( departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureDate time.Time, timeDelta *time.Duration, departureRadius int64, arrivalRadius int64, count int64, ) ([]ocss.DriverJourney, error) { td := 1 * time.Hour if timeDelta != nil { td = *timeDelta } results, err := blablacarDailySearch( api.BaseURL+"/search", api.APIKey, departureLat, departureLng, arrivalLat, arrivalLng, departureDate, td, ) if err != nil { log.Error().Err(err).Msg("error in blablacarDailySearch") return nil, err } journeys := []ocss.DriverJourney{} for _, r := range results { var price *ocss.Price if r.Price != nil { paying := ocss.Paying price = &ocss.Price{ Type: &paying, Amount: &r.Price.Amount, Currency: &r.Price.Currency, } } var pickupDatetime ocss.OCSSTime // Try to parse as Unix timestamp first i, err := strconv.ParseInt(*r.PickupDatetime, 10, 64) if err != nil { // If not a Unix timestamp, try parsing as ISO 8601 datetime pd, err2 := time.Parse(time.RFC3339, *r.PickupDatetime) if err2 != nil { log.Error().Err(err2).Str("datetime", *r.PickupDatetime).Msg("error parsing pickup datetime") pickupDatetime = ocss.OCSSTime(departureDate) } else { pickupDatetime = ocss.OCSSTime(pd) } } else { pd := time.Unix(i, 0) pickupDatetime = ocss.OCSSTime(pd) } driverJourney := ocss.DriverJourney{ DriverTrip: ocss.DriverTrip{ Driver: ocss.User{ ID: uuid.NewString(), Operator: api.OperatorName, Alias: "Nom anonymisé", }, DepartureToPickupWalkingDuration: r.DepartureToPickupWalkingTime, DropoffToArrivalWalkingDuration: r.DropoffToArrivalWalkingTime, Trip: ocss.Trip{ Operator: api.OperatorName, PassengerPickupLat: nilCheck(r.PickupLatitude), PassengerPickupLng: nilCheck(r.PickupLongitude), PassengerDropLat: nilCheck(r.DropoffLatitude), PassengerDropLng: nilCheck(r.DropoffLongitude), Duration: nilCheck(r.Duration), Distance: r.Distance, JourneyPolyline: r.JourneyPolyline, }, }, JourneySchedule: ocss.JourneySchedule{ ID: r.ID, PassengerPickupDate: pickupDatetime, WebUrl: r.WebURL, }, AvailableSteats: r.AvailableSeats, Price: price, } journeys = append(journeys, driverJourney) } return journeys, nil } func (api *BBCDailyCarpoolAPI) GetPassengerJourneys( departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureDate time.Time, timeDelta *time.Duration, departureRadius int64, arrivalRadius int64, count int64, ) ([]ocss.PassengerJourney, error) { return nil, errors.New("not implemented") } func blablacarDailySearch(url string, access_token string, departure_latitude float64, departure_longitude float64, arrival_latitude float64, arrival_longitude float64, departure_time time.Time, departure_timedelta time.Duration) ([]BBCDailyResult, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { log.Error().Err(err).Msg("new request issue") return nil, err } req.Header.Set("Content-Type", "application/json") q := req.URL.Query() q.Add("access_token", access_token) q.Add("departure_latitude", fmt.Sprintf("%f", departure_latitude)) q.Add("departure_longitude", fmt.Sprintf("%f", departure_longitude)) q.Add("arrival_latitude", fmt.Sprintf("%f", arrival_latitude)) q.Add("arrival_longitude", fmt.Sprintf("%f", arrival_longitude)) q.Add("departure_epoch", strconv.FormatInt(departure_time.Unix(), 10)) q.Add("departure_timedelta", fmt.Sprintf("%v", departure_timedelta.Abs().Seconds())) req.URL.RawQuery = q.Encode() resp, err := http.DefaultClient.Do(req) if err != nil { log.Error().Err(err).Msg("error in BBCDaily request") return nil, err } defer resp.Body.Close() log.Debug(). Int("status_code", resp.StatusCode). Str("status", resp.Status). Msg("BlaBlaCarDaily API response received") body, err := io.ReadAll(resp.Body) if err != nil { log.Error().Err(err).Msg("error reading BBCDaily response body") return nil, err } log.Debug(). Str("response_body", string(body)). Msg("BlaBlaCarDaily API raw response") response := []BBCDailyResult{} err = json.Unmarshal(body, &response) if err != nil { log.Error(). Err(err). Str("resp_body", string(body)). Any("status", resp.Status). Msg("cannot parse json response from BlaBlaCarDaily API") return nil, err } log.Debug(). Int("results_count", len(response)). Msg("BlaBlaCarDaily API response parsed successfully") return response, nil } func nilCheck[T any](a *T) T { var t T if a == nil { return t } return *a }