initial commit
This commit is contained in:
125
internal/infrastructure/weather/api_client.go
Normal file
125
internal/infrastructure/weather/api_client.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package weather
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/paramah/ai_devs4/s01e03/internal/domain"
|
||||
)
|
||||
|
||||
// APIClient implements domain.WeatherClient using wttr.in
|
||||
type APIClient struct {
|
||||
baseURL string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewAPIClient creates a new weather API client
|
||||
// apiKey parameter is ignored as wttr.in doesn't require authentication
|
||||
func NewAPIClient(baseURL, apiKey string) *APIClient {
|
||||
if baseURL == "" {
|
||||
baseURL = "https://wttr.in"
|
||||
}
|
||||
return &APIClient{
|
||||
baseURL: baseURL,
|
||||
client: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
type wttrResponse struct {
|
||||
CurrentCondition []struct {
|
||||
TempC string `json:"temp_C"`
|
||||
Humidity string `json:"humidity"`
|
||||
WeatherDesc []struct {
|
||||
Value string `json:"value"`
|
||||
} `json:"weatherDesc"`
|
||||
} `json:"current_condition"`
|
||||
NearestArea []struct {
|
||||
AreaName []struct {
|
||||
Value string `json:"value"`
|
||||
} `json:"areaName"`
|
||||
} `json:"nearest_area"`
|
||||
}
|
||||
|
||||
// GetWeather gets weather information for a location using wttr.in
|
||||
func (c *APIClient) GetWeather(ctx context.Context, location string) (*domain.WeatherInfo, error) {
|
||||
// Build URL - wttr.in format: https://wttr.in/Location?format=j1
|
||||
u, err := url.Parse(c.baseURL + "/" + url.PathEscape(location))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing URL: %w", err)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
q.Set("format", "j1")
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
// wttr.in prefers this header to return JSON
|
||||
req.Header.Set("User-Agent", "curl/7.0")
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sending request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
|
||||
var apiResp wttrResponse
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling response: %w", err)
|
||||
}
|
||||
|
||||
if len(apiResp.CurrentCondition) == 0 {
|
||||
return nil, fmt.Errorf("no weather data available for location: %s", location)
|
||||
}
|
||||
|
||||
current := apiResp.CurrentCondition[0]
|
||||
|
||||
// Parse temperature
|
||||
temp, err := strconv.ParseFloat(current.TempC, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing temperature: %w", err)
|
||||
}
|
||||
|
||||
// Parse humidity
|
||||
humidity, err := strconv.Atoi(current.Humidity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing humidity: %w", err)
|
||||
}
|
||||
|
||||
// Get description
|
||||
description := "unknown"
|
||||
if len(current.WeatherDesc) > 0 {
|
||||
description = current.WeatherDesc[0].Value
|
||||
}
|
||||
|
||||
// Get location name
|
||||
locationName := location
|
||||
if len(apiResp.NearestArea) > 0 && len(apiResp.NearestArea[0].AreaName) > 0 {
|
||||
locationName = apiResp.NearestArea[0].AreaName[0].Value
|
||||
}
|
||||
|
||||
return &domain.WeatherInfo{
|
||||
Location: locationName,
|
||||
Temperature: temp,
|
||||
Description: description,
|
||||
Humidity: humidity,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user