package transitous import ( "context" "encoding/json" "fmt" "net/http" "net/url" "time" "github.com/rs/zerolog/log" ) // Client represents a Transitous API client type Client struct { baseURL string httpClient *http.Client } // NewClient creates a new Transitous client func NewClient(baseURL string) *Client { return &Client{ baseURL: baseURL, httpClient: &http.Client{ Timeout: 30 * time.Second, }, } } // PlanParams represents the parameters for route planning type PlanParams struct { FromPlace string ToPlace string Time *time.Time } // PlanResponse represents the response from the Transitous API type PlanResponse struct { Itineraries []Itinerary `json:"itineraries"` } // PlanWithResponse plans a route and returns the response func (c *Client) PlanWithResponse(ctx context.Context, params *PlanParams) (*TransitousResponse, error) { // Build URL with query parameters u, err := url.Parse(fmt.Sprintf("%s/api/v4/plan", c.baseURL)) if err != nil { return nil, fmt.Errorf("failed to parse base URL: %w", err) } query := u.Query() query.Set("fromPlace", params.FromPlace) query.Set("toPlace", params.ToPlace) if params.Time != nil { // Use ISO 8601 format with timezone like in the example query.Set("time", params.Time.Format(time.RFC3339)) } // Additional parameters matching the example query.Set("withFares", "true") query.Set("fastestDirectFactor", "1.5") query.Set("joinInterlinedLegs", "false") query.Set("maxMatchingDistance", "250") // query.Set("arriveBy", "true") u.RawQuery = query.Encode() // Create HTTP request req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", "COOPGO-Platform/1.0") log.Debug(). Str("url", u.String()). Msg("Making Transitous API request") // Execute request resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API request failed with status %d", resp.StatusCode) } // Parse response var transitousResponse TransitousResponse if err := json.NewDecoder(resp.Body).Decode(&transitousResponse); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } log.Debug(). Str("from", params.FromPlace). Str("to", params.ToPlace). Int("itineraries", len(transitousResponse.Itineraries)). Msg("Transitous API response received") return &transitousResponse, nil }